Remote Controlling XMonad: Cases and Materials

Hi, this is a follow up of the thread started by Zed Lopez[1] on controlling XMonad remotely. Stefan O'Rear proposed a solution[2] and I implemented it[3]. We briefly discussed this solution on the #xmonad channel[4] and I thought some more documentation was needed on the methodology to use to achieve the goal. allbery_b reported that such an approach has been used by Mozilla, so I wanted to find more examples to study. Zed said that it was missing this feature from Ratpoison.[5] BTW, the successor of Ratpoison, Stumpwm is developed in Common Lisp...;-) So I went to have a look at how ratpoison deals with the subject and this is what I discovered[7,8]. This is what happens in Ratpoison: a. the Client (that sends the command): 1. creates w simple window 1 pixel width 2. sets an atom (RP_COMMAND) with a command to that window 3. sets an atom (RP_COMMAND_REQUEST) in the root window with XChangeProperty: this atom carries the first window id 4. waits for a propertyNotify to be sent to this window b. Ratpoison: 1. gets the property of the root window (which will return the id of the window containing the command string) 2. searches the window with the command string and executes the command 3. writes an atom (RP_COMMAND_RESULT) to the window with the command result. This will send the propertyNotify the will unblock the client. A similar approach is also used by Enlightment (see src/comms.c) and its remote shell Eesh (see Eesh.xs). WMII, instead, uses libipx and a Plan9 approach (I must confess I don't know much about P9), while DWM has no remote control capability. Now, attached you'll find two archives. The first one is a simple way of running commands from an external program, without a two way communication. It is basically a refactoring of the patch I've already sent[3]. It is simple and effective. I had no problem running it here. The second archive (xrcRatpoisonWay.tar.gz) instead is the ratpoison implementation: I literally copied the algorithm. And there's is a problem. This code produces an X server crash every now and then. I cannot reproduce it and, no matter how many times I read it, I cannot find a bug in my code. I believe there's something wrong with Xlib.Extras.rawGetWindowProperty (a null pointer?). Indeed if you change in ServerMode.hs: let c' = Just [118,105,101,119,49] --c' <- io $ getWindowProperty8 dpy c win no more crashes. You may also see that the command execution sometimes fails. Is that something that used to happen in Ratpoison too? Anyway it is something I think it is worth to investigate a bit more. And I think I will. BTW, these patches require a vary small modification to XMonad itself. XMonad has just to wait for a propertyNotify event sent to the root window. If that happens a hook is called (I named that hook serverHook). I do not want you to equivocate: I'm not pushing for this patch to be included, but I thought to report here some of the information I was able to gather on the subject. Moreover, as far as I can understand, there's is not so much interest in this feature. But here this stuff proved to be quite addictive...;-) Thanks for your kind attention. Andrea [1] see http://www.haskell.org/pipermail/xmonad/2007-July/001502.html [2] see http://www.haskell.org/pipermail/xmonad/2007-July/001509.html [3] see http://www.haskell.org/pipermail/xmonad/2007-July/001533.html [4] see http://www.cse.unsw.edu.au/~dons/irc/xmonad/2007-07-26.txt [5] http://www.nongnu.org/ratpoison/ [6] http://www.nongnu.org/stumpwm/ [7] see also http://cvs.savannah.nongnu.org/viewvc/ratpoison/src/communications.c?root=ratpoison&view=markup [8] see also receive_command() and execute_remote_command() and related comments here: http://cvs.savannah.nongnu.org/viewvc/ratpoison/src/events.c?root=ratpoison&view=markup [9] http://search.cpan.org/~rbs/Eesh-0.3/

Andrea Rossato wrote:
I do not want you to equivocate: I'm not pushing for this patch to be included, but I thought to report here some of the information I was able to gather on the subject. Moreover, as far as I can understand, there's is not so much interest in this feature. But here this stuff proved to be quite addictive...;-)
I'm one of the people who is interested in having this capability (a small example of use: changing workspaces or setting layouts via dzen menus), and it's great to see someone investigating it. Thanks, Andrea. Nathan

Nathan Howell schrieb:
Andrea Rossato wrote:
I do not want you to equivocate: I'm not pushing for this patch to be included, but I thought to report here some of the information I was able to gather on the subject. Moreover, as far as I can understand, there's is not so much interest in this feature. But here this stuff proved to be quite addictive...;-)
I'm one of the people who is interested in having this capability (a small example of use: changing workspaces or setting layouts via dzen menus), and it's great to see someone investigating it. Thanks, Andrea.
Nathan
I like the idea too! Maybe this is a little bit off-topic but why don't use a standard like dbus? There are allready some haskell bindings out there (http://neugierig.org/software/hdbus/ -- ok they were not updated for about a year but maybe it could be a point to start from though). Tobias

On Mon, Jul 30, 2007 at 08:47:11PM +0200, Tobias Hammerschmidt wrote:
I like the idea too! Maybe this is a little bit off-topic but why don't use a standard like dbus? There are allready some haskell bindings out there (http://neugierig.org/software/hdbus/ -- ok they were not updated for about a year but maybe it could be a point to start from though).
well, we could have a look at dbus too, but the problem is finding a way to listening for external commands without blocking XMonad (which is already blocked, in the "forever" loop, by nextEvent). I can send commands through a socket (very easy, see example below), but how can I get XMonad to wait over a socket without blocking the main thread? Concurrency is not going to give us an easy solution, AFAIK. Perhaps it's just me, but I find this problem quite difficult. If you have any idea please let me know. All the best, Andrea 1. Save this as XMonadContrib/ServerMode.hs ----- module XMonadContrib.ServerMode import Network import System.IO import XMonadContrib.Commands serverMode :: X () serverMode = do com <- io $ serverMode' runCommand' com -- if I now add a line with "serverMode" XMonad is not very useful anymore...;-) serverMode' :: IO String serverMode' = do s <- listenOn (PortNumber 6913) (h, hn, pn) <- accept s hSetBuffering h NoBuffering sClose s com <- hGetLine h hClose h return com ---- 2. import it from Config.hs 3. add a key binding that calls serverMode 4. use this code to send commands (you can run one command then serverMode must be restarted. XMonad will be blocked till a command is recieved) --- module Main where import System.Environment import System.Exit import Network usage :: String -> String usage n = "Usage: " ++ n ++ " command\nSend a command to a running instance of XMonad" main :: IO () main = do args <- getArgs pn <- getProgName let com = case args of [] -> error $ usage pn w -> (w !! 0) sendTo "localhost" (PortNumber 6913) com exitWith ExitSuccess

On Jul 31, 2007, at 5:37 , Andrea Rossato wrote:
On Mon, Jul 30, 2007 at 08:47:11PM +0200, Tobias Hammerschmidt wrote:
I like the idea too! Maybe this is a little bit off-topic but why don't use a standard like dbus? There are allready some haskell bindings out there (http://neugierig.org/software/hdbus/ -- ok they were not updated for about a year but maybe it could be a point to start from though).
well, we could have a look at dbus too, but the problem is finding a way to listening for external commands without blocking XMonad (which is already blocked, in the "forever" loop, by nextEvent).
I would say that passing commands through the X server is *the* correct way to communicate between X11 clients, including between a window manager and another client; it is what relates them to each other. Unfortunately, while the ideal way to do it is XSendEvent, that is a seriously broken and insecure API --- hence the use of properties. That said, you might want to look at http://sequence.complete.org/ node/257 for an example of selecting on multiple connections, using a command channel socket and the X11 ConnectionNumber($display) macro. (Just be careful to do all other X11 actions in the main thread; only forkIO the listener on ConnectionNumber, not the actual X11 actions.) -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

On Sun, Jul 29, 2007 at 01:22:32PM +0200, Andrea Rossato wrote:
The second archive (xrcRatpoisonWay.tar.gz) instead is the ratpoison implementation: I literally copied the algorithm. And there's is a problem. This code produces an X server crash every now and then. I cannot reproduce it and, no matter how many times I read it, I cannot find a bug in my code. I believe there's something wrong with Xlib.Extras.rawGetWindowProperty (a null pointer?).
An X server crash is by definition not your fault. X.org tracker: bugs.freedesktop.org, product "xorg" Stefan

On Jul 29, 2007, at 14:25 , Stefan O'Rear wrote:
On Sun, Jul 29, 2007 at 01:22:32PM +0200, Andrea Rossato wrote:
problem. This code produces an X server crash every now and then. I cannot reproduce it and, no matter how many times I read it, I cannot find a bug in my code. I believe there's something wrong with Xlib.Extras.rawGetWindowProperty (a null pointer?).
An X server crash is by definition not your fault.
Which is basically what I said in #xmonad. The X server should *never* crash due to a client call (bad or otherwise). -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

On Sun, Jul 29, 2007 at 11:25:43AM -0700, Stefan O'Rear wrote:
On Sun, Jul 29, 2007 at 01:22:32PM +0200, Andrea Rossato wrote:
The second archive (xrcRatpoisonWay.tar.gz) instead is the ratpoison implementation: I literally copied the algorithm. And there's is a problem. This code produces an X server crash every now and then. I
An X server crash is by definition not your fault.
X.org tracker: bugs.freedesktop.org, product "xorg"
Stefan
I think I expressed myself inappropriately: as far as I know it's my patched xmonad crashing with a memory dump. And I think this is related to rawGetWindowProperty, even though if I'm still looking for a bug in *my* code. Sorry for the misunderstanding. Andrea

On Sun, Jul 29, 2007 at 01:22:32PM +0200, Andrea Rossato wrote:
BTW, these patches require a vary small modification to XMonad itself. XMonad has just to wait for a propertyNotify event sent to the root window. If that happens a hook is called (I named that hook serverHook).
Well, I build up such a case... typical of a lawyer I'd say...;-) Now I'm studying some of David's code, and LayoutHelpers, specifically. There's no need at all to modify XMonad, this is just a simple contrib module I'm going to submit shortly. Unfortunately David did not document much his code, and some of it, even if designed to help others, remains unused. I'll try to write some documentation too. I'm sure David doesn't mind. Ciao Andrea

On Tue, Aug 07, 2007 at 03:45:56PM +0200, Andrea Rossato wrote:
Well, I build up such a case... typical of a lawyer I'd say...;-)
As I promised this is a module that works without patching XMonad. I feel a bit ashamed, because it also highlights my basic ignorance of the layout system... but I must confess I do not care that much: tabbed, tall and full are definitely enough for me, so I never thought about writing a layout. This module exports a function that takes a layout and will make that layout respond to commands send with the application that is commented out at the end of the file. The layout must be actually in use. More layout can be set in "serverMode". If you use tabbed you should not use this module. This is also the reason why I'm not sending it as a patch proposal: I want to investigate where the problems with tabbed arises (btw, the problems are not related to the specific use of LayoutHelpers., since I tried also different approaches). Andrea

On Tue, Aug 07, 2007 at 03:45:56PM +0200, Andrea Rossato wrote:
On Sun, Jul 29, 2007 at 01:22:32PM +0200, Andrea Rossato wrote:
BTW, these patches require a vary small modification to XMonad itself. XMonad has just to wait for a propertyNotify event sent to the root window. If that happens a hook is called (I named that hook serverHook).
Well, I build up such a case... typical of a lawyer I'd say...;-)
Now I'm studying some of David's code, and LayoutHelpers, specifically.
There's no need at all to modify XMonad, this is just a simple contrib module I'm going to submit shortly.
Unfortunately David did not document much his code, and some of it, even if designed to help others, remains unused. I'll try to write some documentation too. I'm sure David doesn't mind.
I will greatly appreciate contributed documentation--or API improvements. LayoutHelpers was basically thrown together quickly to try to create *some* sort of working interface for a Layout transformer, but it isn't really something I can be proud of. :( It's been continually a hack. I have been thinking that we (I?) ought to define a layout monad L that basically would track the state of the layout (something like a state monad stacked on top of X) and return a value. Then we could write much prettier layout code. -- David Roundy Department of Physics Oregon State University

On Tue, Aug 07, 2007 at 11:47:07AM -0700, David Roundy wrote:
On Tue, Aug 07, 2007 at 03:45:56PM +0200, Andrea Rossato wrote:
On Sun, Jul 29, 2007 at 01:22:32PM +0200, Andrea Rossato wrote:
BTW, these patches require a vary small modification to XMonad itself. XMonad has just to wait for a propertyNotify event sent to the root window. If that happens a hook is called (I named that hook serverHook).
Well, I build up such a case... typical of a lawyer I'd say...;-)
Now I'm studying some of David's code, and LayoutHelpers, specifically.
There's no need at all to modify XMonad, this is just a simple contrib module I'm going to submit shortly.
Unfortunately David did not document much his code, and some of it, even if designed to help others, remains unused. I'll try to write some documentation too. I'm sure David doesn't mind.
I will greatly appreciate contributed documentation--or API improvements. LayoutHelpers was basically thrown together quickly to try to create *some* sort of working interface for a Layout transformer, but it isn't really something I can be proud of. :(
It's been continually a hack. I have been thinking that we (I?) ought to define a layout monad L that basically would track the state of the layout (something like a state monad stacked on top of X) and return a value. Then we could write much prettier layout code.
Yes we could. Well, we can... I didn't forget this thread... and I just pushed (in my repos) an EventHook (based on the stuff I coded to answer to nomeata's challenge): Hooks.ServerMode. The code is well documented, with usage examples and all (see here): http://gorgias.mine.nu/xmonad-docs/xmonad-contrib/XMonad-Hooks-ServerMode.ht... Anyway: import XMonad.Hooks.ServerMode layoutHook = eventHook ServerMode $ avoidStruts $ simpleTabbed ||| Full ||| etc.. main = xmonad defaultConfig { layoutHook = myLayouts } Below the code for a client (sendMessage), which will send a command number and xmonad will execute the relative command. If you ask for a wrong command number or for 0 (sendCommand 0), xmonad will print (in ~/.xsession-errors) the list of command numbers with their relative command. One of the nice things of an event hook implemented at the layout level, is that you can have more event hooks (as it is documented here: http://gorgias.mine.nu/xmonad-docs/xmonad-contrib/XMonad-Hooks-EventHook.htm... Cheers, Andrea module Main where import Graphics.X11.Xlib import Graphics.X11.Xlib.Extras import System.Environment import Data.Char usage :: String -> String usage n = "Usage: " ++ n ++ " command number\nSend a command number to a running instance of XMonad" main :: IO () main = do args <- getArgs pn <- getProgName let com = case args of [] -> error $ usage pn w -> (w !! 0) sendCommand com sendCommand :: String -> IO () sendCommand s = do d <- openDisplay "" rw <- rootWindow d $ defaultScreen d a <- internAtom d "XMONAD_COMMAND" False allocaXEvent $ \e -> do setEventType e clientMessage setClientMessageEvent e rw a 32 (fromIntegral (read s)) currentTime sendEvent d rw False structureNotifyMask e sync d False
participants (7)
-
Andrea Rossato
-
Andrea Rossato
-
Brandon S. Allbery KF8NH
-
David Roundy
-
Nathan Howell
-
Stefan O'Rear
-
Tobias Hammerschmidt