Quantcast

emacs making itself sticky and copyToAllToggle

Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

emacs making itself sticky and copyToAllToggle

Antoine Beaupré
Hi,

I've been working on improving the integration between the great
[writeroom-mode][] and Xmonad here, in what is probably the worst case
of procrastination, yak-shaving level. Writeroom is a "distraction-free
writing" mode for emacs. It turns out the "sticky" signal it sends is
not recognized by Xmonad, a known limitation which I will not expand on
here - other people more familiar with Xmonad may fix or talk about this
better than me.

 [writeroom-mode]: https://github.com/joostkremers/writeroom-mode/

But, needing to scratch that particular itch, and with the help of
clever people from the IRC channel, I was able to basically accomplish
what I needed. This involved creating a new function which I think could
be useful in the CopyWindow library:

-- | Toggle between "copyToAll" or "killAllOtherCopies". Copies to all
-- workspaces, or remove from all other workspaces, depending on
-- previous state (checked with "wsContainingCopies").
copyToAllToggle :: X ()
copyToAllToggle = do
    -- check which workspaces have copies
    copies <- wsContainingCopies
    if null copies
      then windows copyToAll -- no workspaces, make sticky
      else killAllOtherCopies -- already other workspaces, unstick

There are probably better ways of implementing this directly in the
CopyWindow code - wsContainingCopies, in particular, is probably
overkill. But it's all I can use directly from my xmonad.hs, so that's
what I did.

The other bit I needed was something to trigger that function from the
outside. I rejected the ServerMode hook because it looked a bit too
complicated and there is a built-in facility within X that works without
this, which, from Emacs' point of view, is the `x-send-client-message`
function. So I made up a new message identifier and wrote a event hook
handler to process it:

-- | handle X client messages that tell Xmonad to make a window appear
-- on all workspaces
--
-- this should really be using _NET_WM_STATE and
-- _NET_WM_STATE_STICKY. but that's more complicated: then we'd need
-- to inspect a window and figure out the current state and act
-- accordingly. I am not good enough with Xmonad to figure out that
-- part yet.
--
-- Instead, just check for the relevant message and check if the
-- focused window is already on all workspaces and toggle based on
-- that.
--
-- this is designed to interoperate with Emacs's writeroom-mode module
-- and called be called from elisp with:
--
-- (x-send-client-message nil 0 nil "XMONAD_COPY_ALL_SELF" 8 '(0))
myClientMessageEventHook :: Event -> X All
myClientMessageEventHook (ClientMessageEvent {ev_message_type = mt, ev_data = dt}) = do
  dpy <- asks display
  -- the client message we're expecting
  copyAllMsg <- io $ internAtom dpy "XMONAD_COPY_ALL_SELF" False
  -- if the event matches the message we expect, toggle sticky state
  when (mt == copyAllMsg && dt /= []) $ do
    copyToAllToggle
  -- we processed the event completely
  return $ All True

All that was left was to hook that into emacs, and I was done! Whoohoo!
Full screen total domination, distraction free work! :)

Attached is the patch to my configuration files that accomplished the
whole thing.

I would love to hear from others what they think of that approach, if
they have improvements or if the above copyToAllToggle function could be
merged in. Ideally, Xmonad would just parse the STICKY client messages
and do the right thing - maybe even directly in CopyWindow - but I have
found this enough Haskell for one day.. :)

Thanks for your attention,

A.

--
Instead of worrying about what somebody else is going to do, which is
not under your control, the important thing is, what are you going to
decide about what is under your control?
                         - Richard Stallman

From 113ae256b9b010dc9159cac24d1348438fce863a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= <[hidden email]>
Date: Thu, 13 Apr 2017 21:25:25 -0400
Subject: [PATCH] hook writeroom mode into sticky xmonad desktops

this allows me to not only write in fullscreen, but also make the
emacs window hide any other virtual desktop, because then the windows
shows up, in fullscreen, on all desktops.

this is a horrible hack, but it works. it sends a custom X message
arbitarily chosen ("XMONAD_COPY_TO_ALL_SELF") to the X server which
gets caught by Xmonad in a event hook.

wrote a copyToAllToggle function that should probably be merged
upstream, and also reuse it in the keybinding because it's so
convenient.

this would be better served by the standard "_NET_WM_STATE_STICKY"
flag, but I leave that as an exercise to the reader (AKA I have no
idea how to deal with those flags).

writeroom-mode probably already sets that flag and sends the message,
we would just need to figure out how to parse the message and find the
state properly. because it's a "toggle" message, we don't actually
know the state directly from the message (presumably) so we need to
look at the state of the focused window. we may also need to manually
update the state of the window as well.
---
 .emacs            |  6 +++++-
 .emacs-custom     |  2 +-
 .xmonad/xmonad.hs | 51 ++++++++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 52 insertions(+), 7 deletions(-)

diff --git a/.emacs b/.emacs
index f25f299..2f717b0 100644
--- a/.emacs
+++ b/.emacs
@@ -526,7 +526,11 @@ also does so and we don't bother doing excursions around.
   :init
   (defun wm-toggle-fullscreen (_)
     (x-send-client-message nil 0 nil "_NET_WM_STATE" 32
-                           '(2 "_NET_WM_STATE_FULLSCREEN" 0))))
+                           '(2 "_NET_WM_STATE_FULLSCREEN" 0)))
+  (defun xmonad-toggle-sticky (_)
+    ;; this requires a special piece of code on the xmonad side to
+    ;; handle that event and do a copyToAllToggle
+    (x-send-client-message nil 0 nil "XMONAD_COPY_ALL_SELF" 8 '(0))))
 
 ;; 800ms
 ;; for notmuch org-mode links
diff --git a/.emacs-custom b/.emacs-custom
index 64ffd23..9d34b17 100644
--- a/.emacs-custom
+++ b/.emacs-custom
@@ -217,7 +217,7 @@
  '(word-wrap t)
  '(writeroom-global-effects
    (quote
-    (writeroom-set-alpha writeroom-set-menu-bar-lines writeroom-set-tool-bar-lines writeroom-set-vertical-scroll-bars writeroom-set-sticky wm-toggle-fullscreen)))
+    (writeroom-set-alpha writeroom-set-menu-bar-lines writeroom-set-tool-bar-lines writeroom-set-vertical-scroll-bars writeroom-set-sticky wm-toggle-fullscreen xmonad-toggle-sticky)))
  '(writeroom-restore-window-config t)
  '(x-stretch-cursor t))
 (custom-set-faces
diff --git a/.xmonad/xmonad.hs b/.xmonad/xmonad.hs
index 57338b2..43d296e 100644
--- a/.xmonad/xmonad.hs
+++ b/.xmonad/xmonad.hs
@@ -104,7 +104,7 @@ import XMonad.Prompt.Window (windowPromptBringCopy)
 import XMonad.Prompt.XMonad (xmonadPrompt)
 
 -- to make windows "sticky" on all desktops (mod-v/V)
-import XMonad.Actions.CopyWindow (copyToAll,killAllOtherCopies,kill1)
+import XMonad.Actions.CopyWindow (wsContainingCopies,copyToAll,killAllOtherCopies,kill1)
 -- to toggle between workspaces
 import XMonad.Actions.CycleWS
 
@@ -119,6 +119,9 @@ import XMonad.Hooks.ManageHelpers
 import System.IO
 import System.Exit
 
+-- for "All"
+import Data.Monoid
+
 -- float some windows by default
 myManageHook = composeAll
     [ manageDocks
@@ -208,6 +211,46 @@ killsoft = do ss <- gets windowset
               whenJust (W.peek ss) $ \w -> when (W.member w $ delete'' w ss) $ windows $ delete'' w
        where delete'' w = W.modify Nothing (W.filter (/= w))
 
+-- | handle X client messages that tell Xmonad to make a window appear
+-- on all workspaces
+--
+-- this should really be using _NET_WM_STATE and
+-- _NET_WM_STATE_STICKY. but that's more complicated: then we'd need
+-- to inspect a window and figure out the current state and act
+-- accordingly. I am not good enough with Xmonad to figure out that
+-- part yet.
+--
+-- Instead, just check for the relevant message and check if the
+-- focused window is already on all workspaces and toggle based on
+-- that.
+--
+-- this is designed to interoperate with Emacs's writeroom-mode module
+-- and called be called from elisp with:
+--
+-- (x-send-client-message nil 0 nil "XMONAD_COPY_ALL_SELF" 8 '(0))
+toggleStickyEventHook :: Event -> X All
+toggleStickyEventHook (ClientMessageEvent {ev_message_type = mt, ev_data = dt}) = do
+  dpy <- asks display
+  -- the client message we're expecting
+  copyAllMsg <- io $ internAtom dpy "XMONAD_COPY_ALL_SELF" False
+  -- if the event matches the message we expect, toggle sticky state
+  when (mt == copyAllMsg && dt /= []) $ do
+    copyToAllToggle
+  -- we processed the event completely
+  return $ All True
+
+-- | Toggle between "copyToAll" or "killAllOtherCopies". Copies to all
+-- workspaces, or remove from all other workspaces, depending on
+-- previous state (checked with "wsContainingCopies").
+copyToAllToggle :: X ()
+copyToAllToggle = do
+    -- check which workspaces have copies
+    copies <- wsContainingCopies
+    if null copies
+      then windows copyToAll -- no workspaces, make sticky
+      else killAllOtherCopies -- already other workspaces, unstick
+
+
 -- main config declaration
 myConfig = defaultConfig {
          modMask = modm
@@ -215,7 +258,7 @@ myConfig = defaultConfig {
        , focusedBorderColor = "#333333"
        , manageHook = myManageHook
        , terminal = "x-terminal-emulator"
-       , handleEventHook = handleEventHook defaultConfig <+> fullscreenEventHook
+       , handleEventHook = handleEventHook defaultConfig <+> fullscreenEventHook <+> toggleStickyEventHook
        , layoutHook = myLayoutHook
     } `additionalKeys` [
     ((noModMask         , xK_Pause), spawn "xscreensaver-command -lock")
@@ -243,9 +286,7 @@ myConfig = defaultConfig {
   , ((modm              , xK_f     ), toggleFloat                           )
   , ((modm              , xK_m     ), withFocused $ sendMessage . maximizeRestore )
   -- Make focused window always visible
-  , ((modm              , xK_v     ), windows copyToAll                     )
-  -- Toggle window state back
-  , ((modm .|. shiftMask, xK_v     ),  killAllOtherCopies                   )
+  , ((modm              , xK_v     ), copyToAllToggle                       )
   -- used to banish a window from the current workspace, if it's also elsewhere
   , ((modm              , xK_c     ), killsoft                                 )
   -- kill even if it's on multiple workspaces
--
2.11.0


_______________________________________________
xmonad mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/xmonad
Loading...