diff options
| author | Josh Rahm <rahm@google.com> | 2023-12-13 12:01:31 -0700 |
|---|---|---|
| committer | Josh Rahm <rahm@google.com> | 2023-12-13 12:03:16 -0700 |
| commit | 4cdab9f06cd0ecf5ad7d5ee15dd83c4dc4eb396a (patch) | |
| tree | 792b639c8b9024e0d05827df27523fac6d897c79 /src/Rahm/Desktop | |
| parent | 7c16c50990492f9e6cc2477f8284dc1a2f33d946 (diff) | |
| download | rde-4cdab9f06cd0ecf5ad7d5ee15dd83c4dc4eb396a.tar.gz rde-4cdab9f06cd0ecf5ad7d5ee15dd83c4dc4eb396a.tar.bz2 rde-4cdab9f06cd0ecf5ad7d5ee15dd83c4dc4eb396a.zip | |
Replacing existing binder DSL with a better and more expressive DSL.
This new DSL is cleaner and more powerful. This new DSL allows mixing
key and mouse bindings in submappings, which can be very useful.
Diffstat (limited to 'src/Rahm/Desktop')
| -rw-r--r-- | src/Rahm/Desktop/Keys.hs | 354 | ||||
| -rw-r--r-- | src/Rahm/Desktop/Keys/Dsl.hs | 607 | ||||
| -rw-r--r-- | src/Rahm/Desktop/Keys/Dsl2.hs | 254 | ||||
| -rw-r--r-- | src/Rahm/Desktop/Submap.hs | 78 |
4 files changed, 455 insertions, 838 deletions
diff --git a/src/Rahm/Desktop/Keys.hs b/src/Rahm/Desktop/Keys.hs index 2cc3d79..412d8f5 100644 --- a/src/Rahm/Desktop/Keys.hs +++ b/src/Rahm/Desktop/Keys.hs @@ -60,29 +60,7 @@ import Rahm.Desktop.History historyForward, jumpToLastLocation, ) -import Rahm.Desktop.Keys.Dsl - ( ButtonBinding (..), - ButtonBindings, - Documented (..), - KeyBinding (..), - KeyBindings, - altMask, - altMod, - altgrMod, - bind, - buttonDocumentation, - controlMod, - doc, - documentation, - getConfig, - justMod, - noMod, - rawMask, - runButtons, - runKeys, - shiftMod, - (-|-), - ) +import Rahm.Desktop.Keys.Dsl2 import Rahm.Desktop.Keys.Wml ( addWindowToSelection, clearWindowSelection, @@ -186,9 +164,6 @@ spawnX = spawn safeSpawnX :: String -> [String] -> X () safeSpawnX = safeSpawn -noWindow :: b -> Window -> b -noWindow = const - selectedWindowsColor = BorderColor "#00ffff" "#00ffff" decreaseVolume = spawnX "pactl set-sink-volume @DEFAULT_SINK@ -5%" @@ -249,34 +224,6 @@ button14 = 14 button15 :: Button button15 = 15 -keyBindingToKeymap :: (XConfig l -> KeyBindings) -> KeyMap l -keyBindingToKeymap bindings config = Map.mapWithKey bindingToX (bindings config) - where - bindingToX :: (KeyMask, KeySym) -> Documented KeyBinding -> X () - bindingToX key b = - case b of - Documented _ (Action x) -> x - Documented _ (Submap mapping) -> - -- This is a submap, add it to the pending buffer. - -- - -- This could potentially use the current event in the XState and - -- lookupString to potentially recover the real string typed, but - -- for now, this will do. - pushAddPendingBuffer (keysymToString $ snd key) $ do - submap (Map.mapWithKey bindingToX mapping) - Documented _ (Repeat mapping) -> do - pushAddPendingBuffer (keysymToString $ snd key) $ do - mapM_ (bindingToX key) (Map.lookup key mapping) - fix $ \recur -> do - submap - ( Map.mapWithKey - ( \k b -> do - pushAddPendingBuffer (keysymToString $ snd k) $ - bindingToX k b >> recur - ) - mapping - ) - mapWindows :: (Ord b) => (a -> b) -> W.StackSet i l a s sd -> W.StackSet i l b s sd mapWindows fn (W.StackSet cur vis hidden float) = W.StackSet @@ -290,13 +237,8 @@ mapWindows fn (W.StackSet cur vis hidden float) = data ShiftType = JustShift | ShiftAndFollow | ShiftAndSwap -keymap :: XConfig l -> KeyBindings -keymap = runKeys $ do - config <- getConfig - - let subkeys keysM = Submap (runKeys keysM config) - repeatable keysM = Repeat (runKeys keysM config) - +bindings :: Binder () +bindings = do bind xK_apostrophe $ do justMod $ doc "Jump to a window/tile currently dragging window" $ do @@ -364,33 +306,33 @@ keymap = runKeys $ do doc "Move XMobar to another screen." $ spawnX "pkill -SIGUSR1 xmobar" - bind xK_F1 $ do - justMod $ - doc - "Print this documentation" - ( safeSpawn - "gxmessage" - [ "-fn", - "Source Code Pro", - "Key Bindings\n\n" - ++ documentation (keymap config) - ++ "\n\nButton Bindings\n\n" - ++ buttonDocumentation (mouseMap config) - ] :: - X () - ) - - bind xK_F7 $ do - justMod $ - doc - "Print this documentation to stdout (at LogLevel Info)" - ( logs - Info - "KeyBindings\n\n%s\n\nButtonBindings\n\n%s" - (documentation (keymap config)) - (buttonDocumentation (mouseMap config)) :: - X () - ) + -- bind xK_F1 $ do + -- justMod $ + -- doc + -- "Print this documentation" + -- ( safeSpawn + -- "gxmessage" + -- [ "-fn", + -- "Source Code Pro", + -- "Key Bindings\n\n" + -- ++ documentation (keymap config) + -- ++ "\n\nButton Bindings\n\n" + -- ++ buttonDocumentation (mouseMap config) + -- ] :: + -- X () + -- ) + + -- bind xK_F7 $ do + -- justMod $ + -- doc + -- "Print this documentation to stdout (at LogLevel Info)" + -- ( logs + -- Info + -- "KeyBindings\n\n%s\n\nButtonBindings\n\n%s" + -- (documentation (keymap config)) + -- (buttonDocumentation (mouseMap config)) :: + -- X () + -- ) bind xK_F10 $ do justMod playPauseDoc @@ -423,9 +365,6 @@ keymap = runKeys $ do doc ("Move the current window to screen " ++ show idx) $ withScreen W.shift idx - altgrMod - (logs Info "Test altgr" :: X ()) - bind xK_bracketright $ do justMod $ doc "Increase the gaps between windows." $ @@ -500,7 +439,7 @@ keymap = runKeys $ do bind xK_d $ justMod $ doc "Record (define) macros." $ - subkeys $ do + subbind $ do bind xK_w $ noMod $ doc @@ -746,7 +685,7 @@ keymap = runKeys $ do bind xK_space $ do justMod $ doc "Layout-related bindings" $ - subkeys $ do + subbind $ do bind xK_n $ (noMod -|- justMod) $ doc "Use the next layout in the layout list." $ do @@ -833,7 +772,7 @@ keymap = runKeys $ do bind xK_t $ do justMod $ doc "Spawn a terminal." $ - spawnX (terminal config) + spawnX =<< asks (terminal . config) shiftMod $ doc "Sink the current window into the tiling." $ @@ -843,26 +782,7 @@ keymap = runKeys $ do altMod $ doc "Spawn a floating terminal" $ - spawnX (terminal config ++ " -t Floating\\ Term") - - bind xK_v - $ - -- Allows repeated strokes of M-h and M-l to reduce and increase volume - -- respectively. - justMod - $ doc - "Allows repeated strokes of M-h and M-l to decrease and\n\ - \increase volume respectively" - $ repeatable - $ do - bind xK_h $ - justMod decreaseVolumeDoc - - bind xK_l $ - justMod increaseVolumeDoc - - bind xK_v $ - justMod (return () :: X ()) + spawnX =<< asks ((++ " -t Floating\\ Term") . terminal . config) bind xK_x $ do justMod $ @@ -872,7 +792,7 @@ keymap = runKeys $ do bind xK_z $ do justMod $ doc "Less often used keybindings." $ - subkeys $ do + subbind $ do bind xK_e $ do (justMod -|- noMod) $ doc "Select an emoji" $ @@ -955,7 +875,9 @@ keymap = runKeys $ do logs next "LogLevel set to %s." (show next) bind xF86XK_Calculator $ do - noMod $ spawnX $ terminal config ++ " -t Floating\\ Term -e /usr/bin/env python3" + noMod $ + spawnX + =<< asks ((++ " -t Floating\\ Term -e /usr/bin/env python3") . terminal . config) bind xF86XK_AudioLowerVolume $ do noMod $ spawnX "pactl set-sink-volume @DEFAULT_SINK@ -1%" @@ -990,63 +912,6 @@ keymap = runKeys $ do noMod $ spawnX "set-backlight.sh -0.05" justMod $ spawnX "set-backlight.sh 0.01" rawMask shiftMask $ spawnX "set-backlight.sh 0" - -buttonBindingsToButtonMap :: (XConfig l -> ButtonBindings) -> ButtonsMap l -buttonBindingsToButtonMap bindings config = Map.mapWithKey bindingToX (bindings config) - where - bindingToX :: (ButtonMask, Button) -> Documented ButtonBinding -> (Window -> X ()) - bindingToX click@(mask, btn) = \case - Documented _ (ButtonAction action) -> action - Documented _ (ButtonSubmap sm) -> - pushAddPendingBuffer (printf "b%d " btn) - . submapButtonsWithKey (\_ _ -> return ()) (Map.mapWithKey bindingToX sm) - Documented _ (ButtonContinuous sm) -> \window -> - pushAddPendingBuffer (printf "b%d " btn) $ do - mapM_ (flip (bindingToX click) window) (Map.lookup click sm) - fix $ \recur -> do - submapButtonsWithKey - ( \_ _ -> return () - ) - ( Map.mapWithKey - ( \k b w -> - pushAddPendingBuffer (printf "b%d " (snd k)) $ - bindingToX k b w >> recur - ) - sm - ) - window - -myMouseMoveWindow = - D.mouseMoveWindowAndThen X.focus $ - mconcat - [ D.ifReleased button3 D.sinkOnRelease, - D.ifReleased' button2 $ \w _ -> X.killWindow w - ] - -myMouseResizeAction = - D.mouseResizeWindowAndThen X.focus $ - mconcat - [ D.ifReleased button1 D.sinkOnRelease - ] - -mouseMap :: forall l. XConfig l -> ButtonBindings -mouseMap = runButtons $ do - config <- getConfig - - -- let x button = Map.lookup button (mouseMap config) - - -- let defaultButtons button = - -- fromMaybe (\w -> return ()) $ - -- Map.lookup button (mouseMap config) - let subMouse = ButtonSubmap . flip runButtons config - - continuous buttons = do - let bindingMap = runButtons buttons config - in forM_ (Map.toList bindingMap) $ \((m, b), _) -> do - bind b $ - rawMask m $ - ButtonContinuous bindingMap - bind button1 $ do justMod $ doc @@ -1087,50 +952,63 @@ mouseMap = runButtons $ do doc "Media next" $ noWindow mediaNext - bind button14 $ do - noMod $ - doc "Additional Mouse Bindings" $ - subMouse $ do - bind button3 $ - noMod $ - doc "Drag a workspace to a different screen" $ - noWindow D.dragWorkspace + let button14Binder = do + bind button3 $ + noMod $ + doc "Drag a workspace to a different screen" $ + noWindow D.dragWorkspace + + bind button1 $ + noMod $ + doc "Swap a window with another window by dragging." $ + noWindow D.dragWindow + + bind button14 $ do + noMod $ + doc "Pop the window under the cursor" $ + noWindow $ + click >> sendMessage togglePop + + bind button15 $ do + noMod $ + doc "Spawn 'pavucontrol'" $ + noWindow $ + spawnX "pavucontrol" + + let mediaButtons = + [ (button4, "Increase volume", noWindow increaseVolume), + (button5, "Decrease volume", noWindow decreaseVolume), + (button2, "Play/Pause", noWindow playPause), + (button9, "History Forward", noWindow (viewAdjacentTo pointerWorkspace next)), + (button8, "History Back", noWindow (viewAdjacentTo pointerWorkspace prev)), + (button6, "Media Previous", noWindow mediaPrev), + (button7, "Media Next", noWindow mediaNext) + ] + + continuous $ do + forM_ mediaButtons $ \(b, d, a) -> + bind b $ noMod $ doc d a - bind button1 $ - noMod $ - doc "Swap a window with another window by dragging." $ - noWindow D.dragWindow + bind xK_h $ + justMod decreaseVolumeDoc - bind button14 $ do - noMod $ - doc "Pop the window under the cursor" $ - noWindow $ - click >> sendMessage togglePop + bind xK_l $ + justMod increaseVolumeDoc - bind button15 $ do - noMod $ - doc "Spawn 'pavucontrol'" $ - noWindow $ - spawnX "pavucontrol" - - let mediaButtons = - [ (button4, "Increase volume", noWindow increaseVolume), - (button5, "Decrease volume", noWindow decreaseVolume), - (button2, "Play/Pause", noWindow playPause), - (button9, "History Forward", noWindow (viewAdjacentTo pointerWorkspace next)), - (button8, "History Back", noWindow (viewAdjacentTo pointerWorkspace prev)), - (button6, "Media Previous", noWindow mediaPrev), - (button7, "Media Next", noWindow mediaNext) - ] + bind button14 $ do + noMod $ + doc "Additional Mouse Bindings" $ + subbind button14Binder - continuous $ - forM_ mediaButtons $ \(b, d, a) -> - bind b $ noMod $ doc d a + bind xK_v $ do + justMod $ + doc "Same as button14" $ + subbind button14Binder bind button13 $ noMod $ doc "General Window Management Extra Mouse Bindings" $ - subMouse $ do + subbind $ do bind button1 $ noMod $ doc @@ -1164,10 +1042,10 @@ mouseMap = runButtons $ do bind button13 $ noMod $ - subMouse $ do + subbind $ do bind button13 $ noMod $ - subMouse $ do + subbind $ do bind button13 $ noMod $ doc "Lock the screen" $ @@ -1182,7 +1060,7 @@ mouseMap = runButtons $ do bind button15 $ do noMod $ doc "General navigation extra mouse bindings" $ - subMouse $ do + subbind $ do bind button13 $ noMod $ doc "Goto the accompaning workspace to the current one." $ @@ -1220,7 +1098,7 @@ mouseMap = runButtons $ do bind button14 $ noMod $ - subMouse $ do + subbind $ do bind button1 $ noMod $ doc "Pin the selected windows" $ @@ -1273,19 +1151,42 @@ mouseMap = runButtons $ do ) in windows f >> escape --- Bindings specific to a window. These are set similarly to th ekeymap above, --- but uses a Query monad to tell which windows the keys will apply to. --- --- This is useful to create hotkeys in applications where hot keys are not --- configurable, or to remove keybindings that are irritating (looking at you, --- ctrl+w in Chrome!!). +myMouseMoveWindow = + D.mouseMoveWindowAndThen X.focus $ + mconcat + [ D.ifReleased button3 D.sinkOnRelease, + D.ifReleased' button2 $ \w _ -> X.killWindow w + ] + +myMouseResizeAction = + D.mouseResizeWindowAndThen X.focus $ + mconcat + [ D.ifReleased button1 D.sinkOnRelease + ] + +applyKeys :: XConfig l -> IO (XConfig l) +applyKeys c = + let conf' = withBindings bindings c + in return $ + windowBindings $ + conf' + { keys = + Map.insert + (modMask c .|. shiftMask, xK_q) + (spawnX "xmonad --recompile && xmonad --restart") + . keys conf' + } + windowSpecificBindings :: XConfig l -> WriterT (Map (KeyMask, KeySym) (X ())) Query () windowSpecificBindings config = do w <- lift ask + let altMask = mod1Mask let mods = permuteMods [shiftMask, controlMask, 0] - let configureIf b k = tell =<< lift (b --> return (keyBindingToKeymap (runKeys k) config)) + let configureIf b k = + let (keymap, _) = resolveBindings (runBinder config k) + in tell =<< lift (b --> return (keymap config)) emitKey = flip sendKey w configureIf (flip elem (browsers ++ spotify) <$> className) $ do @@ -1398,20 +1299,11 @@ windowBindings xconfig = map <- execWriterT $ windowSpecificBindings xconfig w <- ask - liftX $ logs Info "For Window: %s" (show w) + liftX $ logs Debug "For Window: %s" (show w) forM_ (Map.toList map) $ \(key, action) -> do - liftX $ logs Info " -- remap: %s" (show key) + liftX $ logs Debug " -- remap: %s" (show key) remapKey key action -applyKeys :: XConfig l -> IO (XConfig l) -applyKeys config = - return $ - windowBindings $ - config - { keys = keyBindingToKeymap keymap, - mouseBindings = buttonBindingsToButtonMap mouseMap - } - modifyWindowBorder :: Integer -> SpacingModifier modifyWindowBorder i = ModifyWindowBorder $ \(Border a b c d) -> Border (clip $ a + i) (clip $ b + i) (clip $ c + i) (clip $ d + i) diff --git a/src/Rahm/Desktop/Keys/Dsl.hs b/src/Rahm/Desktop/Keys/Dsl.hs deleted file mode 100644 index 1246d3b..0000000 --- a/src/Rahm/Desktop/Keys/Dsl.hs +++ /dev/null @@ -1,607 +0,0 @@ -{-# LANGUAGE FunctionalDependencies #-} - -module Rahm.Desktop.Keys.Dsl - ( doc, - (-|-), - ButtonBinding (..), - ButtonBindings, - Documented (..), - HasConfig, - KeyBinding (..), - KeyBindings, - altAltgrMod, - altHyperAltgrMod, - altHyperMod, - altMask, - altMod, - altSuperAltgrMod, - altSuperHyperAltgrMod, - altSuperHyperMod, - altSuperMod, - altgrMask, - altgrMod, - bind, - buttonDocumentation, - controlAltAltgrMod, - controlAltHyperAltgrMod, - controlAltHyperMod, - controlAltMod, - controlAltSuperAltgrMod, - controlAltSuperHyperAltgrMod, - controlAltSuperHyperMod, - controlAltSuperMod, - controlAltgrMod, - controlHyperAltgrMod, - controlHyperMod, - controlMod, - controlSuperAltgrMod, - controlSuperHyperAltgrMod, - controlSuperHyperMod, - controlSuperMod, - documentation, - getConfig, - hyperAltgrMod, - hyperMask, - hyperMod, - justMod, - maskMod, - noMod, - rawMask, - runButtons, - runKeys, - shiftAltAltgrMod, - shiftAltHyperAltgrMod, - shiftAltHyperMod, - shiftAltMod, - shiftAltSuperAltgrMod, - shiftAltSuperHyperAltgrMod, - shiftAltSuperHyperMod, - shiftAltSuperMod, - shiftAltgrMod, - shiftControlAltAltgrMod, - shiftControlAltHyperAltgrMod, - shiftControlAltHyperMod, - shiftControlAltMod, - shiftControlAltSuperAltgrMod, - shiftControlAltSuperHyperAltgrMod, - shiftControlAltSuperHyperMod, - shiftControlAltSuperMod, - shiftControlAltgrMod, - shiftControlHyperAltgrMod, - shiftControlHyperMod, - shiftControlMod, - shiftControlSuperAltgrMod, - shiftControlSuperHyperAltgrMod, - shiftControlSuperHyperMod, - shiftControlSuperMod, - shiftHyperAltgrMod, - shiftHyperMod, - shiftMod, - shiftSuperAltgrMod, - shiftSuperHyperAltgrMod, - shiftSuperHyperMod, - shiftSuperMod, - superAltgrMod, - superHyperAltgrMod, - superHyperMod, - superMask, - superMod, - ) -where - -import Control.Arrow (first, second) -import Control.Monad.State (State, execState, modify') -import Control.Monad.Writer - ( MonadWriter (tell), - execWriter, - forM_, - when, - ) -import Data.Bits ((.&.)) -import Data.List (intercalate, sortOn) -import Data.Map (Map) -import qualified Data.Map as Map - ( empty, - fromList, - fromListWith, - toList, - ) -import Text.Printf (printf) -import XMonad - ( Button, - ButtonMask, - KeyMask, - KeySym, - MonadState (get), - Window, - X, - XConfig (modMask), - controlMask, - keysymToString, - mod1Mask, - mod3Mask, - mod4Mask, - shiftMask, - (.|.), - ) - -data Documented t = Documented String t - -data KeyBinding - = Action (X ()) - | Submap KeyBindings - | Repeat KeyBindings - -type KeyBindings = Map (KeyMask, KeySym) (Documented KeyBinding) - -data ButtonBinding - = ButtonAction (Window -> X ()) - | ButtonSubmap ButtonBindings - | ButtonContinuous ButtonBindings - --- Window -> X () - -type ButtonBindings = Map (KeyMask, Button) (Documented ButtonBinding) - -{- Module that defines a DSL for binding keys. -} -newtype KeysM l a = KeysM (State (XConfig l, KeyBindings) a) - deriving (Functor, Applicative, Monad) - -newtype ButtonsM l a = ButtonsM (State (XConfig l, ButtonBindings) a) - deriving (Functor, Applicative, Monad) - -newtype BindingBuilder b a = BindingBuilder (State (KeyMask, [(KeyMask, b)]) a) - deriving (Functor, Applicative, Monad) - -class HasConfig m where - getConfig :: m l (XConfig l) - -class Bindable k where - type BindableValue k :: * - type BindableMonad k :: (* -> *) -> * -> * - - bind :: k -> BindingBuilder (BindableValue k) a -> BindableMonad k l () - --- section :: String -> BindableMonad k l () -> BindableMonad k l () - -class Binding k b where - toB :: k -> b - - rawMask :: KeyMask -> k -> BindingBuilder b () - rawMask m x = BindingBuilder $ modify' (second ((m, toB x) :)) - -instance Binding (Window -> X ()) (Documented ButtonBinding) where - toB = Documented "" . ButtonAction - -instance Binding (X ()) (Documented KeyBinding) where - toB = Documented "" . Action - -instance Binding KeyBindings (Documented KeyBinding) where - toB = Documented "" . Submap - -instance Binding a (Documented a) where - toB = Documented "" - -instance Binding a a where - toB = id - --- Relationships to witness which types can be used with the "doc" function, --- which is used to document actions in a safe and programmable way.. -class Relation k b | k -> b - -instance Relation (X ()) KeyBinding - -instance Relation KeyBinding KeyBinding - -instance Relation ButtonBinding ButtonBinding - -instance Relation (Window -> X ()) ButtonBinding - -doc :: (Relation k b, Binding k (Documented b)) => String -> k -> Documented b -doc str k = let (Documented _ t) = toB k in Documented str t - -runKeys :: KeysM l a -> XConfig l -> KeyBindings -runKeys (KeysM stateM) config = - snd $ execState stateM (config, Map.empty) - -runButtons :: ButtonsM l a -> XConfig l -> ButtonBindings -runButtons (ButtonsM stateM) config = - snd $ execState stateM (config, Map.empty) - -instance HasConfig KeysM where - getConfig = fst <$> KeysM get - -instance HasConfig ButtonsM where - getConfig = fst <$> ButtonsM get - -{- Generally it is assumed that the mod key shoud be pressed, but not always. -} -noMod :: (Binding k b) => k -> BindingBuilder b () -noMod = rawMask 0 - -maskMod :: (Binding k b) => KeyMask -> k -> BindingBuilder b () -maskMod mask action = do - modMask <- fst <$> BindingBuilder get - rawMask (modMask .|. mask) action - -altMask :: KeyMask -altMask = mod1Mask - -hyperMask :: KeyMask -hyperMask = mod3Mask - -altgrMask :: KeyMask -altgrMask = 0x80 - -superMask :: KeyMask -superMask = mod4Mask - -justMod :: (Binding k b) => k -> BindingBuilder b () -justMod = maskMod 0 - -instance Bindable KeySym where - type BindableValue KeySym = Documented KeyBinding - type BindableMonad KeySym = KeysM - - -- bind :: KeySym -> BindingBuilder (X x) a -> KeysM l () - bind key (BindingBuilder stM) = do - m <- modMask <$> getConfig - let (_, values) = execState stM (m, []) - - KeysM $ - modify' $ - second $ - flip (<>) (Map.fromList (map (\(m, v) -> ((m, key), v)) values)) - -instance Bindable Button where - type BindableValue Button = Documented ButtonBinding - type BindableMonad Button = ButtonsM - - -- bind :: KeySym -> BindingBuilder (Window -> X ()) a -> ButtonsM l () - bind button (BindingBuilder stM) = do - m <- modMask <$> getConfig - let (_, values) = execState stM (m, []) - - ButtonsM $ - modify' $ - second $ - flip (<>) (Map.fromList (map (\(m, v) -> ((m, button), v)) values)) - -shiftControlAltSuperHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltSuperHyperAltgrMod = - maskMod (shiftMask .|. controlMask .|. altMask .|. superMask .|. hyperMask .|. altgrMask) - -shiftControlAltSuperHyperMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltSuperHyperMod = - maskMod (shiftMask .|. controlMask .|. altMask .|. superMask .|. hyperMask) - -shiftControlAltSuperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltSuperAltgrMod = - maskMod (shiftMask .|. controlMask .|. altMask .|. superMask .|. altgrMask) - -shiftControlAltSuperMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltSuperMod = - maskMod (shiftMask .|. controlMask .|. altMask .|. superMask) - -shiftControlAltHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltHyperAltgrMod = - maskMod (shiftMask .|. controlMask .|. altMask .|. hyperMask .|. altgrMask) - -shiftControlAltHyperMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltHyperMod = - maskMod (shiftMask .|. controlMask .|. altMask .|. hyperMask) - -shiftControlAltAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltAltgrMod = - maskMod (shiftMask .|. controlMask .|. altMask .|. altgrMask) - -shiftControlAltMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltMod = - maskMod (shiftMask .|. controlMask .|. altMask) - -shiftControlSuperHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlSuperHyperAltgrMod = - maskMod (shiftMask .|. controlMask .|. superMask .|. hyperMask .|. altgrMask) - -shiftControlSuperHyperMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlSuperHyperMod = - maskMod (shiftMask .|. controlMask .|. superMask .|. hyperMask) - -shiftControlSuperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlSuperAltgrMod = - maskMod (shiftMask .|. controlMask .|. superMask .|. altgrMask) - -shiftControlSuperMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlSuperMod = - maskMod (shiftMask .|. controlMask .|. superMask) - -shiftControlHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlHyperAltgrMod = - maskMod (shiftMask .|. controlMask .|. hyperMask .|. altgrMask) - -shiftControlHyperMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlHyperMod = - maskMod (shiftMask .|. controlMask .|. hyperMask) - -shiftControlAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlAltgrMod = - maskMod (shiftMask .|. controlMask .|. altgrMask) - -shiftControlMod :: (Binding k b) => k -> BindingBuilder b () -shiftControlMod = - maskMod (shiftMask .|. controlMask) - -shiftAltSuperHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltSuperHyperAltgrMod = - maskMod (shiftMask .|. altMask .|. superMask .|. hyperMask .|. altgrMask) - -shiftAltSuperHyperMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltSuperHyperMod = - maskMod (shiftMask .|. altMask .|. superMask .|. hyperMask) - -shiftAltSuperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltSuperAltgrMod = - maskMod (shiftMask .|. altMask .|. superMask .|. altgrMask) - -shiftAltSuperMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltSuperMod = - maskMod (shiftMask .|. altMask .|. superMask) - -shiftAltHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltHyperAltgrMod = - maskMod (shiftMask .|. altMask .|. hyperMask .|. altgrMask) - -shiftAltHyperMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltHyperMod = - maskMod (shiftMask .|. altMask .|. hyperMask) - -shiftAltAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltAltgrMod = - maskMod (shiftMask .|. altMask .|. altgrMask) - -shiftAltMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltMod = - maskMod (shiftMask .|. altMask) - -shiftSuperHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftSuperHyperAltgrMod = - maskMod (shiftMask .|. superMask .|. hyperMask .|. altgrMask) - -shiftSuperHyperMod :: (Binding k b) => k -> BindingBuilder b () -shiftSuperHyperMod = - maskMod (shiftMask .|. superMask .|. hyperMask) - -shiftSuperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftSuperAltgrMod = - maskMod (shiftMask .|. superMask .|. altgrMask) - -shiftSuperMod :: (Binding k b) => k -> BindingBuilder b () -shiftSuperMod = - maskMod (shiftMask .|. superMask) - -shiftHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftHyperAltgrMod = - maskMod (shiftMask .|. hyperMask .|. altgrMask) - -shiftHyperMod :: (Binding k b) => k -> BindingBuilder b () -shiftHyperMod = - maskMod (shiftMask .|. hyperMask) - -shiftAltgrMod :: (Binding k b) => k -> BindingBuilder b () -shiftAltgrMod = - maskMod (shiftMask .|. altgrMask) - -shiftMod :: (Binding k b) => k -> BindingBuilder b () -shiftMod = maskMod shiftMask - -controlAltSuperHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -controlAltSuperHyperAltgrMod = - maskMod (controlMask .|. altMask .|. superMask .|. hyperMask .|. altgrMask) - -controlAltSuperHyperMod :: (Binding k b) => k -> BindingBuilder b () -controlAltSuperHyperMod = - maskMod (controlMask .|. altMask .|. superMask .|. hyperMask) - -controlAltSuperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -controlAltSuperAltgrMod = - maskMod (controlMask .|. altMask .|. superMask .|. altgrMask) - -controlAltSuperMod :: (Binding k b) => k -> BindingBuilder b () -controlAltSuperMod = - maskMod (controlMask .|. altMask .|. superMask) - -controlAltHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -controlAltHyperAltgrMod = - maskMod (controlMask .|. altMask .|. hyperMask .|. altgrMask) - -controlAltHyperMod :: (Binding k b) => k -> BindingBuilder b () -controlAltHyperMod = - maskMod (controlMask .|. altMask .|. hyperMask) - -controlAltAltgrMod :: (Binding k b) => k -> BindingBuilder b () -controlAltAltgrMod = - maskMod (controlMask .|. altMask .|. altgrMask) - -controlAltMod :: (Binding k b) => k -> BindingBuilder b () -controlAltMod = - maskMod (controlMask .|. altMask) - -controlSuperHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -controlSuperHyperAltgrMod = - maskMod (controlMask .|. superMask .|. hyperMask .|. altgrMask) - -controlSuperHyperMod :: (Binding k b) => k -> BindingBuilder b () -controlSuperHyperMod = - maskMod (controlMask .|. superMask .|. hyperMask) - -controlSuperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -controlSuperAltgrMod = - maskMod (controlMask .|. superMask .|. altgrMask) - -controlSuperMod :: (Binding k b) => k -> BindingBuilder b () -controlSuperMod = - maskMod (controlMask .|. superMask) - -controlHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -controlHyperAltgrMod = - maskMod (controlMask .|. hyperMask .|. altgrMask) - -controlHyperMod :: (Binding k b) => k -> BindingBuilder b () -controlHyperMod = - maskMod (controlMask .|. hyperMask) - -controlAltgrMod :: (Binding k b) => k -> BindingBuilder b () -controlAltgrMod = - maskMod (controlMask .|. altgrMask) - -controlMod :: (Binding k b) => k -> BindingBuilder b () -controlMod = maskMod controlMask - -altSuperHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -altSuperHyperAltgrMod = - maskMod (altMask .|. superMask .|. hyperMask .|. altgrMask) - -altSuperHyperMod :: (Binding k b) => k -> BindingBuilder b () -altSuperHyperMod = - maskMod (altMask .|. superMask .|. hyperMask) - -altSuperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -altSuperAltgrMod = - maskMod (altMask .|. superMask .|. altgrMask) - -altSuperMod :: (Binding k b) => k -> BindingBuilder b () -altSuperMod = - maskMod (altMask .|. superMask) - -altHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -altHyperAltgrMod = - maskMod (altMask .|. hyperMask .|. altgrMask) - -altHyperMod :: (Binding k b) => k -> BindingBuilder b () -altHyperMod = - maskMod (altMask .|. hyperMask) - -altAltgrMod :: (Binding k b) => k -> BindingBuilder b () -altAltgrMod = - maskMod (altMask .|. altgrMask) - -altMod :: (Binding k b) => k -> BindingBuilder b () -altMod = maskMod altMask - -superHyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -superHyperAltgrMod = - maskMod (superMask .|. hyperMask .|. altgrMask) - -superHyperMod :: (Binding k b) => k -> BindingBuilder b () -superHyperMod = - maskMod (superMask .|. hyperMask) - -superAltgrMod :: (Binding k b) => k -> BindingBuilder b () -superAltgrMod = - maskMod (superMask .|. altgrMask) - -superMod :: (Binding k b) => k -> BindingBuilder b () -superMod = maskMod superMask - -hyperAltgrMod :: (Binding k b) => k -> BindingBuilder b () -hyperAltgrMod = - maskMod (hyperMask .|. altgrMask) - -hyperMod :: (Binding k b) => k -> BindingBuilder b () -hyperMod = maskMod hyperMask - -altgrMod :: (Binding k b) => k -> BindingBuilder b () -altgrMod = maskMod altgrMask - -{- Can combine two or more of the functions above to apply the same action to - - multiple masks. -} -(-|-) :: - (Binding k b) => - (k -> BindingBuilder b ()) -> - (k -> BindingBuilder b ()) -> - k -> - BindingBuilder b () -(-|-) fn1 fn2 f = fn1 f >> fn2 f - -buttonDocumentation :: ButtonBindings -> String -buttonDocumentation = execWriter . document' "" - where - document' pref keybindings = - forM_ (sortOn (map (\(a, b) -> (b, a)) . snd . snd) $ Map.toList (keyBindingsToList keybindings)) $ \(doc, (thing, keys)) -> do - when (not (null doc) || hasSubmap thing) $ - tell $ printf "%s%s: %s\n" pref (intercalate " or " $ map prettyShow keys) doc - case thing of - ButtonAction _ -> return () - ButtonSubmap submap -> document' (pref ++ " ") submap - ButtonContinuous submap -> do - tell pref - tell " (repeatable):\n" - document' (pref ++ " ") submap - - keyBindingsToList :: ButtonBindings -> Map String (ButtonBinding, [(ButtonMask, Button)]) - keyBindingsToList b = - (\list -> ((\(_, Documented _ t) -> t) (head list), map fst list)) - <$> group (\(_, Documented doc _) -> doc) (sortOn (snd . fst) $ Map.toList b) - - prettyShow :: (ButtonMask, Button) -> String - prettyShow (mask, button) = printf "%s%s" (showMask mask) (buttonToString button) - - buttonToString = \case - 1 -> "Left Click" - 2 -> "Middle Click" - 3 -> "Right Click" - 4 -> "Wheel Up" - 5 -> "Wheel Down" - 6 -> "Wheel Left" - 7 -> "Wheel Right" - 8 -> "Browser Back" - 9 -> "Browser Forward" - 13 -> "Thumb Target" - 14 -> "Index Forward" - 15 -> "Index Back" - b -> "Button " ++ show b - - hasSubmap b = case b of - ButtonAction _ -> False - _ -> True - -documentation :: KeyBindings -> String -documentation = execWriter . document' "" - where - document' pref keybindings = - forM_ (sortOn (map (\(a, b) -> (b, a)) . snd . snd) $ Map.toList (keyBindingsToList keybindings)) $ \(doc, (thing, keys)) -> do - when (not (null doc) || hasSubmap thing) $ - tell $ printf "%s%s: %s\n" pref (intercalate " or " $ map prettyShow keys) doc - case thing of - Action _ -> return () - Submap submap -> document' (pref ++ " ") submap - Repeat submap -> do - tell pref - tell " (repeatable):\n" - document' (pref ++ " ") submap - - keyBindingsToList :: KeyBindings -> Map String (KeyBinding, [(KeyMask, KeySym)]) - keyBindingsToList b = - (\list -> ((\(_, Documented _ t) -> t) (head list), map fst list)) - <$> group (\(_, Documented doc _) -> doc) (sortOn (snd . fst) $ Map.toList b) - - prettyShow :: (KeyMask, KeySym) -> String - prettyShow (mask, key) = printf "%s%s" (showMask mask) (keysymToString key) - - hasSubmap b = case b of - Action _ -> False - _ -> True - -showMask :: KeyMask -> String -showMask mask = - let masks = - [ (shiftMask, "S"), - (altMask, "A"), - (mod3Mask, "H"), - (mod4Mask, "M"), - (altgrMask, "AGr"), - (controlMask, "C") - ] - in concatMap ((++ "-") . snd) $ filter ((/= 0) . (.&. mask) . fst) masks - -group :: (Ord b) => (a -> b) -> [a] -> Map b [a] -group fn = Map.fromListWith (++) . map (first fn . (\a -> (a, [a]))) diff --git a/src/Rahm/Desktop/Keys/Dsl2.hs b/src/Rahm/Desktop/Keys/Dsl2.hs new file mode 100644 index 0000000..3debc48 --- /dev/null +++ b/src/Rahm/Desktop/Keys/Dsl2.hs @@ -0,0 +1,254 @@ +module Rahm.Desktop.Keys.Dsl2 where + +import Control.Monad.Fix (fix) +import Control.Monad.RWS (MonadTrans (lift), MonadWriter, forM_) +import Control.Monad.Reader (Reader, ask, runReader) +import Control.Monad.State (MonadTrans, StateT (StateT)) +import Control.Monad.Trans.Maybe (MaybeT (..)) +import Control.Monad.Trans.Writer (Writer, WriterT, execWriter, execWriterT) +import Control.Monad.Writer.Class (tell) +import Data.Functor.Identity (Identity) +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Maybe (fromMaybe) +import Rahm.Desktop.Common (pointerWindow, runMaybeT_) +import Rahm.Desktop.Logger (LogLevel (Debug), logs) +import Rahm.Desktop.Submap (ButtonOrKeyEvent (ButtonPress, KeyPress), getStringForKey, nextButtonOrKeyEvent) +import Rahm.Desktop.XMobarLog (spawnXMobar) +import Rahm.Desktop.XMobarLog.PendingBuffer (pushAddPendingBuffer, pushPendingBuffer) +import XMonad + +data Documented t = Documented + { docString :: String, + undocument :: t + } + +type family Action t where + Action KeySym = X () + Action Button = Window -> X () + +data XConfigH where + XConfigH :: forall l. XConfig l -> XConfigH + +data Binding t + = Action (Action t) + | Submap (forall l. XConfig l -> BindingsMap) + | Repeat (Binding t) (forall l. XConfig l -> BindingsMap) + | NoBinding + +data BindingsMap = BindingsMap + { key_bindings :: Map (KeyMask, KeySym) (Documented (Binding KeySym)), + button_bindings :: Map (KeyMask, Button) (Documented (Binding Button)), + no_match_catch_key :: (KeyMask, KeySym, String) -> X (), + no_match_catch_button :: (KeyMask, Button) -> Window -> X () + } + +newtype MaskBinder k a = MaskBinder + { unMaskBinder :: WriterT (Map KeyMask (Documented (Binding k))) (Reader XConfigH) a + } + deriving + ( Functor, + Applicative, + Monad, + MonadWriter (Map KeyMask (Documented (Binding k))), + MonadReader XConfigH + ) + +instance Semigroup BindingsMap where + (BindingsMap mk1 mb1 _ _) <> (BindingsMap mk2 mb2 fk fb) = + BindingsMap (mk1 <> mk2) (mb1 <> mb2) fk fb + +instance Monoid BindingsMap where + mempty = BindingsMap mempty mempty (\_ -> return ()) (\_ _ -> return ()) + +newtype Binder a = Binder (WriterT BindingsMap (Reader XConfigH) a) + deriving (Functor, Applicative, Monad, MonadWriter BindingsMap, MonadReader XConfigH) + +bindOtherKeys :: ((KeyMask, KeySym, String) -> X ()) -> Binder () +bindOtherKeys fn = Binder $ tell (mempty {no_match_catch_key = fn}) + +bindOtherButtons :: ((KeyMask, Button) -> Window -> X ()) -> Binder () +bindOtherButtons fn = Binder $ tell (mempty {no_match_catch_button = fn}) + +class Documentable a b where + toDocumented :: a -> b + +instance Documentable (Documented a) (Documented a) where + toDocumented = id + +instance Documentable a (Documented a) where + toDocumented = Documented "" + +class BindingType a where + type BoundTo a :: * + + toBinding :: a -> Documented (Binding (BoundTo a)) + +instance BindingType (Binding t) where + type BoundTo (Binding t) = t + toBinding = Documented "" + +instance BindingType (X ()) where + type BoundTo (X ()) = KeySym + toBinding = Documented "" . Action + +instance BindingType (Window -> X ()) where + type BoundTo (Window -> X ()) = Button + toBinding = Documented "" . Action + +instance (BindingType a) => BindingType (Documented a) where + type BoundTo (Documented a) = BoundTo a + toBinding (Documented s (toBinding -> (Documented _ a))) = Documented s a + +class Bind k where + doBinding :: k -> Map KeyMask (Documented (Binding k)) -> BindingsMap + rawMaskRaw :: KeyMask -> Documented (Binding k) -> MaskBinder k () + +instance Bind Button where + doBinding but mp = mempty {button_bindings = Map.mapKeys (,but) mp} + rawMaskRaw mask act = tell (Map.singleton mask act) + +instance Bind KeySym where + doBinding key mp = mempty {key_bindings = Map.mapKeys (,key) mp} + rawMaskRaw mask act = tell (Map.singleton mask act) + +rawMask :: (Bind (BoundTo a), BindingType a) => KeyMask -> a -> MaskBinder (BoundTo a) () +rawMask mask act = rawMaskRaw mask (toBinding act) + +withMod :: (Bind (BoundTo a), BindingType a) => KeyMask -> a -> MaskBinder (BoundTo a) () +withMod m act = do + (XConfigH (modMask -> mm)) <- ask + rawMask (mm .|. m) act + +noMod, justMod, shiftMod, controlMod, altMod :: (Bind (BoundTo a), BindingType a) => a -> MaskBinder (BoundTo a) () +justMod = withMod 0 +noMod = rawMask 0 +shiftMod = withMod shiftMask +controlMod = withMod controlMask +altMod = withMod mod1Mask + +(-|-) :: + (Bind (BoundTo a), BindingType a) => + (a -> MaskBinder (BoundTo a) ()) -> + (a -> MaskBinder (BoundTo a) ()) -> + a -> + MaskBinder (BoundTo a) () +m1 -|- m2 = \act -> m1 act >> m2 act + +bind :: (Bind k) => k -> MaskBinder k () -> Binder () +bind k h = + tell . doBinding k . runReader (execWriterT $ unMaskBinder h) =<< ask + +bindL :: (Bind k) => [k] -> MaskBinder k () -> Binder () +bindL ks h = mapM_ (`bind` h) ks + +doc :: String -> a -> Documented a +doc = Documented + +noWindow :: X () -> Window -> X () +noWindow fn _ = fn + +resolveBindings :: + BindingsMap -> + ( XConfig l -> Map (KeyMask, KeySym) (X ()), + XConfig l -> Map (ButtonMask, Button) (Window -> X ()) + ) +resolveBindings (BindingsMap keyBindings buttonBindings _ _) = + ( \c -> Map.mapWithKey (\k -> pushK k (bindingToX c) . undocument) keyBindings, + \c -> Map.mapWithKey (\k -> pushB k (bindingToWinX c) . undocument) buttonBindings + ) + where + pushB :: (ButtonMask, Button) -> (Binding Button -> Window -> X ()) -> Binding Button -> Window -> X () + pushB (_, b) fn binding win = + if isRepeatOrSubmap binding + then pushPendingBuffer ("b" ++ show b ++ " ") $ fn binding win + else fn binding win + + pushK (m, k) fn binding = + if isRepeatOrSubmap binding + then do + let s = getStringForKey (m, k) + pushPendingBuffer (s ++ " ") $ fn binding + else fn binding + + bindingToX :: forall l. XConfig l -> Binding KeySym -> X () + bindingToX conf = \case + NoBinding -> return () + Action a -> a + Submap sm -> doSubmap conf (sm conf) (return ()) + Repeat a sm -> bindingToX conf a >> fix (doSubmap conf (sm conf)) + + bindingToWinX :: forall l. XConfig l -> Binding Button -> Window -> X () + bindingToWinX conf binding win = case binding of + NoBinding -> return () + Action fn -> fn win + Submap sm -> doSubmap conf (sm conf) (return ()) + Repeat a sm -> bindingToWinX conf a win >> fix (doSubmap conf (sm conf)) + + doSubmap :: forall l. XConfig l -> BindingsMap -> X () -> X () + doSubmap conf (BindingsMap kbind bbind _ _) after = runMaybeT_ $ do + nextPressEvent $ + \case + (ButtonPress m b) -> do + binding <- hoist $ Map.lookup (m, b) bbind + lift $ do + win <- pointerWindow + bindingToWinX conf (undocument binding) win + after + (KeyPress m k s) -> do + binding <- hoist $ Map.lookup (m, k) kbind + lift $ do + bindingToX conf (undocument binding) + after + + isRepeatOrSubmap = \case + Repeat {} -> True + Submap {} -> True + _ -> False + + nextPressEvent fn = do + ev <- nextButtonOrKeyEvent + let str = case ev of + ButtonPress m b -> "b" ++ show b + KeyPress _ _ s -> s + lift $ + pushAddPendingBuffer (str ++ " ") $ + runMaybeT_ $ + fn ev + + hoist = MaybeT . return + +subbind :: Binder () -> Binding t +subbind (Binder b) = + Submap $ \config -> + runReader (execWriterT b) (XConfigH config) + +repeatable :: Binder () -> Binding t +repeatable (Binder b) = + Repeat NoBinding $ \config -> + runReader (execWriterT b) (XConfigH config) + +-- Similar to repeatable, but all the keys in the binder start the loop. +continuous :: Binder () -> Binder () +continuous (Binder b) = do + conf <- ask + let bm@(BindingsMap keyBinds mouseBinds _ _) = + runReader (execWriterT b) conf + + forM_ (Map.toList keyBinds) $ \((m, k), Documented _ b) -> + bind k $ rawMask m $ Repeat b $ const bm + + forM_ (Map.toList mouseBinds) $ \((m, k), Documented _ b) -> + bind k $ rawMask m $ Repeat b $ const bm + +runBinder :: XConfig l -> Binder a -> BindingsMap +runBinder conf (Binder binder) = runReader (execWriterT binder) (XConfigH conf) + +withBindings :: Binder a -> XConfig l -> XConfig l +withBindings b config = + let (keyBinds, buttonBinds) = + resolveBindings $ runBinder config b + in config + { keys = keyBinds, + mouseBindings = buttonBinds + } diff --git a/src/Rahm/Desktop/Submap.hs b/src/Rahm/Desktop/Submap.hs index b705a24..9c20381 100644 --- a/src/Rahm/Desktop/Submap.hs +++ b/src/Rahm/Desktop/Submap.hs @@ -8,19 +8,28 @@ module Rahm.Desktop.Submap submap, submapDefault, submapDefaultWithKey, + ButtonOrKeyEvent (..), + nextButtonOrKeyEvent, + getStringForKey, escape, ) where import Control.Concurrent (threadDelay) +import Control.Exception (SomeException (SomeException), catch) +import Control.Monad (when) import Control.Monad.Fix (fix) import Control.Monad.Trans (MonadTrans (lift)) import Control.Monad.Trans.Maybe (MaybeT (MaybeT)) +import Data.Aeson (Result (Error)) +import Data.Bits ((.&.)) +import Data.Char (toUpper) import Data.Map (Map) import qualified Data.Map as Map (findWithDefault, lookup) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Word (Word64) import Rahm.Desktop.Common (pointerWindow, runMaybeT_) +import Rahm.Desktop.Logger (logs) import XMonad ( Button, ButtonMask, @@ -37,6 +46,7 @@ import XMonad XEventPtr, allocaXEvent, asKeyEvent, + asks, buttonPressMask, checkMaskEvent, cleanMask, @@ -49,14 +59,19 @@ import XMonad isModifierKey, keyPressMask, keycodeToKeysym, + keysymToKeycode, + keysymToString, lookupString, maskEvent, pointerMotionMask, + setKeyEvent, + shiftMask, ungrabKeyboard, ungrabPointer, (.|.), ) import qualified XMonad.Util.ExtensibleState as XS +import XMonad.Util.Loggers (logSp) newtype Escape = Escape Bool @@ -98,6 +113,51 @@ getMaskEventWithTimeout timeout d mask fn = do then return True else threadDelay 1000 >> getMaskEventWithTimeout' ptr timeout +data ButtonOrKeyEvent + = ButtonPress + { event_mask :: KeyMask, + event_button :: Button + } + | KeyPress + { event_mask :: KeyMask, + event_keysym :: KeySym, + event_string :: String + } + +nextButtonOrKeyEvent :: MaybeT X ButtonOrKeyEvent +nextButtonOrKeyEvent = do + b <- lift getEscape + when b (MaybeT (return Nothing)) + + XConf {theRoot = root, display = d} <- ask + io $ do + grabKeyboard d root False grabModeAsync grabModeAsync currentTime + grabPointer d root False buttonPressMask grabModeAsync grabModeAsync 0 0 currentTime + + ret <- MaybeT $ + io $ + fix $ \tryAgain -> do + ret <- + getMaskEventWithTimeout 5000 d (keyPressMask .|. buttonPressMask) $ \p -> do + ev <- getEvent p + case ev of + ButtonEvent {ev_button = b, ev_state = m} -> + return $ ButtonPress m b + KeyEvent {ev_keycode = code, ev_state = m} -> do + keysym <- keycodeToKeysym d code 0 + (_, str) <- lookupString (asKeyEvent p) + return $ KeyPress m keysym str + case ret of + Just (KeyPress m sym str) | isModifierKey sym -> tryAgain + x -> return x + + io $ do + ungrabKeyboard d currentTime + ungrabPointer d currentTime + + m' <- lift $ cleanMask (event_mask ret) + return ret {event_mask = m'} + {- - Like submap fram XMonad.Actions.Submap, but sends the string from - XLookupString to the function along side the keysym. @@ -134,6 +194,24 @@ mapNextStringWithKeysym fn = do m <- lift $ cleanMask m' fn m keysym str +-- getStringForKey :: (KeyMask, KeySym) -> X String +-- getStringForKey (m, sym) = do +-- d <- asks display +-- io $ +-- allocaXEvent +-- ( \xev -> do +-- kc <- keysymToKeycode d sym +-- setKeyEvent xev 0 0 0 m kc False +-- (_, str) <- lookupString (asKeyEvent xev) +-- return str +-- ) +-- `catch` ( \e -> do +-- putStrLn $ "Error in getStringForKey: " ++ show (e :: SomeException) +-- return "?" +-- ) +getStringForKey :: (KeyMask, KeySym) -> String +getStringForKey (m, sym) = (if (m .&. shiftMask) /= 0 then map toUpper else id) (keysymToString sym) + {- Like submap, but on the character typed rather than the kysym. -} mapNextString :: (KeyMask -> String -> MaybeT X a) -> MaybeT X a mapNextString fn = mapNextStringWithKeysym (\m _ s -> fn m s) |