Difference between pages "Xmonad/Using xmonad in MATE" and "LUI"

From HaskellWiki
< Xmonad(Difference between pages)
Jump to navigation Jump to search
(explain how to get mod4 back)
 
m (To be removed if no new content appears...)
 
Line 1: Line 1:
  +
= LUI =
{{xmonad}}
 
[[Category:xmonad]]
 
   
== Using xmonad in MATE ==
+
== A purely functional widget set ==
MATE is a supported fork of Gnome 2, with various components renamed to avoid collisions with Gnome components. At present, Fedora ships both MATE and an xmonad session using it (<code>xmonad-mate</code> package); for other platforms, look for MATE in your package manager or check http://mate-desktop.org.
 
   
  +
A LUI widget has a very simple pure semantic model. It is a function from a model that it is editing to an image, size, and mapping of event groups to documentation and new models.
The current development version of <code>xmonad-contrib</code> from git has an <code>XMonad.Config.Mate</code> which should work out of the box on most platforms. For earlier versions, you may want to copy <code>XMonad.Config.Gnome</code> to <code>~/.xmonad/lib/XMonad/Config/Mate.hs</code> and replace (matching case as appropriate) all instances of <code>gnome</code> with <code>mate</code>. This will affect the terminal, the session manager connection, and the X11 message sent to activate the run command dialog, among other things.
 
   
  +
<code>type Widget model = model -> (Image, Size, Map EventGroup (Documentation, Event -> model))</code>
== Replacing the default window manager ==
 
You will need to create a <code>freedesktop.org</code> desktop file for <code>xmonad</code>, probably in <code>/usr/share/applications/xmonad.desktop</code>:
 
   
  +
An Image is semantically an "infinite mapping of coordinates to colors", Size is the actual part of the infinite Image that the widget resides in.
[Desktop Entry]
 
Type=Application
 
Name=XMonad
 
Exec=/usr/bin/xmonad
 
NoDisplay=true
 
X-GNOME-WMName=XMonad
 
X-GNOME-Autostart-Phase=WindowManager
 
X-GNOME-Provides=windowmanager
 
X-GNOME-Autostart-Notify=true
 
   
  +
The Map documents what each EventGroup does, additionally providing a function to map specific Event values to their handling, which is limited to returning new states of the model.
To replace <code>marco</code> with <code>xmonad</code> for all sessions, use the following (per user):
 
   
  +
LUI only has keyboard support as of yet.
dconf write /org/mate/session/required-components/windowmanager xmonad
 
   
  +
== Widgets currently exported by LUI ==
Alternatively, you may need to set this via gsettings on distributions such as Arch
 
   
  +
* Grid -- places child widgets in a grid, and allows moving the keyboard focus (selected child).
gsettings set org.mate.session.required-components.windowmanager xmonad
 
   
  +
* Box -- Horizontal/Vertical boxes are just specializations of Grids to a size of 1 in one of the dimensions.
(TODO: alternative session file)
 
   
  +
* TextEdit -- A standard line text editor
== Recent MATE with <code>window-manager-launcher</code> ==
 
   
  +
* TextView -- A simple label, cannot be edited
Recently Linux Mint upgraded its MATE to use a separate script to start the window manager; as shipped, it only works with a limited number of window managers that does not include xmonad. I have minimally modified it to support other window managers, including a first cut at user-installed ones. (If someone has a better way to handle this, please do so; I have no way to host this file currently.) My changes are both marked with <code># sigh</code>.
 
   
  +
* Space -- A simple spacer widget
<pre style="white-space: pre;">
 
#!/usr/bin/python3
 
   
  +
* FocusDelegator -- Wrap your widgets with this widget, and the user will be able to select the widget itself (in which state the widget does not have focus, but is selected).
import sys
 
import os
 
import gettext
 
import signal
 
import subprocess
 
import time
 
import gi
 
from gi.repository import Gio
 
   
  +
* Adapter -- Allows adapting an existing widget's model (with a Data.Accessor), image or size.
# i18n
 
gettext.install("mintdesktop", "/usr/share/linuxmint/locale")
 
   
  +
* KeysTable -- Given a map of event handlers, can display the documentation of the event groups to the user.
settings = Gio.Settings("com.linuxmint.desktop")
 
   
  +
* Scroll -- A sized "scrolling window" into a larger widget. No scroll bars yet...
# Detect which DE is running
 
if "XDG_CURRENT_DESKTOP" not in os.environ:
 
print ("window-manager-launcher: XDG_CURRENT_DESKTOP is not set! Exiting..")
 
sys.exit(0)
 
   
  +
* Unfocusable -- Prevent focus from going into the child widget, but still display the child widget.
current_desktop = os.environ["XDG_CURRENT_DESKTOP"]
 
   
  +
= Focus delegation =
if current_desktop not in ["MATE", "XFCE"]:
 
print ("Current desktop %s is not supported." % current_desktop)
 
sys.exit(0)
 
   
  +
The top-level widget always has focus. It may choose to pass its focus to a child widget, if it has any. For example, a Grid widget will pass its focus to the selected child. A FocusDelegator will pass its focus to its selected child if it is in child mode, and stop doing so when going to self-mode.
if current_desktop == "MATE":
 
wm = settings.get_string("mate-window-manager")
 
else:
 
wm = settings.get_string("xfce-window-manager")
 
   
  +
To implement focus delegation, widgets simply merge the child widget's event map with their own event map however they see fit.
# Kill all compositors/managers first
 
p = subprocess.Popen(['ps', '-u', str(os.getuid())], stdout=subprocess.PIPE)
 
out, err = p.communicate()
 
processes_found = False
 
for process in ['compton', "marco", "xfwm4", "compiz", "metacity", "openbox", "awesome" ]:
 
for line in out.splitlines():
 
pname = line.decode('utf-8').split()[-1]
 
if process in pname:
 
pid = int(line.split(None, 1)[0])
 
print ("Killing pid %d (%s)" % (pid, pname))
 
try:
 
os.kill(pid, signal.SIGKILL)
 
except Exception as e:
 
print ("Failed to kill process %d (%s): %s" % (pid, pname, e))
 
processes_found = True
 
   
  +
A navigational widget (one that implements some notion of a "cursor") will typically prefer its children key bindings over its own. Unless overridden by a child widget's key mapping, it will merge its event map into the child's. When its cursor "hits the end", the widget stops mapping the keys that no longer have meaning, and thereby exposes any parent widget mapping.
if (processes_found):
 
# avoid race conditions before launching new WMs
 
print ("Waiting 0.2 seconds...")
 
time.sleep(0.2)
 
   
  +
The above trick allows a hierarchy of navigational widgets to exist, allowing the user to use the same keys to go in a general direction. In practice, the user is sending navigation keys to different widgets at different times, but the experience the user gets is that of moving a global cursor.
# sigh
 
os.environ["PATH"] += ':' + os.path.expanduser("~/.local/bin")
 
   
  +
= Use of accessors =
if wm == "marco":
 
settings = Gio.Settings("org.mate.Marco.general")
 
settings.set_boolean("compositing-manager", False)
 
subprocess.Popen(["marco", "--no-composite", "--replace"])
 
elif wm == "marco-composite":
 
settings = Gio.Settings("org.mate.Marco.general")
 
settings.set_boolean("compositing-manager", True)
 
subprocess.Popen(["marco", "--composite", "--replace"])
 
elif wm == "marco-compton":
 
settings = Gio.Settings("org.mate.Marco.general")
 
settings.set_boolean("compositing-manager", False)
 
subprocess.Popen(["marco", "--no-composite", "--replace"])
 
time.sleep(2)
 
subprocess.Popen(["compton", "--backend", "glx", "--vsync", "opengl-swc"])
 
elif wm == "xfwm4":
 
subprocess.Popen(["xfconf-query", "-c", "xfwm4", "-p", "/general/use_compositing", "--set", "false"])
 
subprocess.Popen(["xfwm4", "--compositor=off", "--replace"])
 
elif wm == "xfwm4-composite":
 
subprocess.Popen(["xfconf-query", "-c", "xfwm4", "-p", "/general/use_compositing", "--set", "true"])
 
subprocess.Popen(["xfwm4", "--compositor=on", "--replace"])
 
elif wm == "xfwm4-compton":
 
subprocess.Popen(["xfconf-query", "-c", "xfwm4", "-p", "/general/use_compositing", "--set", "false"])
 
subprocess.Popen(["xfwm4", "--compositor=off", "--replace"])
 
time.sleep(2)
 
subprocess.Popen(["compton", "--backend", "glx", "--vsync", "opengl-swc"])
 
elif wm == "compiz":
 
subprocess.Popen(["compiz", "--replace"])
 
elif wm == "metacity":
 
settings = Gio.Settings("org.gnome.metacity")
 
settings.set_boolean("compositing-manager", False)
 
subprocess.Popen(["metacity", "--replace"])
 
elif wm == "metacity-composite":
 
settings = Gio.Settings("org.gnome.metacity")
 
settings.set_boolean("compositing-manager", True)
 
subprocess.Popen(["metacity", "--replace"])
 
elif wm == "metacity-compton":
 
settings = Gio.Settings("org.gnome.metacity")
 
settings.set_boolean("compositing-manager", False)
 
subprocess.Popen(["metacity", "--replace"])
 
time.sleep(2)
 
subprocess.Popen(["compton", "--backend", "glx", "--vsync", "opengl-swc"])
 
elif wm == "openbox":
 
subprocess.Popen(["openbox", "--replace"])
 
elif wm == "openbox-compton":
 
subprocess.Popen(["openbox", "--replace"])
 
time.sleep(2)
 
subprocess.Popen(["compton", "--backend", "glx", "--vsync", "opengl-swc"])
 
elif wm == "awesome":
 
# subprocess.call(["killall", "marco", "xfwm4", "compiz", "metacity", "openbox", "awesome"]) # Kill all other window managers that might possibly still be running
 
# time.sleep(0.1) # Wait some time until really all other window managers are killed otherwise awesome won't start up
 
subprocess.Popen(["awesome"])
 
if current_desktop == "MATE": # The mate panel seems to move up a bit when starting awesome
 
subprocess.Popen(["mate-panel", "--replace"]) # this seems to fix this issue
 
# sigh
 
else:
 
subprocess.Popen([wm, "--replace"])
 
</pre>
 
   
  +
Widgets only edit a model, and have no additional state of their own. This means that the entire widget state, including its GUI state is stored within the model. A widget is typically created with an accessor (See Data.Accessor) to the model, that can pull the exact information it needs from the entire model.
== Freeing up Mod4 ==
 
   
  +
This model makes no distinction between the importance of a cursor, and of the text that a TextEdit edits, for example. The accessor given to the TextEdit widget, however, allows complete control of exactly where the text and cursor are stored within the model. One can store important text in an important part of the data structure, while placing the cursor in some GUI-state part of the model.
The MATE Advanced Menu widget will grab `mod4`. You can switch to a different menu widget or reconfigure the menu widget:
 
   
  +
== Transactional updates ==
# Right click the menu widget
 
  +
# Select "Preferences"
 
  +
This model is *transactional*, because a widget can only return a whole new model at once. For example, a TextEdit must update both the text and cursor at the same time. The widget user can of course reject any such update which would reject the entire transaction, without accidentally rejecting a text edit but letting the cursor move outside valid range.
# Select the "Main button" pane (this should already be visible)
 
  +
# Click the "Keyboard Shortcut" button, which will show the "Menu" key
 
  +
[[Category:Pages to be removed]]
# Press Backspace to clear the button, or some other button if you want to map a different key to it.
 

Latest revision as of 08:07, 23 May 2021

LUI

A purely functional widget set

A LUI widget has a very simple pure semantic model. It is a function from a model that it is editing to an image, size, and mapping of event groups to documentation and new models.

type Widget model = model -> (Image, Size, Map EventGroup (Documentation, Event -> model))

An Image is semantically an "infinite mapping of coordinates to colors", Size is the actual part of the infinite Image that the widget resides in.

The Map documents what each EventGroup does, additionally providing a function to map specific Event values to their handling, which is limited to returning new states of the model.

LUI only has keyboard support as of yet.

Widgets currently exported by LUI

  • Grid -- places child widgets in a grid, and allows moving the keyboard focus (selected child).
  • Box -- Horizontal/Vertical boxes are just specializations of Grids to a size of 1 in one of the dimensions.
  • TextEdit -- A standard line text editor
  • TextView -- A simple label, cannot be edited
  • Space -- A simple spacer widget
  • FocusDelegator -- Wrap your widgets with this widget, and the user will be able to select the widget itself (in which state the widget does not have focus, but is selected).
  • Adapter -- Allows adapting an existing widget's model (with a Data.Accessor), image or size.
  • KeysTable -- Given a map of event handlers, can display the documentation of the event groups to the user.
  • Scroll -- A sized "scrolling window" into a larger widget. No scroll bars yet...
  • Unfocusable -- Prevent focus from going into the child widget, but still display the child widget.

Focus delegation

The top-level widget always has focus. It may choose to pass its focus to a child widget, if it has any. For example, a Grid widget will pass its focus to the selected child. A FocusDelegator will pass its focus to its selected child if it is in child mode, and stop doing so when going to self-mode.

To implement focus delegation, widgets simply merge the child widget's event map with their own event map however they see fit.

A navigational widget (one that implements some notion of a "cursor") will typically prefer its children key bindings over its own. Unless overridden by a child widget's key mapping, it will merge its event map into the child's. When its cursor "hits the end", the widget stops mapping the keys that no longer have meaning, and thereby exposes any parent widget mapping.

The above trick allows a hierarchy of navigational widgets to exist, allowing the user to use the same keys to go in a general direction. In practice, the user is sending navigation keys to different widgets at different times, but the experience the user gets is that of moving a global cursor.

Use of accessors

Widgets only edit a model, and have no additional state of their own. This means that the entire widget state, including its GUI state is stored within the model. A widget is typically created with an accessor (See Data.Accessor) to the model, that can pull the exact information it needs from the entire model.

This model makes no distinction between the importance of a cursor, and of the text that a TextEdit edits, for example. The accessor given to the TextEdit widget, however, allows complete control of exactly where the text and cursor are stored within the model. One can store important text in an important part of the data structure, while placing the cursor in some GUI-state part of the model.

Transactional updates

This model is *transactional*, because a widget can only return a whole new model at once. For example, a TextEdit must update both the text and cursor at the same time. The widget user can of course reject any such update which would reject the entire transaction, without accidentally rejecting a text edit but letting the cursor move outside valid range.