Выходные выдались на удивление тихими, хотя оживленная рабочая неделя обещала совсем другое, и я продолжил играться с EeePC, в ключе "стянуть привычные вещи с Мака", на этот раз существенной корректировке подвергся конфиг Xmonad'а.
1. Фактически, активно пользоваться множеством рабочих столов я начал только в OS X - в Leopard'е они называются Spaces, и почему-то эти Spaces мне понравились и я нашел им достойное применение. По-умолчанию рабочих стола четыре, на каждом из которых я стараюсь запускать лишь определенные приложения: на первом - IM-клиент, на втором - терминал(ы), на третьем - почтовый клиент, на четвертом - различные браузеры. Такой расклад сложился исторически, и я к нему привык. Остальные приложения запускаются где прийдется, особой стратегии нет.
Что в этом плане прежде всего хотелось от Xmonad'а? - привязать запуск конкретного приложения к конкретному рабочему столу, для каждого рабочего стола установить свои специфические Layout'ы, которые подходят для запускаемых на нем приложений. Оказывается, сделать это достаточно просто. Допустим у нас четыре рабочих стола:
myWorkspaces = ["1","2","3","4"]
Для того чтобы firefox(iceweasel в дебиане) запускался на четвертом, в ManageHooks надо добавить что-то типа:
className =? "Iceweasel" --> doF(W.shift "4")
className можно подсмотреть в выводе утилиты xprop:
diesel@xenocefal:~$ xprop | grep WM_CLASS
WM_CLASS(STRING) = "xterm", "XTerm"
после запуска этой строчки курсор изменит форму и нужно будет щелкнуть им по окну WM_CLASS которого мы хотим узнать.
Вторая задача: каждому воркспейсу по своему Layout'у решается с помощью расширения XMonad.Layout.PerWorkspace, соответствующий import нужно добавить в начало конфига, а затем в layoutHook настройки будут выглядеть следующим образом:
myLayout = avoidStruts $
onWorkspace "1" (( windowNavigation $ (mytabs ****|* mytabs)) ||| mytabs) $
onWorkspaces ["2","3"] mytabs $
onWorkspace "4" (noBorders Full ||| mytabs)$
noBorders Full
я думаю комментировать тут особо нечего.
2. Apple'овцы для своих Spaces, кроме казалось бы логичной схемы переключения между рабочими столами а-ля Alt+F1, or smth like, когда нужно четко указать рабочий стол на который хочешь переключится, сделали еще одну, которой я активно пользуюсь. Основана эта схема переключения на, скажем так, задании относительного пути :) Допустим наша схема расположения рабочих столов выглядит вот так:
1 | 2 |
3 | 4 |
тогда, допустим если я нахожусь на рабочем столе за нумером два, и жму Ctrl + Down - я попадаю на рабочий стол за нумером четыре, Ctrl+Right - приведет на рабочий стол за нумером 3, а Ctrl+Up никуда не приведет, ну и так далее. При четырех рабочих столах, в принципе такими сочетаниями клавиш можно попасть с любого рабочего стола на любой. И хотя, вроде бы, такой способ переключения перечит заветам интерфейсостроителей в плане "хорошо, когда какое-нибудь действие приводит всегда к одному и тому же результату", что в нашем случае можно интерпретировать как "приводит на вполне определенный за каждым конкретным хоткеем рабочий стол", но тем не менее, как я уже сказал, подобную схему нахожу удобной.
Очевидный способ, частного решения данной задачи для четырех рабочих столов достаточно прост - зададим функцию которая, скажем по названию рабочего стола, и коду нажатой клавиши будет возвращать нам название рабочего стола на который требуется перейти. Получится что-то типа:
switchSpace "1" 2 = "1"
switchSpace "1" 4 = "4"
switchSpace "1" 6 = "2"
switchSpace "1" 8 = "3"
switchSpace "2" 2 = "2"
switchSpace "2" 4 = "1"
switchSpace "2" 6 = "3"
switchSpace "2" 8 = "4"
switchSpace "3" 2 = "1"
switchSpace "3" 4 = "2"
switchSpace "3" 6 = "4"
switchSpace "3" 8 = "3"
switchSpace "4" 2 = "2"
switchSpace "4" 4 = "3"
switchSpace "4" 6 = "1"
switchSpace "4" 8 = "4"
не очень красиво, но жить можно, если не понадобится больше рабочих столов. 2,4,6,8 - здесь, это отфонарные коэфициенты для стрелочек, на основе того как они на NumPad'е есть.
Когда такая функция в конфиге появится, в хоткеи можно дописать строчки типа:
((modMask, xK_Right), withWindowSet $ \s -> do windows $ W.view (switchSpace ( W.tag . W.workspace . W.current $ s ) 6))
и если мы ничего не напутали с цифрами - все будет работать. Попробуем немного упростить эту строчку, и сделать так чтобы нам было меньше писать:
((modMask, xK_Right), switchFrom 6)
где switchFrom объявим отдельно как:
switchFrom x = withWindowSet $ \s -> do windows $ W.view (switchSpace (currentTag s) x)
currentTag s = W.tag . W.workspace . W.current $ s
currentTag здесь возвращает название текущего воркспейса, которое нам нужно дать switchSpace для определения воркспейса на который мы хотим переключится, а собственно в функции switchFrom происходит переключение.
Допустим, что рабочих столов будет больше, очевидно нужно немного изменить поведение функции switchSpace для того чтобы она более разумно выдавала следующий рабочий стол. Итак:
switchSpace w x = myWorkspaces!!((myNextIndex w x)-1)
в переводе на человеческий: вытащить из списка myWorkspaces элемент за нумером, который получится в результате выполнения функции myNextIndex, ну вернее (myNextIndex-1). w - имя текущего воркспейса, x - "номер ассоциированный со стрелочкой".
myNextIndex w x = myNextWorkspace ( (myElemIndex w myWorkspaces)+1 ) x
здесь myElemIndex находит под каким индексом в списке myWorkspaces спрятано имя текущего десктоп, прибавляет к нему единицу(мне не хочется считать десктопы с нуля), и отдает на съедение myNextWorkspace:
myNextWorkspace current turn | current + turn > 0 && current + turn <= rows * cols = current + turn
| current + turn == 0 && turn == -1 = rows*cols
| current + turn == rows+cols + 1 && turn == 1 = 1
| otherwise = current
myNextWorkspace самая главная функция. Логика работы следующая, мы немного меняем циферки которые передаются при нажатии каждой клавиши:
Up = (-колличество строк)
Down = колличество строк
Left = -1
Up = +1
и.... пробуем: если сумма номера текущего рабочего стола влазит в количество рабочих столов - возвращаем эту сумму, если не влазит - возвращаем другие странные значения. Все вместе это выглядит примерно так. Хоткеи:
[ ((modMask, xK_Right), switchFrom 1)
,((modMask, xK_Left), switchFrom (-1))
,((modMask, xK_Up), switchFrom (-rows))
,((modMask, xK_Down), switchFrom rows) ]
Прочая ерунда:
switchFrom x = withWindowSet $ \s -> do windows $ W.view (switchSpace (currentTag s) x)
currentTag s = W.tag . W.workspace . W.current $ s
rows = 2
cols = 2
myNextWorkspace :: Int->Int->Int
myNextWorkspace current turn | current + turn > 0 && current + turn <= rows * cols = current + turn
| current + turn == 0 && turn == -1 = rows*cols
| current + turn == rows+cols + 1 && turn ==1=1
| otherwise = current myElemIndex :: Eq a => a -> [a] -> Int
myElemIndex x a = head (elemIndices x a)
myNextIndex w x = myNextWorkspace ( (myElemIndex w myWorkspaces)+1 ) x
switchSpace w x = myWorkspaces!!((myNextIndex w x)-1)
Я не силен в haskell'е - много раз начинал разбираться, но кажется надо очень много свободного времени для таких разборок... и этот десяток строк занял у мну достаточно много времени на разбирательства, хотя не скажу что было скучно и неинтересно. Уверен, что можно это записать как-то более разумно, но я рад получить хоть и ugly, но работающий вариант. :)