From 925ddcba65a81b4bd88fbdc9bd6955f1ef114811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20J=C3=A4ger?= Date: Fri, 3 Jan 2025 15:22:16 +0100 Subject: [PATCH] X.H.Screencorners: Add per monitor hot corners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pascal Jäger --- CHANGES.md | 11 ++++ XMonad/Hooks/ScreenCorners.hs | 109 ++++++++++++++++++++++++---------- 2 files changed, 89 insertions(+), 31 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 16c903bd1..0672404e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -31,6 +31,17 @@ `DestroyWindowEvent` messages instead, which are broadcast to layouts since xmonad v0.17.0. + * `XMonad.Hooks.ScreenCorners` + + - Screencorners were only possible at the edge of the screen canvas not + the edges of monitors. This worked on single monitor setups because there + the screen canvas edges and the monitor edges fall into the some place. + This is not the case in multi monitor setups and therefore how corners/ + edges were only possible on the outer edges/ corners of the outmost + monitors if those monitors were not shifted in the y axis. + With `addMonitorCorner` and `addMonitorCorners` it is now possible to add + hot cornern for each corner of a monitor. + ## 0.18.1 (August 20, 2024) ### Breaking Changes diff --git a/XMonad/Hooks/ScreenCorners.hs b/XMonad/Hooks/ScreenCorners.hs index a1d3323fb..5e1eed209 100644 --- a/XMonad/Hooks/ScreenCorners.hs +++ b/XMonad/Hooks/ScreenCorners.hs @@ -5,7 +5,8 @@ -- | -- Module : XMonad.Hooks.ScreenCorners -- Description : Run X () actions by touching the edge of your screen with your mouse. --- Copyright : (c) 2009-2025 Nils Schweinsberg, 2015 Evgeny Kurnevsky, 2024 Yuanle Song +-- Copyright : (c) 2009-2025 Nils Schweinsberg, 2015 Evgeny Kurnevsky, 2024 Yuanle Song, +-- 2025 Pascal Jaeger -- License : BSD3-style (see LICENSE) -- -- Maintainer : Nils Schweinsberg @@ -21,6 +22,8 @@ module XMonad.Hooks.ScreenCorners ScreenCorner (..), addScreenCorner, addScreenCorners, + addMonitorCorner, + addMonitorCorners, -- * Event hook screenCornerEventHook, @@ -35,6 +38,7 @@ import XMonad import XMonad.Layout.LayoutModifier import XMonad.Prelude import qualified XMonad.Util.ExtensibleState as XS +import Graphics.X11.Xinerama (xineramaQueryScreens, XineramaScreenInfo(..)) data ScreenCorner = SCUpperLeft @@ -51,31 +55,46 @@ data ScreenCorner -- ExtensibleState modifications -------------------------------------------------------------------------------- -newtype ScreenCornerState = ScreenCornerState (M.Map Window (ScreenCorner, X ())) +newtype ScreenCornerState = ScreenCornerState (M.Map (ScreenCorner, Int) (Window, X ())) instance ExtensionClass ScreenCornerState where initialValue = ScreenCornerState M.empty -- | Add one single @X ()@ action to a screen corner +{-# DEPRECATED addScreenCorner "addScreenCorner works only in a single monitor setup. Use addMonitorCorner instead." #-} addScreenCorner :: ScreenCorner -> X () -> X () addScreenCorner corner xF = do ScreenCornerState m <- XS.get - (win, xFunc) <- case find (\(_, (sc, _)) -> sc == corner) (M.toList m) of - Just (w, (_, xF')) -> return (w, xF' >> xF) -- chain X actions + let key = (corner, -1) -- Use -1 to indicate a non-monitor-specific corner + (win, xFunc) <- case M.lookup key m of + Just (w, xF') -> return (w, xF' >> xF) -- Chain X actions Nothing -> (,xF) <$> createWindowAt corner - - XS.modify $ \(ScreenCornerState m') -> ScreenCornerState $ M.insert win (corner, xFunc) m' + XS.modify $ \(ScreenCornerState m') -> ScreenCornerState $ M.insert key (win, xFunc) m' -- | Add a list of @(ScreenCorner, X ())@ tuples +{-# DEPRECATED addScreenCorners "addScreenCorners works only in a single monitor setup. Use addMonitorCorners instead." #-} addScreenCorners :: [(ScreenCorner, X ())] -> X () addScreenCorners = mapM_ (uncurry addScreenCorner) +-- | Add one single @X ()@ action to a screen corner on a specific monitor +addMonitorCorner :: ScreenCorner -> Int -> Dimension -> X () -> X () +addMonitorCorner corner monitorNumber hotZoneSize xF = do + ScreenCornerState m <- XS.get + let key = (corner, monitorNumber) + (win, xFunc) <- case M.lookup key m of + Just (w, xF') -> return (w, xF' >> xF) -- Chain X actions + Nothing -> (,xF) <$> createWindowAtMonitor corner monitorNumber hotZoneSize + XS.modify $ \(ScreenCornerState m') -> ScreenCornerState $ M.insert key (win, xFunc) m' + +-- | Add a list of @(ScreenCorner, Int, Dimension, X ())@ tuples +addMonitorCorners :: [(ScreenCorner, Int, Dimension, X ())] -> X () +addMonitorCorners = mapM_ (\(corner, monitor, hotZoneSize, xF) -> addMonitorCorner corner monitor hotZoneSize xF) + -------------------------------------------------------------------------------- -- Xlib functions -------------------------------------------------------------------------------- --- "Translate" a ScreenCorner to real (x,y) Positions with proper width and --- height. +-- "Translate" a ScreenCorner to real (x,y) Positions with proper width and height. createWindowAt :: ScreenCorner -> X Window createWindowAt SCUpperLeft = createWindowAt' 0 0 1 1 createWindowAt SCUpperRight = withDisplay $ \dpy -> @@ -108,6 +127,30 @@ createWindowAt SCRight = withDisplay $ \dpy -> threshold = 150 in createWindowAt' (fi w) threshold 1 (fi $ fi h - threshold * 2) +-- "Translate" a ScreenCorner to real (x,y) Positions on a specific monitor +createWindowAtMonitor :: ScreenCorner -> Int -> Dimension -> X Window +createWindowAtMonitor corner monitorNumber hotZoneSize = withDisplay $ \dpy -> do + screens <- io $ xineramaQueryScreens dpy + case screens of + Just scrs | monitorNumber < length scrs -> do + let XineramaScreenInfo _ x y w h = scrs !! monitorNumber + hotZoneSize' = fromIntegral hotZoneSize :: Int + x' = fromIntegral x :: Int + y' = fromIntegral y :: Int + w' = fromIntegral w :: Int + h' = fromIntegral h :: Int + (xPos, yPos, width, height) = case corner of + SCUpperLeft -> (x', y', hotZoneSize', hotZoneSize') + SCUpperRight -> (x' + w' - hotZoneSize', y', hotZoneSize', hotZoneSize') + SCLowerLeft -> (x', y' + h' - hotZoneSize', hotZoneSize', hotZoneSize') + SCLowerRight -> (x' + w' - hotZoneSize', y' + h' - hotZoneSize', hotZoneSize', hotZoneSize') + SCTop -> (x' + 150, y', w' - 300, hotZoneSize') + SCBottom -> (x' + 150, y' + h' - hotZoneSize', w' - 300, hotZoneSize') + SCLeft -> (x', y' + 150, hotZoneSize', h' - 300) + SCRight -> (x' + w' - hotZoneSize', y' + 150, hotZoneSize', h' - 300) + createWindowAt' (fi xPos) (fi yPos) (fi width) (fi height) + _ -> error $ "Invalid monitor number or no screens available for monitorNumber=" ++ show monitorNumber + -- Create a new X window at a (x,y) Position, with given width and height. createWindowAt' :: Position -> Position -> Dimension -> Dimension -> X Window createWindowAt' x y width height = withDisplay $ \dpy -> io $ do @@ -117,22 +160,22 @@ createWindowAt' x y width height = withDisplay $ \dpy -> io $ do attrmask = cWOverrideRedirect w <- allocaSetWindowAttributes $ \attributes -> do + set_override_redirect attributes True - createWindow - dpy -- display - rootw -- parent window - x -- x - y -- y - width -- width - height -- height - 0 -- border width - 0 -- depth - inputOnly -- class - visual -- visual - attrmask -- valuemask - attributes -- attributes - - -- we only need mouse entry events + createWindow dpy -- display + rootw -- parent window + x -- x + y -- y + width -- width + height -- height + 0 -- border width + 0 -- depth + inputOnly -- class + visual -- visual + attrmask -- valuemask + attributes -- attributes + + -- we only need mouse entry events selectInput dpy w enterWindowMask selectInput dpy w enterWindowMask mapWindow dpy w sync dpy False @@ -147,8 +190,8 @@ screenCornerEventHook :: Event -> X All screenCornerEventHook CrossingEvent {ev_window = win} = do ScreenCornerState m <- XS.get - case M.lookup win m of - Just (_, xF) -> xF + case find (\(_, (w, _)) -> w == win) (M.toList m) of + Just (_, (_, xF)) -> xF Nothing -> return () return (All True) @@ -164,7 +207,7 @@ data ScreenCornerLayout a = ScreenCornerLayout instance LayoutModifier ScreenCornerLayout a where hook ScreenCornerLayout = withDisplay $ \dpy -> do ScreenCornerState m <- XS.get - io $ mapM_ (raiseWindow dpy) $ M.keys m + io $ mapM_ (raiseWindow dpy . fst . snd) $ M.toList m unhook = hook screenCornerLayoutHook :: l a -> ModifiedLayout ScreenCornerLayout l a @@ -188,11 +231,15 @@ screenCornerLayoutHook = ModifiedLayout ScreenCornerLayout -- -- > myStartupHook = do -- > ... --- > addScreenCorner SCUpperRight (goToSelected def { gs_cellwidth = 200}) --- > addScreenCorner SCBottom (goToSelected def) --- > addScreenCorners [ (SCLowerRight, nextWS) --- > , (SCLowerLeft, prevWS) --- > ] +-- > addMonitorCorner SCUpperLeft 0 20 (spawn "firefox") +-- > addMonitorCorner SCBottom 0 20 (spawn "firefox") +-- > addMonitorCorners [ (SCUpperRight, 1, 20, spawn "xterm") +-- > , (SCLowerRight, 2, 20, nextWS) +-- > ] +-- > ... +-- +-- Where 0-2 are the monitors and 20 is the size of the hot corner. +-- -- -- Then add layout hook: --