----------------------------------------------------------------------------- -- | -- Module : XMonad.Hooks.OnPropertyChange -- Description : Apply a manageHook on a property (e.g., @WM_CLASS@) change -- Copyright : (c) Brandon S Allbery, 2015 -- License : BSD3-style (see LICENSE) -- -- Maintainer : allbery.b@gmail.com -- Stability : unstable -- Portability : not portable -- -- Module to apply a ManageHook to an already-mapped window when a property -- changes. This would commonly be used to match browser windows by title, -- since the final title will only be set after (a) the window is mapped, -- (b) its document has been loaded, (c) all load-time scripts have run. -- (Don't blame browsers for this; it's inherent in HTML and the DOM. And -- changing title dynamically is explicitly permitted by ICCCM and EWMH; -- you don't really want to have your editor window umapped/remapped to -- show the current document and modified state in the titlebar, do you?) -- -- This is a handleEventHook that triggers on a PropertyChange event. It -- currently ignores properties being removed, in part because you can't -- do anything useful in a ManageHook involving nonexistence of a property. -- -- This module could also be useful for Electron applications like Spotify -- which sets its WM_CLASS too late for window manager to map it properly. -- ----------------------------------------------------------------------------- module XMonad.Hooks.OnPropertyChange ( -- * Usage -- $usage onXPropertyChange, onTitleChange, onClassChange, ) where import XMonad import XMonad.Prelude -- $usage -- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.OnPropertyChange -- -- Enable it by including in you handleEventHook definition: -- -- > main = xmonad $ def -- > { ... -- > , handleEventHook = onXPropertyChange "WM_NAME" (title =? "Spotify" --> doShift "5") -- > , ... -- > } -- -- Or you could create a dynamicManageHook as below: -- -- > myDynamicManageHook :: ManageHook -- > myDynamicManageHook = -- > composeAll -- > [ className =? "Spotify" --> doShift (myWorkspaces !! 4), -- > title =? "maybe_special_terminal" <||> title =? "special_terminal" --> doCenterFloat, -- > className =? "dynamicApp" <&&> title =? "dynamic_app" --> doCenterFloat -- > ] -- -- And then use it in your handleEventHookDefinition: -- -- > main = xmonad $ def -- > { ... -- > , handleEventHook = onXPropertyChange "WM_NAME" myDynamicManageHook -- > , ... -- > } -- -- | -- Run a 'ManageHook' when a specific property is changed on a window. Note -- that this will run on any window which changes the property, so you should -- be very specific in your 'ManageHook' matching (lots of windows change -- their titles on the fly!): -- -- > onXPropertyChange "WM_NAME" (className =? "Iceweasel" <&&> title =? "whatever" --> doShift "2") -- -- Note that the fixity of (-->) won't allow it to be mixed with ($), so you -- can't use the obvious $ shorthand. -- -- > onXPropertyChange "WM_NAME" $ title =? "Foo" --> doFloat -- won't work! -- -- Consider instead phrasing it like any -- other 'ManageHook': -- -- > main = xmonad $ def -- > { ... -- > , handleEventHook = onXPropertyChange "WM_NAME" myDynHook -- > , ... -- > } -- > -- > myDynHook = composeAll [...] -- onXPropertyChange :: String -> ManageHook -> Event -> X All onXPropertyChange :: String -> ManageHook -> Event -> X All onXPropertyChange String prop ManageHook hook PropertyEvent { ev_window :: Event -> Atom ev_window = Atom w, ev_atom :: Event -> Atom ev_atom = Atom a, ev_propstate :: Event -> CInt ev_propstate = CInt ps } = do Atom pa <- String -> X Atom getAtom String prop Bool -> X () -> X () forall (f :: * -> *). Applicative f => Bool -> f () -> f () when (CInt ps CInt -> CInt -> Bool forall a. Eq a => a -> a -> Bool == CInt propertyNewValue Bool -> Bool -> Bool && Atom a Atom -> Atom -> Bool forall a. Eq a => a -> a -> Bool == Atom pa) (X () -> X ()) -> X () -> X () forall a b. (a -> b) -> a -> b $ do WindowSet -> WindowSet g <- Endo WindowSet -> WindowSet -> WindowSet forall a. Endo a -> a -> a appEndo (Endo WindowSet -> WindowSet -> WindowSet) -> X (Endo WindowSet) -> X (WindowSet -> WindowSet) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b <$> Endo WindowSet -> X (Endo WindowSet) -> X (Endo WindowSet) forall a. a -> X a -> X a userCodeDef ((WindowSet -> WindowSet) -> Endo WindowSet forall a. (a -> a) -> Endo a Endo WindowSet -> WindowSet forall a. a -> a id) (ManageHook -> Atom -> X (Endo WindowSet) forall a. Query a -> Atom -> X a runQuery ManageHook hook Atom w) (WindowSet -> WindowSet) -> X () windows WindowSet -> WindowSet g All -> X All forall a. a -> X a forall (m :: * -> *) a. Monad m => a -> m a return All forall a. Monoid a => a mempty -- so anything else also processes it onXPropertyChange String _ ManageHook _ Event _ = All -> X All forall a. a -> X a forall (m :: * -> *) a. Monad m => a -> m a return All forall a. Monoid a => a mempty -- | A shorthand for dynamic titles; i.e., applications changing their -- @WM_NAME@ property. onTitleChange :: ManageHook -> Event -> X All -- strictly, this should also check _NET_WM_NAME. practically, both will -- change and each gets its own PropertyEvent, so we'd need to record that -- we saw the event for that window and ignore the second one. Instead, just -- trust that nobody sets only _NET_WM_NAME. (I'm sure this will prove false, -- since there's always someone who can't bother being compliant.) onTitleChange :: ManageHook -> Event -> X All onTitleChange = String -> ManageHook -> Event -> X All onXPropertyChange String "WM_NAME" -- | A shorthand for dynamic resource and class names; i.e., -- applications changing their @WM_CLASS@ property. onClassChange :: ManageHook -> Event -> X All onClassChange :: ManageHook -> Event -> X All onClassChange = String -> ManageHook -> Event -> X All onXPropertyChange String "WM_CLASS"