Xmonad (Вопросы и обсуждение)

ion, dwm, wmii, awesome, xmonad и другие

Модераторы: broom, aim

Аватара пользователя
AlexYeCu
Сообщения: 1103
ОС: fedora

Re: Xmonad

Сообщение AlexYeCu » 12.12.2015 15:25

Вариант с eventHadler-ом даёт как раз нужное поведение.
Спасибо сказали:

Аватара пользователя
sgfault
Сообщения: 586
Статус: -

Re: Xmonad

Сообщение sgfault » 13.12.2015 18:18

Что касается ответа на ваш старый вопрос..

AlexYeCu писал(а):
01.03.2015 16:19
-фреймы на нём автоматически не создаются; --вот этого пока не сделал


Код: Выделить всё

import Data.Maybe
import Control.Applicative

import XMonad
import qualified XMonad.StackSet as W


Назовем workspace, на котором нельзя открывать окна, lock workspace. Тогда определим функцию для выбора какого-то другого workspace, куда окно будет перекинуто в случае, если оно должно быть помещено на lock:

Код: Выделить всё

anotherWorkspace :: WorkspaceId -> WindowSet -> WorkspaceId
anotherWorkspace t      = head . filter (/= t) . map W.tag . W.workspaces


Тогда manageHook для реализации lock workspace можно сделать так:
1. В первом варианте будем перемещать окно в фокусе на anotherWorkspace только, если текущий workspace - lock:

Код: Выделить всё

-- Move away focused window (on current workspace), if current workspace is
-- "locked". Note, that such implementation will move *not* new window, if
-- focusDown is used (for keeping focus still, when new window appears), and
-- does not work at all, if new window gets moved to "lock" workspace, while
-- current workspace is another (not "lock").
manageLockWorkspace :: WorkspaceId -> ManageHook
manageLockWorkspace t   = do
    w <- ask
    --doF $ \ws -> lockWorkspace t (W.findTag w ws) ws
    doF $ flip (lockWorkspace t) <*> W.findTag w

lockWorkspace :: WorkspaceId -> Maybe WorkspaceId -> WindowSet -> WindowSet
lockWorkspace t = maybe id $ \i ->
    if i == t
      then flip W.shift <*> (anotherWorkspace t)
      else id


В этой реализации:
- если в ManageHook используется focusDown (чтобы избежать изменения фокуса при открытии новых окон), на anotherWorkspace переместится *не* новое окно, а какое-то из открытых ранее.
- если текущий workspace не lock и новое окно открывается на lock workspace (например, так `className =? "XClock" --> doShift "7"`), то этот manageLockWorkspace вообще не сработает.

2. Второй вариант работает всегда (те вне зависимости от фокуса и текущего workspace):

Код: Выделить всё

-- Moves away new window from "lock" workspace regardless of current workspace
-- and focus.
manageLockWorkspace2 :: WorkspaceId -> ManageHook
manageLockWorkspace2 t  = ask >>= doF . lockWorkspace2 t

lockWorkspace2 :: WorkspaceId -> Window -> WindowSet -> WindowSet
lockWorkspace2 t w ws = fromMaybe ws $ do
    i <- W.findTag w ws
    return $ if i == t
      then W.shiftWin (anotherWorkspace t ws) w ws
      else ws


Разница между этими вариантами - какая функция используется для перемещения окна: в первом - shift, во втором - shiftWin.

Также, хочу обратить внимание, что получение workspace, где откроется новое окно (с помощью `findTag`), должно производится в pure функции (Endo WindowSet), которую возвращает ManageHook. Те, это нельзя делать в Query монаде - такой вариант работать вообще не будет:

Код: Выделить всё

-- Not working version of "moving away new window".
manageLockWorkspace3 :: WorkspaceId -> ManageHook
manageLockWorkspace3 t  = do
    w <- ask
    mi <- liftX $ withWindowSet (return . W.findTag w)
    trace (show mi)
    doF $ \ws -> flip (maybe ws) mi $ \i ->
      if i == t
        then W.shiftWin (anotherWorkspace t ws) w ws
        else ws


Дело в том, что все действия в монаде запускаются до того, как новое окно будет добавлено в WindowSet (из XMonad/Operations.hs):

Код: Выделить всё

manage ... =
    mh <- asks (manageHook . config)
    g <- appEndo <$> userCodeDef (Endo id) (runQuery mh w)
    windows (g . f)


Все действия в manageHook отработали, чтобы получить функцию g . Новое окно добавляется в WindowSet pure функцией f (определенной тоже в `manage`), и, соответственно, только у функции g есть возможность работать с WindowSet-ом, в котором уже есть новое окно (чтобы определить workspace, где будет помещено окно, нужно работать именно с WindowSet-ом).

Еще хочу обратить внимание, что в случае использования composeOne (из XMonad.Hooks.ManageHelpers) или перемещений окна на другой workspace с помощью `className =? "XClock" --> doShift "7"` и подобного, надо следить, чтобы manageLockWorkspace запускался последним (те самый левый член в сумме моноидов <+>), тк моноид Endo - это просто композиция функций, в которой члены слева применяются позже. Другими словами, функция "видит" окно там, куда его поместили функции, выполнившиеся до нее, а не после.
Спасибо сказали:

Аватара пользователя
sgfault
Сообщения: 586
Статус: -

Re: Xmonad

Сообщение sgfault » 12.10.2016 12:43

Не прошло и года, как я могу предложить другой способ реализации рабочего стола блокировки (lock workspace), те просто рабочего стола, на котором не создаются новые окна:

Код: Выделить всё

manageLock :: WorkspaceId -> (WindowSet -> WorkspaceId) -> ManageHook
manageLock lockWs anotherWs = manageFocus (newOn lockWs --> moveTo anotherWs)

moveTo :: (WindowSet -> WorkspaceId) -> FocusHook
moveTo anotherWs    = new $ asks pure >>= doF . (shiftWin <*> anotherWs <*>)
  where
    shiftWin :: WindowSet -> WorkspaceId -> Window -> WindowSet
    shiftWin ws i w = W.shiftWin i w ws


В отличие от предыдущего здесь я использовал предикат `newOn` для определения, что новое окно появилось на рабочем столе блокировки. Те то, про что выше я писал, можно определить только в pure функции (WindowSet -> WindowSet). И теперь.. пришло время переписывать xmonad, xmonad сам себе не перепишет представить модуль XMonad.Hooks.Focus . Насколько мне известно, в xmonad-contrib нету пока что модуля, предоставляющего похожие возможности (хотя я не очень старался его найти).

Модуль реализует монад над Query, который предоставляет дополнительную информацию о новом окне:
- рабочий стол, где появится новое окно.
- сфокусированное (сейчас) окно на этом рабочем столе.
- текущий рабочий стол (просто для полноты информации, это можно узнать и без всяких модулей).

И два флага в extensible state:
- включена ли блокировка фокуса? Если блокировка включена, то библиотечные функции (этого модуля) не будут перемещать фокус на новое окно.
- было ли новое окно активировано (_NET_ACTIVE_WINDOW)? Точнее, в этом случае это уже будет не новое окно, но с ним можно работать, как с новым.

Модуль также предоставляет операции "поднимающие" (lift) стандартные комбинаторы ManageHook-а в монад FocusQuery, чтобы их (комбинаторы) можно было запускать на сфокусированном окне, и функции, работающие в FocusQuery, для сохранения фокуса и рабочего стола, перемещения фокуса и переключения рабочих столов.

Пример FocusHook-а:

Код: Выделить всё

     -- FocusHook для активированных окон.
     activateFocusHook :: FocusHook
     activateFocusHook = composeAll
                     -- Не перемещать фокус, если `gmrun` сфокусирован на
                     -- рабочем столе, где появилось активированное окно.
                     -- Переключение рабочего стола все еще может произойти.
                     [ focused (className =? "Gmrun")
                                     --> keepFocus
                     -- Поведение по умолчанию для активированных окон:
                     -- переключиться на рабочий стол с активированным окном и
                     -- переместить на него фокус.
                     , return True   --> switchWorkspace <+> switchFocus
                     ]


     -- FocusHook для новых окон.
     newFocusHook :: FocusHook
     newFocusHook      = composeOne
                     -- Всегда перемещать фокус на (новое окно) `gmrun`.
                     [ new (className =? "Gmrun")        -?> switchFocus
                     -- Всегда сохранять фокус на окне `gmrun`. Если новое
                     -- окно тоже `gmrun`, то фокус переместится на него.
                     , focused (className =? "Gmrun")    -?> keepFocus
                     -- Если на текущем рабочем столе сфокусировано диалоговое
                     -- окно firefox-а (например, для ввода мастер-пароля) и
                     -- новое окно появляется тоже на текущем рабочем столе,
                     -- не перемещать фокус на новое окно.
                     , newOnCur <&&> focused
                         ((className =? "Iceweasel" <||> className =?  "Firefox") <&&> isDialog)
                                                         -?> keepFocus
                     -- Поведение по умолчанию для новых окон: перемещать
                     -- фокус на новое окно.
                     , return True                       -?> switchFocus
                     ]


Вот пример перемещения всех активируемых окон на текущий рабочий стол с сохранением фокуса (на текущем окне):

Код: Выделить всё

    import Data.Monoid
    import Control.Applicative
    import Control.Monad

    import XMonad
    import qualified XMonad.StackSet as W

    import XMonad.Hooks.Focus
    import XMonad.Hooks.ManageHelpers hiding (composeOne, (-?>))

    main :: IO ()
    main = do
             let xcf = handleFocusQuery (Just (0, xK_v)) (composeOne
                             [ activated -?> (newOnCur --> keepFocus)
                             , Just <$> newFocusHook
                             ])
                         $ defaultConfig
                             { modMask = mod4Mask
                             , manageHook = manageFocus activateOnCurrentWs
                             }
             xmonad xcf

    activateOnCurrentWs :: FocusHook
    activateOnCurrentWs = activated --> asks currentWorkspace >>=
                             new . unlessFocusLock . doShift

    composeOne :: (Monoid a, Monad m) => [m (Maybe a)] -> m a
    composeOne [] = return mempty
    composeOne (mx : xs) = do
         x <- mx
         case x of
           Just y  -> return y
           Nothing -> composeOne xs

    infixr 0 -?>
    (-?>) :: Monad m => m Bool -> m a -> m (Maybe a)
    (-?>) mb mx     = do
         b <- mb
         if b
           then liftM Just mx
           else return Nothing


Обратите внимание, что нужны более общие версии `composeOne` и (-?>), чем определены в X.H.ManageHelpers. К сожалению, X.H.ManageHelpers предоставляет комбинаторы, работающие только на ManageHook, хотя многие (к счастью) легко переписать на более общие.

Ограничения:
- детям до 18 лет..
- Модуль запускает весь `manageHook` для активированного окна. Это значит, что если какие-то действия должны выполняться только для новых окон, перед ними надо добавить предикат `not <$> activated`. После этого, соответственно, тип выражения станет FocusHook, и выражение надо будет сконвертировать обратно в ManageHook с помощью `manageFocus` .
- Тк этот модуль обрабатывает активацию окон, он не совместим с `ewmh` из X.H.EwmhDesktops. Те если их включить вместе, все скомпилится, просто активированное окно в результате окажется там, куда его отправит `ewmh`, и комбинаторы этого модуля не сработают.

Код модуля gist. Это версия, которую можно скопировать в '~/.xmonad/lib/XMonad/Hooks/Focus.hs' . Она компилируется на Haskell Platform 2013.2.0.0 (ghc 7.6.3), xmonad 0.12 на Debian 8 .
Репозиторий, в котором есть этот этот модуль - github/sgf-xmonad-modules. И мой конфиг xmonad-а, который включает этот модуль, собирается `stack`-ом и устанавливается `make install`-ом - sgf-xmonad-config (автоматика!).

На данный момент для модуля нету тестов, и докуменатция написана не в haddock формате (хотя комменты с примерами есть).

Перед использованием, прочитайте инструкцию в комментах (в начале файла).
Спасибо сказали: