]> git.madduck.net Git - etc/awesome.git/blob - .config/awesome/rc.lua

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

moving tags between screens
[etc/awesome.git] / .config / awesome / rc.lua
1 -- {{{ Imports
2
3 -- Standard awesome library
4 local gears = require("gears")
5 local awful = require("awful")
6 require("awful.autofocus")
7 -- Widget and layout library
8 local wibox = require("wibox")
9 -- Tyrannical tab handling
10 --local tyrannical = require("tyrannical")
11 -- Theme handling library
12 local beautiful = require("beautiful")
13 local xrdb = beautiful.xresources
14 -- Notification library
15 local naughty = require("naughty")
16 local menubar = require("menubar")
17 local hotkeys_popup = require("awful.hotkeys_popup").widget
18 -- Enable hotkeys help widget for VIM and other apps
19 -- when client with a matching name is opened:
20 require("awful.hotkeys_popup.keys")
21
22 -- Load Debian menu entries
23 local debian = require("debian.menu")
24 local has_fdo, freedesktop = pcall(require, "freedesktop")
25 -- Other libraries
26 local lain = require("lain")
27 local ccwidgets = require("cryptocoin_widgets")
28 local fxwidgets = require("forex_widgets")
29 local clocksarray = require("clocksarray")
30 local dbg = require("debugfunc")
31 local th = require("taghelpers")
32 -- }}}
33
34 -- {{{ Error handling
35 -- Check if awesome encountered an error during startup and fell back to
36 -- another config (This code will only ever execute for the fallback config)
37 if awesome.startup_errors then
38     naughty.notify({ preset = naughty.config.presets.critical,
39                      title = "Oops, there were errors during startup!",
40                      text = awesome.startup_errors })
41 end
42
43 -- Handle runtime errors after startup
44 do
45     local in_error = false
46     awesome.connect_signal("debug::error", function (err)
47         -- Make sure we don't go into an endless error loop
48         if in_error then return end
49         in_error = true
50
51         naughty.notify({ preset = naughty.config.presets.critical,
52                          title = "Oops, an error happened!",
53                          text = tostring(err) })
54         in_error = false
55     end)
56 end
57 -- }}}
58
59 -- {{{ Variable definitions
60 --xrdb.set_dpi(95, screen[1])
61 --xrdb.set_dpi(120, screen[2])
62
63 -- Themes define colours, icons, font and wallpapers.
64 beautiful.init(gears.filesystem.get_configuration_dir () .. "theme/theme.lua")
65
66 -- This is used later as the default terminal and editor to run.
67 terminal = "rxvt-unicode"
68 editor = os.getenv("EDITOR") or "editor"
69 editor_cmd = terminal .. " -e " .. editor
70
71 -- Default modkey.
72 -- Usually, Mod4 is the key with a logo between Control and Alt.
73 -- If you do not like this or do not have such a key,
74 -- I suggest you to remap Mod4 to another key using xmodmap or other tools.
75 -- However, you can use another modifier like Mod1, but it may interact with others.
76 modkey = "Mod4"
77 cmdkey = "Mod3"
78
79 -- Table of layouts to cover with awful.layout.inc, order matters.
80 local layouts = {
81     default = awful.layout.suit.fair,
82     default_horiz = awful.layout.suit.fair.horizontal,
83     tiled = awful.layout.suit.tile,
84     tiled_horiz = awful.layout.suit.tile.top,
85     floating = awful.layout.suit.floating,
86     maximised = awful.layout.suit.max
87 }
88 awful.layout.layouts = {
89     layouts.default,
90     layouts.tiled,
91     layouts.maximised,
92     layouts.floating,
93     layouts.default_horiz,
94     layouts.tiled_horiz,
95 }
96 -- }}}
97
98 -- {{{ Helper functions
99 local function client_menu_toggle_fn()
100     local instance = nil
101
102     return function ()
103         if instance and instance.wibox.visible then
104             instance:hide()
105             instance = nil
106         else
107             instance = awful.menu.clients({ theme = { width = 250 } })
108         end
109     end
110 end
111
112 local function set_wallpaper(s)
113     -- Wallpaper
114     if beautiful.wallpaper then
115         local wallpaper = beautiful.wallpaper
116         -- If wallpaper is a function, call it with the screen
117         if type(wallpaper) == "function" then
118             wallpaper = wallpaper(s)
119         end
120         gears.wallpaper.maximized(wallpaper, s, true)
121     end
122 end
123
124 local function move_mouse_to_area(a)
125     local coords = mouse.coords()
126     if (coords.x < a.x or
127         coords.x > (a.x+a.width) or
128         coords.y < a.y or
129         coords.y > (a.y+a.height)) then
130
131         mouse.coords({
132             x = a.x + a.width/2,
133             y = a.y + a.height/2,
134         }, true)
135     end
136 end
137
138 -- }}}
139
140 -- {{{ Menu
141 -- Create a launcher widget and a main menu
142 myawesomemenu = {
143    { "hotkeys", function() return false, hotkeys_popup.show_help end},
144    { "manual", terminal .. " -e man awesome" },
145    { "edit config", editor_cmd .. " " .. awesome.conffile },
146    { "restart", awesome.restart },
147    { "quit", function() awesome.quit() end}
148 }
149
150 local menu_awesome = { "awesome", myawesomemenu, beautiful.awesome_icon }
151 local menu_terminal = { "open terminal", terminal }
152
153 if has_fdo then
154     mymainmenu = freedesktop.menu.build({
155         before = { menu_awesome },
156         after =  { menu_terminal }
157     })
158 else
159     mymainmenu = awful.menu({
160         items = {
161                   menu_awesome,
162                   { "Debian", debian.menu.Debian_menu.Debian },
163                   menu_terminal,
164                 }
165     })
166 end
167
168
169 mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon,
170                                      menu = mymainmenu })
171
172 -- Menubar configuration
173 menubar.utils.terminal = terminal -- Set the terminal for applications that require it
174 -- }}}
175
176 -- {{{ Wibar
177 --local spacer = wibox.widget {
178 --    color = beautiful.bg_minimize,
179 --    forced_width = 4,
180 --    widget = wibox.widget.separator
181 --}
182 local function make_spacer(text)
183     local spacer = wibox.widget.textbox()
184     spacer:set_text(text or " │ ")
185     return spacer
186 end
187
188 -- Keyboard map indicator and switcher
189 mykeyboardlayout = awful.widget.keyboardlayout()
190
191 local lain_bat = lain.widget.bat({
192     batteries = {"BAT0", "BAT1"},
193     settings = function()
194         local delim = "↓"
195         if bat_now.status == "Charging" then delim = "↑"
196         elseif bat_now.status == "Unknown" then delim = "٭" end
197         widget:set_text(bat_now.perc .. "% " .. delim .. " " .. bat_now.time)
198     end,
199 })
200
201 -- Create a textclock widget
202 clocksarray = clocksarray.get_clocksarray("%a %d %b %H:%M:%S %Z", {
203 --        ["NZ"] = "Pacific/Auckland",
204         ["DE"] = "Europe/Berlin"
205     }, make_spacer())
206
207 -- Create a wibox for each screen and add it
208 local taglist_buttons = gears.table.join(
209                     awful.button({ }, 1, function(t) t:view_only() end),
210                     awful.button({ modkey }, 1, function(t)
211                                               if client.focus then
212                                                   client.focus:move_to_tag(t)
213                                               end
214                                           end),
215                     awful.button({ }, 3, awful.tag.viewtoggle),
216                     awful.button({ modkey }, 3, function(t)
217                                               if client.focus then
218                                                   client.focus:toggle_tag(t)
219                                               end
220                                           end),
221                     awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end),
222                     awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end)
223                 )
224
225 local tasklist_buttons = gears.table.join(
226                      awful.button({ }, 1, function (c)
227                                               if c == client.focus then
228                                                   -- I don't like click-minimising
229                                                   -- c.minimized = true
230                                               else
231                                                   -- Without this, the following
232                                                   -- :isvisible() makes no sense
233                                                   c.minimized = false
234                                                   if not c:isvisible() and c.first_tag then
235                                                       c.first_tag:view_only()
236                                                   end
237                                                   -- This will also un-minimize
238                                                   -- the client, if needed
239                                                   client.focus = c
240                                                   c:raise()
241                                               end
242                                           end),
243                      awful.button({ }, 3, client_menu_toggle_fn()),
244                      awful.button({ }, 4, function ()
245                                               awful.client.focus.byidx(1)
246                                           end),
247                      awful.button({ }, 5, function ()
248                                               awful.client.focus.byidx(-1)
249                                           end))
250 -- }}}
251
252 -- {{{ Screens
253
254 -- Re-set wallpaper when a screen's geometry changes (e.g. different resolution)
255 screen.connect_signal("property::geometry", set_wallpaper)
256
257 -- {{{ Basic setup for screens
258 local function screen_set_profile(s, profile)
259     s.profile = profile
260     s.outputstr = table.concat(gears.table.keys(s.outputs), "+")
261     s.name = s.profile .. "/" .. s.outputstr
262 end
263
264 awful.screen.connect_for_each_screen(function(s)
265
266     s.set_profile = screen_set_profile
267
268     -- Wallpaper
269     set_wallpaper(s)
270
271     -- Create a text widget to display screen name
272     s.namebox = wibox.container.background(wibox.widget.textbox(s.name),
273       beautiful.bg_minimize)
274
275     -- Create a promptbox for each screen
276     s.mypromptbox = awful.widget.prompt()
277     -- Create an imagebox widget which will contains an icon indicating which layout we're using.
278     -- We need one layoutbox per screen.
279     s.mylayoutbox = awful.widget.layoutbox(s)
280     s.mylayoutbox:buttons(awful.util.table.join(
281                            awful.button({ }, 1, function () awful.layout.inc( 1) end),
282                            awful.button({ }, 3, function () awful.layout.inc(-1) end),
283                            awful.button({ }, 4, function () awful.layout.inc( 1) end),
284                            awful.button({ }, 5, function () awful.layout.inc(-1) end)))
285     -- Create a taglist widget
286     s.mytaglist = awful.widget.taglist(s, awful.widget.taglist.filter.all, taglist_buttons)
287
288     -- Create a tasklist widget
289     s.mytasklist = awful.widget.tasklist(s, awful.widget.tasklist.filter.currenttags, tasklist_buttons)
290
291     -- Create the wibox, but only if there isn't one yet
292     if not s.mywibox then
293         s.mywibox = awful.wibar({ position = "top", screen = s })
294     end
295
296     -- Add widgets to the wibox
297     local right_widgets = gears.table.join(clocksarray, {
298         make_spacer(" "),
299         wibox.widget.systray(),
300         s.mylayoutbox,
301         layout = wibox.layout.fixed.horizontal,
302     })
303
304 --    if s == screen.primary then
305         right_widgets = gears.table.join({
306             make_spacer(" "),
307             ccwidgets.btc_widget,
308             make_spacer(),
309             ccwidgets.eth_widget,
310             make_spacer(),
311             fxwidgets.ecb_widget,
312             make_spacer(),
313             lain_bat.widget,
314             make_spacer(),
315         }, right_widgets)
316 --    end
317
318     s.mywibox:setup {
319         layout = wibox.layout.align.horizontal,
320         { -- Left widgets
321             layout = wibox.layout.fixed.horizontal,
322             --s.namebox,
323             s.mytaglist,
324             make_spacer(" "),
325             s.mypromptbox,
326         },
327         s.mytasklist, -- Middle widget
328         right_widgets,
329     }
330 end) -- }}}
331
332 -- {{{ autorandr integration
333 local function find_screen_by_pattern(pattern)
334     for s in screen do
335         print(s.name .. " :: " .. pattern)
336         if s.name:match(pattern) then
337             return s
338         end
339     end
340 end
341
342 local function get_target_screen_for_tag(tag)
343     local function primary_screen(reason)
344         local s = screen.primary
345         local msg = "  → primary screen \"" .. s.name .. "\""
346         if reason then msg = msg .. " (" .. reason .. ")" end
347         print(msg)
348         return s
349     end
350
351     print("Figuring out target screen for tag " .. tag.name .. "…")
352     if tag.targets then
353         if type(tag.targets) == "table" then
354             for _,target in ipairs(tag.targets) do
355                 local s = find_screen_by_pattern(target:gsub('%-', '%%-'))
356                 if s then
357                     print("  → screen " .. s.name)
358                     return s
359                 end
360             end
361         elseif tag.targets == "primary" then
362             return primary_screen("explicit request")
363         end
364         return primary_screen("no matching target in " .. table.concat(tag.targets, ","))
365     else
366         return primary_screen("no targets specified")
367     end
368 end
369
370 local function move_tag_to_target_screen(tag)
371     tag.screen = get_target_screen_for_tag(tag)
372 end
373
374 local function move_tags_to_target_screens()
375     for _,tag in ipairs(root.tags()) do
376         move_tag_to_target_screen(tag)
377     end
378 end
379
380 tag.connect_signal("request::screen", function(t)
381     -- throw the tag onto any other screen, it'll get reassigned later when
382     -- a new profile has been processed.
383     for s in screen do
384         if s ~= t.screen then
385             t.screen = s
386             t.selected = false
387             break
388         end
389     end
390     naughty.notify({
391         title = "Screen removed",
392         text = "Salvaged tab " .. t.name,
393     })
394 end)
395
396 function handle_new_autorandr_profile(newprofile)
397     -- The main idea here is that autorandr invokes this via awesome-client
398     -- after switching to a new profile. Awesome will have already set up all
399     -- the screens long before this function is called. Therefore, we just do
400     -- the necessary modifications to the existing screens, and move tags
401     -- around.
402
403     if not newprofile then
404         error("Missing new profile name")
405     end
406
407     naughty.notify({
408         preset = naughty.config.presets.low,
409         title = "New autorandr profile",
410         text = "Reconfiguring for profile <b>" .. newprofile .. "</b>",
411     })
412
413     for s in screen do
414         s:set_profile(newprofile)
415     end
416     move_tags_to_target_screens()
417 end
418
419 local function initialise_to_autorandr_profile()
420     local profile
421     profile = nil
422
423     local function process_line(line)
424         if profile then return end
425         local match = string.match(line, "^([^%s]+) %(detected%)")
426         if match then
427             profile = match
428         end
429     end
430
431     local function output_done()
432         if not profile then
433             error("autorandr detected no profile")
434             profile = "awesome"
435         end
436         handle_new_autorandr_profile(profile)
437     end
438
439     local function handle_exit(reason, code)
440         if not (reason == "exit" and code == 0) then
441             error("autorandr error: " .. reason .. ": " .. tostring(code))
442         end
443     end
444
445     awful.spawn.with_line_callback('autorandr', {
446         stdout = process_line,
447         output_done = output_done,
448         exit = handle_exit
449     })
450 end
451 awesome.connect_signal("startup", initialise_to_autorandr_profile)
452 -- }}}
453
454 -- }}}
455
456 -- {{{ Tags
457
458 local default_tag = {
459     name        = nil,
460     init        = true,
461     layout      = layouts.default,
462     fallback    = true,
463     targets     = "primary",
464 }
465 local default_tags = {}
466 for i = 1, 9 do
467     default_tags[i] = {}
468     for k,v in pairs(default_tag) do
469         default_tags[i][k] = v
470     end
471     default_tags[i].name = tostring(i)
472 end
473 default_tags[1].selected = true
474
475 default_tags = gears.table.join(default_tags, {
476   {
477     name        = "irc",
478     init        = true,
479     exclusive   = true,
480     layout      = layouts.tiled,
481     selected    = true,
482     exec_once   = { terminal .. " -name irc -e env MOSH_TITLE_NOPREFIX=true mosh --family=all -- irc-host tmux new -As irc irssi" },
483     instance    = { "irc" },
484     targets     = { "gauting/eDP-?1", "lehel/DisplayPort-2" },
485   },
486   {
487     name        = "[]",
488     init        = true,
489     exclusive   = true,
490     master_count = 0,
491     column_count = 3,
492     layout      = layouts.tiled,
493     selected    = false,
494     exec_once   = { "revolt" },
495     instance    = { "Revolt" },
496     targets     = { "gauting/eDP-?1", "lehel/DisplayPort-2" },
497   },
498   {
499     name        = "dflt",
500     init        = false,
501     fallback    = true,
502     layout      = layouts.floating,
503     volatile    = true,
504     selected    = true,
505   },
506   {
507     name        = "cal",
508     init        = true,
509     exclusive   = true,
510     layout      = layouts.default,
511     exec_once   = { "thunderbird" },
512     class       = { "thunderbird" },
513     targets     = { "gauting/eDP-?1", "lehel/DisplayPort-1" },
514   },
515   {
516     name        = "chr",
517     init        = true,
518     exclusive   = true,
519     layout      = layouts.default,
520     exec_once   = { "chromium" },
521     class       = { "Chromium" },
522     targets     = { "gauting/eDP-?1", "lehel/DisplayPort-1", "present/HDMI.*" },
523   },
524   {
525     name        = "ffx",
526     init        = true,
527     exclusive   = true,
528     layout      = layouts.default,
529     exec_once   = { "firefox" },
530     class       = { "Firefox" },
531     targets     = { "gauting/eDP-?1", "lehel/DisplayPort-1", "present/HDMI.*" },
532   },
533 })
534
535 if not tyrannical then
536
537 for _,t in ipairs(default_tags) do
538     if t.init then
539         t.screen = t.screen or screen.primary
540         t.layout = t.layout or layouts.default
541         local newt = th.add_tag(t.name, t, false)
542     end
543 end
544
545 else -- {{{ tyrannical is loaded
546 tyrannical.settings.default_layout = layouts.default
547 tyrannical.settings.master_width_factor = 0.5
548 tyrannical.settings.block_children_focus_stealing = true
549 tyrannical.settings.group_children = true
550
551 tyrannical.tags = default_tags
552
553 tyrannical.properties.size_hints_honor = { URxvt = false }
554
555 --XX---- Ignore the tag "exclusive" property for the following clients (matched by classes)
556 --XX--tyrannical.properties.intrusive = {
557 --XX--  "ksnapshot"     , "pinentry"       , "gtksu"     , "kcalc"        , "xcalc"               ,
558 --XX--  "feh"           , "Gradient editor", "About KDE" , "Paste Special", "Background color"    ,
559 --XX--  "kcolorchooser" , "plasmoidviewer" , "Xephyr"    , "kruler"       , "plasmaengineexplorer",
560 --XX--}
561 --XX--
562 --XX---- Ignore the tiled layout for the matching clients
563 --XX--tyrannical.properties.floating = {
564 --XX--  "MPlayer"      , "pinentry"        , "ksnapshot"  , "pinentry"     , "gtksu"          ,
565 --XX--  "xine"         , "feh"             , "kmix"       , "kcalc"        , "xcalc"          ,
566 --XX--  "yakuake"      , "Select Color$"   , "kruler"     , "kcolorchooser", "Paste Special"  ,
567 --XX--  "New Form"     , "Insert Picture"  , "kcharselect", "mythfrontend" , "plasmoidviewer"
568 --XX--}
569 --XX--
570 --XX---- Make the matching clients (by classes) on top of the default layout
571 --XX--tyrannical.properties.ontop = {
572 --XX--  "Xephyr"       , "ksnapshot"       , "kruler"
573 --XX--}
574 --XX--
575 --XX---- Force the matching clients (by classes) to be centered on the screen on init
576 --XX--tyrannical.properties.centered = {
577 --XX--  "kcalc"
578 --XX--}
579 end -- }}}
580
581 -- }}}
582
583 -- {{{ Mouse bindings
584 root.buttons(gears.table.join(
585     awful.button({ }, 3, function () mymainmenu:toggle() end),
586     awful.button({ }, 4, awful.tag.viewnext),
587     awful.button({ }, 5, awful.tag.viewprev)
588 ))
589 -- }}}
590
591 -- {{{ Key bindings
592
593 local function toggle_tag_by_name(tagname, exclusive)
594     return function()
595         local t = awful.tag.find_by_name(nil, tagname)
596         if t then
597             if exclusive then
598                 t:view_only()
599             else
600                 awful.tag.viewtoggle(t)
601             end
602             cf = awful.client.getmaster(t.screen)
603             if cf then
604                 cf:jump_to()
605             end
606         end
607     end
608 end
609
610 local function move_tags_to_screen_relative(direction)
611     local s = awful.screen.focused()
612     for _,tag in ipairs(s.selected_tags) do
613         print("index: " .. s.index .. " count: " .. screen:count())
614         tag.screen = screen[(s.index + screen:count() + direction) % screen.count()]
615     end
616 end
617
618 globalkeys = gears.table.join(
619     awful.key({ modkey,           }, "s",      hotkeys_popup.show_help,
620               {description="show help", group="awesome"}),
621     awful.key({ modkey,           }, "Left",   awful.tag.viewprev,
622               {description = "view previous", group = "tag"}),
623     awful.key({ modkey,           }, "Right",  awful.tag.viewnext,
624               {description = "view next", group = "tag"}),
625     awful.key({ modkey,           }, "Escape", awful.tag.history.restore,
626               {description = "go back", group = "tag"}),
627
628     awful.key({ modkey,           }, "k",
629         function ()
630             awful.client.focus.byidx( 1)
631         end,
632         {description = "focus next by index", group = "client"}
633     ),
634     awful.key({ modkey,           }, "j",
635         function ()
636             awful.client.focus.byidx(-1)
637         end,
638         {description = "focus previous by index", group = "client"}
639     ),
640
641     -- Layout manipulation
642     awful.key({ modkey, "Shift"   }, "k", function () awful.client.swap.byidx( 1)    end,
643               {description = "swap with next client by index", group = "client"}),
644     awful.key({ modkey, "Shift"   }, "j", function () awful.client.swap.byidx(-1)    end,
645               {description = "swap with previous client by index", group = "client"}),
646     awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative( 1) end,
647               {description = "focus the next screen", group = "screen"}),
648     awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative(-1) end,
649               {description = "focus the previous screen", group = "screen"}),
650     awful.key({ modkey, "Shift", "Control" }, "k", function () move_tags_to_screen_relative( 1) end,
651               {description = "move tags to the next screen", group = "screen"}),
652     awful.key({ modkey, "Shift", "Control" }, "j", function () move_tags_to_screen_relative(-1) end,
653               {description = "move tags to the previous screen", group = "screen"}),
654     awful.key({ modkey, "Shift"   }, "Return", awful.client.urgent.jumpto,
655               {description = "jump to urgent client", group = "client"}),
656     awful.key({ modkey,           }, "Tab",
657         function ()
658             awful.client.focus.history.previous()
659             if client.focus then
660                 client.focus:raise()
661             end
662         end,
663         {description = "go back", group = "client"}),
664
665     -- Standard program
666     awful.key({ modkey,           }, "Return", function () awful.spawn(terminal) end,
667               {description = "open a terminal", group = "launcher"}),
668     awful.key({ modkey,           }, "r", function()
669         package.loaded.rc = nil
670         require("rc")
671     end,
672               {description = "reload rc.lua", group = "awesome"}),
673     awful.key({ modkey, "Control" }, "r", awesome.restart,
674               {description = "reload awesome", group = "awesome"}),
675     awful.key({ modkey, "Shift"   }, "q", awesome.quit,
676               {description = "quit awesome", group = "awesome"}),
677
678     awful.key({ modkey,           }, "l",     function () awful.tag.incmwfact( 0.05)          end,
679               {description = "increase master width factor", group = "layout"}),
680     awful.key({ modkey,           }, "h",     function () awful.tag.incmwfact(-0.05)          end,
681               {description = "decrease master width factor", group = "layout"}),
682     awful.key({ modkey, "Shift"   }, "h",     function () awful.tag.incnmaster( 1, nil, true) end,
683               {description = "increase the number of master clients", group = "layout"}),
684     awful.key({ modkey, "Shift"   }, "l",     function () awful.tag.incnmaster(-1, nil, true) end,
685               {description = "decrease the number of master clients", group = "layout"}),
686     awful.key({ modkey, "Control" }, "h",     function () awful.tag.incncol( 1, nil, true)    end,
687               {description = "increase the number of columns", group = "layout"}),
688     awful.key({ modkey, "Control" }, "l",     function () awful.tag.incncol(-1, nil, true)    end,
689               {description = "decrease the number of columns", group = "layout"}),
690     awful.key({ modkey,           }, "space", function () awful.layout.inc( 1)                end,
691               {description = "select next", group = "layout"}),
692     awful.key({ modkey, "Shift"   }, "space", function () awful.layout.inc(-1)                end,
693               {description = "select previous", group = "layout"}),
694
695     awful.key({ modkey, "Control" }, "n",
696               function ()
697                   local c = awful.client.restore()
698                   -- Focus restored client
699                   if c then
700                       client.focus = c
701                       c:raise()
702                   end
703               end,
704               {description = "restore minimized", group = "client"}),
705
706     -- Prompt
707     awful.key({ cmdkey },            "r",
708               function ()
709                   local widget = awful.screen.focused().mypromptbox.widget
710                   local function spawn(command, args)
711                       gears.debug.dump(args)
712                       awful.spawn(command, args)
713                   end
714
715                   awful.prompt.run {
716                     prompt       = "Exec: ",
717                     bg_cursor    = '#ff0000',
718                     textbox      = widget,
719                     history_path = awful.util.get_cache_dir() .. "/history",
720                     completion_callback = awful.completion.shell,
721                     hooks = {
722                         -- Replace the 'normal' Return with a custom one
723                         {{         }, 'Return', function(command)
724                             spawn(command)
725                         end},
726                         -- Spawn method to spawn in the current tag
727                         {{'Mod1'   }, 'Return', function(command)
728                             spawn(command,{
729                                 intrusive = true,
730                                 tag       = mouse.screen.selected_tag
731                             })
732                         end},
733                         -- Spawn in the current tag as floating and on top
734                         {{'Shift'  }, 'Return', function(command)
735                             spawn(command,{
736                                 ontop     = true,
737                                 floating  = true,
738                                 tag       = mouse.screen.selected_tag
739                             })
740                         end},
741                         -- Spawn in a new tag
742                         {{'Control'}, 'Return', function(command)
743                             spawn(command,{
744                                 new_tag = true,
745                                 layout = layouts.default,
746                                 volatile = true,
747                             })
748                         end},
749                         -- Cancel
750                         {{         }, 'Escape', function(_) return end},
751                     },
752                 }
753         end,
754               {description = "run prompt", group = "launcher"}),
755
756     awful.key({ modkey }, "x",
757               function ()
758                   awful.prompt.run {
759                     prompt       = "Eval: ",
760                     bg_cursor    = '#ff0000',
761                     textbox      = awful.screen.focused().mypromptbox.widget,
762                     exe_callback = awful.util.eval,
763                     history_path = awful.util.get_cache_dir() .. "/history_eval"
764                   }
765               end,
766               {description = "lua execute prompt", group = "awesome"}),
767     -- Menubar
768     awful.key({ modkey }, "w", function() menubar.show() end,
769               {description = "show the menubar", group = "launcher"}),
770
771     -- Tag helpers
772     awful.key({ modkey,           }, "a", function()
773         th.add_tag(nil, {layout=layouts.default} ,true)
774     end,
775     {description = "add a tag", group = "tag"}),
776     awful.key({ modkey,           }, "d", th.delete_tag,
777               {description = "delete the current tag", group = "tag"}),
778     awful.key({ modkey, "Shift",           }, "a", function()
779         th.move_to_new_tag(nil, nil, { layout = layouts.maximised },true,true,true)
780     end,
781               {description = "add a volatile tag with the focused client", group = "tag"}),
782     awful.key({ modkey, "Shift", "Control" }, "a", function()
783         th.move_to_new_tag(nil, nil, { layout = layouts.maximised },false,true,true)
784     end,
785               {description = "add a permanent tag with the focused client", group = "tag"}),
786     awful.key({ modkey, "Mod1"   }, "a", th.copy_tag,
787               {description = "create a copy of the current tag", group = "tag"}),
788     awful.key({ modkey, "Control"   }, "a", th.rename_tag,
789               {description = "rename the current tag", group = "tag"}),
790     awful.key({ modkey, "Control", "Shift", "Mod1" }, "a", th.collect_orphan_clients_to_tag,
791               {description = "collect all orphaned clients", group = "client"}),
792
793     awful.key({ modkey }, "y", toggle_tag_by_name("irc", true),
794               {description = "view tag 'irc'", group = "tag"}),
795     awful.key({ modkey, "Control" }, "y", toggle_tag_by_name("irc"),
796               {description = "toggle tag 'irc'", group = "tag"}),
797     awful.key({ modkey }, "u", toggle_tag_by_name("[]", true),
798               {description = "view tag '[]'", group = "tag"}),
799     awful.key({ modkey, "Control" }, "u", toggle_tag_by_name("[]"),
800               {description = "toggle tag '[]'", group = "tag"}),
801     awful.key({ modkey }, "i", toggle_tag_by_name("cal", true),
802               {description = "view tag 'cal'", group = "tag"}),
803     awful.key({ modkey, "Control" }, "i", toggle_tag_by_name("cal"),
804               {description = "toggle tag 'cal'", group = "tag"}),
805     awful.key({ modkey }, "o", toggle_tag_by_name("chr", true),
806               {description = "view tag 'chr'", group = "tag"}),
807     awful.key({ modkey, "Control" }, "o", toggle_tag_by_name("chr"),
808               {description = "toggle tag 'chr'", group = "tag"}),
809     awful.key({ modkey }, "p", toggle_tag_by_name("ffx", true),
810               {description = "view tag 'ff'", group = "tag"}),
811     awful.key({ modkey, "Control" }, "p", toggle_tag_by_name("ffx"),
812               {description = "toggle tag 'ff'", group = "tag"}),
813 {})
814
815 clientkeys = gears.table.join(
816     awful.key({ modkey,           }, "f",
817         function (c)
818             c.fullscreen = not c.fullscreen
819             c:raise()
820         end,
821         {description = "toggle fullscreen", group = "client"}),
822     awful.key({ modkey, "Shift"   }, "c",      function (c) c:kill()                         end,
823               {description = "close", group = "client"}),
824     awful.key({ modkey, "Control" }, "space",  awful.client.floating.toggle                     ,
825               {description = "toggle floating", group = "client"}),
826     awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end,
827               {description = "move to master", group = "client"}),
828     awful.key({ modkey,           }, "z",      function (c) c:move_to_screen() end,
829               {description = "move to screen", group = "client"}),
830     awful.key({ modkey,           }, "t",      function (c) c.ontop = not c.ontop            end,
831               {description = "toggle keep on top", group = "client"}),
832     awful.key({ modkey,           }, "n",
833         function (c)
834             -- The client currently has the input focus, so it cannot be
835             -- minimized, since minimized clients can't have the focus.
836             c.minimized = true
837         end ,
838         {description = "minimize", group = "client"}),
839     awful.key({ modkey,           }, "m",
840         function (c)
841             c.maximized = not c.maximized
842             c.maximized_horizontal = false
843             c.maximized_vertical = false
844             c:raise()
845         end ,
846         {description = "(un)maximize", group = "client"}),
847     awful.key({ modkey, "Control" }, "m",
848         function (c)
849             c.maximized_vertical = not c.maximized_vertical
850             c:raise()
851         end ,
852         {description = "(un)maximize vertically", group = "client"}),
853     awful.key({ modkey, "Shift"   }, "m",
854         function (c)
855             c.maximized_horizontal = not c.maximized_horizontal
856             c:raise()
857         end ,
858         {description = "(un)maximize horizontally", group = "client"})
859 )
860
861 -- Bind all key numbers to tags.
862 -- Be careful: we use keycodes to make it work on any keyboard layout.
863 -- This should map on the top row of your keyboard, usually 1 to 9.
864 for i = 1, 9 do
865     globalkeys = gears.table.join(globalkeys,
866         -- View tag only.
867         awful.key({ modkey }, "#" .. i + 9, toggle_tag_by_name(tostring(i), true),
868                   {description = "view tag #"..i, group = "tag"}),
869         -- Toggle tag display.
870         awful.key({ modkey, "Control" }, "#" .. i + 9, toggle_tag_by_name(tostring(i)),
871                   {description = "toggle tag #" .. i, group = "tag"}),
872         -- Move client to tag.
873         awful.key({ modkey, "Shift" }, "#" .. i + 9,
874                   function ()
875                       if client.focus then
876                           local tag = awful.tag.find_by_name(screen.primary, tostring(i))
877                           if tag then
878                               client.focus:move_to_tag(tag)
879                           end
880                      end
881                   end,
882                   {description = "move focused client to tag #"..i, group = "tag"}),
883         -- Toggle tag on focused client.
884         awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9,
885                   function ()
886                       if client.focus then
887                           local tag = awful.tag.find_by_name(screen.primary, tostring(i))
888                           if tag then
889                               client.focus:toggle_tag(tag)
890                           end
891                       end
892                   end,
893                   {description = "toggle focused client on tag #" .. i, group = "tag"})
894     )
895 end
896
897 clientbuttons = gears.table.join(
898     awful.button({ }, 1, function (c) client.focus = c; c:raise() end),
899     awful.button({ modkey }, 1, awful.mouse.client.move),
900     awful.button({ modkey }, 3, awful.mouse.client.resize))
901
902 -- misc apps
903 globalkeys = awful.util.table.join(globalkeys,
904 awful.key({ cmdkey }, "n", function () awful.spawn("firefox") end),
905 awful.key({ cmdkey }, "c", function () awful.spawn("chromium --enable-remote-extensions") end),
906 awful.key({ cmdkey }, "y", function () awful.spawn(terminal .. " -e ipython3") end),
907 awful.key({ cmdkey }, "m", function () awful.spawn(terminal .. " -name mutt -e mutt") end),
908 awful.key({ cmdkey }, "t", function () awful.spawn("thunderbird") end),
909 awful.key({ cmdkey }, "g", function () awful.spawn("gscan2pdf") end),
910 awful.key({ cmdkey }, "v", function () awful.spawn("virt-manager") end),
911 awful.key({ cmdkey }, "l", function () awful.spawn("libreoffice") end),
912 awful.key({ cmdkey }, "p", function () awful.spawn("pavucontrol") end),
913 awful.key({ cmdkey }, "i", function () awful.spawn(terminal .. " -name irc -e env MOSH_TITLE_NOPREFIX=true mosh --family=all -- irc-host tmux new -As irc irssi") end),
914 awful.key({ cmdkey }, "x", function ()
915     awful.spawn("xautolock -enable")
916     awful.spawn("xautolock -locknow")
917 end),
918 awful.key({ cmdkey, "Shift" }, "x", function () awful.spawn("xautolock -disable") end),
919
920 awful.key({ cmdkey }, "BackSpace", function () awful.spawn("pkill -USR1 offlineimap") end),
921
922 -- function keys
923 awful.key(nil, "XF86ScreenSaver", function () awful.spawn("xset dpms force off") end),
924 awful.key(nil, "XF86AudioMute", function () awful.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle") end),
925 awful.key({ cmdkey }, "End", function () awful.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle") end),
926 awful.key(nil, "XF86AudioLowerVolume", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ -2%") end),
927 awful.key({ cmdkey }, "Next", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ -2%") end),
928 awful.key(nil, "XF86AudioRaiseVolume", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ +2%") end),
929 awful.key({ cmdkey }, "Prior", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ +2%") end),
930 awful.key(nil, "XF86AudioMicMute", function () awful.spawn("pactl set-source-mute @DEFAULT_SOURCE@ toggle") end),
931 awful.key({ cmdkey }, "Home", function () awful.spawn("pactl set-source-mute @DEFAULT_SOURCE@ toggle") end),
932 awful.key({ cmdkey }, "Insert", function () awful.spawn("pa_cycle_default source") end),
933 awful.key({ cmdkey }, "Delete", function () awful.spawn("pa_cycle_default sink") end),
934 awful.key(nil, "XF86MonBrightnessDown", function () awful.spawn("xbacklight -dec 5%") end),
935 awful.key(nil, "XF86MonBrightnessUp", function () awful.spawn("xbacklight -inc 5%") end),
936 awful.key(nil, "XF86Display", function () awful.spawn("autorandr --change --force"); initialise_to_autorandr_profile() end),
937 awful.key(nil, "XF86WLAN", function () awful.spawn("") end),
938 awful.key(nil, "XF86Tools", function () awful.spawn("") end),
939 awful.key(nil, "XF86Search", function () awful.spawn("") end),
940 awful.key(nil, "XF86LaunchA", function () awful.spawn("") end),
941 awful.key(nil, "XF86Explorer", function () awful.spawn("") end),
942 awful.key(nil, "XF86Favorites", function () awful.spawn("systemctl suspend") end),
943
944 awful.key({ cmdkey }, "Multi_key", function () run_output_notify("flameshot gui", "Output") end),
945 awful.key({ cmdkey, "Shift" }, "Multi_key", function () run_output_notify("flameshot full --delay 2000 --clipboard", "Output") end),
946
947 awful.key({ cmdkey }, "Up", function () awful.spawn("pap prev") end),
948 awful.key({ cmdkey }, "Left", function () awful.spawn("pap seek -10") end),
949 awful.key({ cmdkey, "Shift" }, "Left", function () awful.spawn("pap seek -60") end),
950 awful.key({ cmdkey }, "Down", function () awful.spawn("pap next") end),
951 awful.key({ cmdkey }, "Right", function () awful.spawn("pap seek +10") end),
952 awful.key({ cmdkey, "Shift" }, "Right", function () awful.spawn("pap seek +60") end),
953 awful.key({ cmdkey }, "space", function () awful.spawn("pap pause") end),
954 awful.key({ cmdkey }, "\\", function () run_output_notify("pap info", "Track info") end),
955 awful.key({ cmdkey }, "]", function () run_output_notify("pap list", "Playlist") end)
956 )
957
958 function run_output_notify(cmd, title)
959     awful.spawn.easy_async(cmd, function(stdout, stderr, reason, exit_code)
960         if #stdout > 1 then
961             naughty.notify({
962                 preset = naughty.config.presets.low,
963                 title = title,
964                 text = stdout})
965             end
966         end)
967 end
968
969 -- Set keys
970 root.keys(globalkeys)
971 -- }}}
972
973 -- {{{ Rules
974 -- Rules to apply to new clients (through the "manage" signal).
975
976 local function float_client_in_the_middle_with_margins(client, leftright, topbottom)
977     local wa = client.screen.workarea
978     if topbottom then
979         client.y = wa.y + topbottom
980         client.height = wa.height - 2*topbottom
981     else
982         client.y = wa.y + (wa.height - client.height)/2
983     end
984     if leftright then
985         client.x = wa.x + leftright
986         client.width = wa.width - 2*leftright
987     else
988         client.x = wa.x + (wa.width - client.width)/2
989     end
990 end
991
992 local function move_to_tag_by_name(s, tagname)
993     return function(c)
994         local t = awful.tag.find_by_name(s, tagname)
995         if not t then
996             error("No tag by the name of " .. tagname)
997             return
998         end
999         c:move_to_tag(t)
1000     end
1001 end
1002
1003 local function move_to_tag_or_create_volatile(s, tagname)
1004     return function(c)
1005         local t = awful.tag.find_by_name(s, tagname)
1006         if t then
1007             c:move_to_tag(t)
1008         else
1009             th.move_to_new_tag(c, tagname, {}, true, true, true)
1010         end
1011     end
1012 end
1013
1014 awful.rules.rules = {
1015     -- All clients will match this rule.
1016     { rule = { },
1017       properties = { border_width = beautiful.border_width,
1018                      border_color = beautiful.border_normal,
1019                      focus = awful.client.focus.filter,
1020                      raise = true,
1021                      keys = clientkeys,
1022                      buttons = clientbuttons,
1023                      screen = awful.screen.preferred,
1024                      placement = awful.placement.no_overlap+awful.placement.no_offscreen,
1025                      floating = false,
1026                      maximized = false,
1027                  },
1028     },
1029     { rule = { type = "dialog" },
1030       properties = { floating = true,
1031                      ontop = true,
1032                      skip_taskbar = true,
1033                      urgent = true,
1034                      --new_tag = true,
1035                      --switchtotag = true,
1036                      placement = awful.placement.centered
1037                    }
1038     },
1039     { rule = { class = "URxvt" },
1040       properties = { size_hints_honor = false, }
1041     },
1042     { rule = { instance = "irc" },
1043       callback = move_to_tag_by_name(nil, "irc"),
1044     },
1045     { rule = { class = "scrcpy" },
1046       callback = move_to_tag_by_name(nil, "[]"),
1047     },
1048     { rule_any = { class = { "Firefox", "firefox" } },
1049       callback = move_to_tag_by_name(nil, "ffx"),
1050     },
1051     { rule = { class = "Chromium" },
1052       callback = move_to_tag_by_name(nil, "chr"),
1053     },
1054     { rule_any = { class = { "thunderbird", "Thunderbird" } },
1055       callback = move_to_tag_by_name(nil, "cal"),
1056     },
1057     { rule = { instance = "mutt" },
1058       properties = {
1059           new_tag = {
1060               name = "mutt",
1061               layout = awful.layout.suit.fair.horizontal,
1062               volatile = true
1063           },
1064           switchtotag = true,
1065       },
1066     },
1067     { rule_any = { class = { "zoom" } },
1068       callback = move_to_tag_or_create_volatile(nil, "Zoom"),
1069     },
1070     { rule_any = { class = { "Ssvnc.tcl" },
1071                    class = { "Ssvnc" },
1072                    name = { "SSL/SSH VNC Viewer.-" },
1073                  },
1074       callback = move_to_tag_or_create_volatile(nil, "SSVNC"),
1075     },
1076     { rule_any = { class = {
1077         "Gxmessage",
1078         "Pinentry"
1079     }},
1080       properties = { floating = true,
1081                      maximized = false,
1082                      focus = true,
1083                      placement = awful.placement.centered,
1084                    },
1085     },
1086     { rule_any = { instance = {
1087         "tridactyl-edit",
1088         "pdfshuffler",
1089         "vlc",
1090         "pavucontrol"
1091     }},
1092       properties = { floating = true,
1093                      maximized = false,
1094                      focus = true,
1095                      placement = awful.placement.centered,
1096                    },
1097     },
1098     { rule_any = { class = {
1099                         "Gimp",
1100                         "Inkscape",
1101                         "Pitivi",
1102                         "Audacity",
1103                         "Microsoft Teams - Preview",
1104                     },
1105                     instance = {
1106                         "libreoffice",
1107                     }
1108                 },
1109       except_any = { type = { "dialog" } },
1110       properties = { new_tag = {
1111                         layout = layouts.maximised,
1112                         volatile = true,
1113                     },
1114                      --switchtotag = true,
1115                      focus = true,
1116                    },
1117                },
1118     { rule_any = { class = {
1119                         "Gscan2pdf",
1120                     },
1121                 },
1122       except_any = { type = { "dialog" } },
1123       properties = { new_tag = {
1124                         layout = layouts.default,
1125                         volatile = true,
1126                     },
1127                     floating = true,
1128                     maximized = false,
1129                     focus = true,
1130                     placement = awful.placement.centered,
1131                     switchtotag = true,
1132                     focus = true,
1133                    },
1134                },
1135 --XX--    { rule = { class = "Gscan2pdf" },
1136 --XX--               properties = {
1137 --XX--                   switchtotag = true
1138 --XX--               },
1139 --XX--               callback = move_to_tag(1, 5)
1140 --XX--           },
1141 --XX--    { rule = { name = "gscan2pdf .*" },
1142 --XX--               properties = {
1143 --XX--                   floating = false,
1144 --XX--               },
1145 --XX--           },
1146 --XX--    { rule = { class = "Thunar", type = "normal" },
1147 --XX--               properties = {
1148 --XX--                   floating = false,
1149 --XX--               },
1150 --XX--           },
1151 --XX--    { rule = { class = "Pinentry", instance = "pinentry" },
1152 --XX--               properties = {
1153 --XX--                   floating = true,
1154 --XX--               },
1155 --XX--           },
1156 --XX--    { rule = { class = "Gxmessage" },
1157 --XX--               properties = {
1158 --XX--                   floating = true,
1159 --XX--               },
1160 --XX--           },
1161 --XX--}
1162 }
1163 -- }}}
1164
1165 -- {{{ Signals
1166 -- Signal function to execute when a new client appears.
1167 client.connect_signal("manage", function (c)
1168     -- Set the windows at the slave,
1169     -- i.e. put it at the end of others instead of setting it master.
1170     -- if not awesome.startup then awful.client.setslave(c) end
1171     --if not awesome.startup then
1172     --    local t = awful.screen.focused().selected_tag
1173     --    if t.name == "xmutt" then
1174     --        awful.client.setslave(c)
1175     --    end
1176     --end
1177
1178     if awesome.startup and
1179       not c.size_hints.user_position
1180       and not c.size_hints.program_position then
1181         -- Prevent clients from being unreachable after screen count changes.
1182         awful.placement.no_offscreen(c)
1183     end
1184
1185     c.maximized_horizontal = false
1186     c.maximized_vertical = false
1187 end)
1188
1189 -- Add a titlebar if titlebars_enabled is set to true in the rules.
1190 client.connect_signal("request::titlebars", function(c)
1191     -- buttons for the titlebar
1192     local buttons = gears.table.join(
1193         awful.button({ }, 1, function()
1194             client.focus = c
1195             c:raise()
1196             awful.mouse.client.move(c)
1197         end),
1198         awful.button({ }, 3, function()
1199             client.focus = c
1200             c:raise()
1201             awful.mouse.client.resize(c)
1202         end)
1203     )
1204
1205     awful.titlebar(c) : setup {
1206         { -- Left
1207             awful.titlebar.widget.iconwidget(c),
1208             buttons = buttons,
1209             layout  = wibox.layout.fixed.horizontal
1210         },
1211         { -- Middle
1212             { -- Title
1213                 align  = "center",
1214                 widget = awful.titlebar.widget.titlewidget(c)
1215             },
1216             buttons = buttons,
1217             layout  = wibox.layout.flex.horizontal
1218         },
1219         { -- Right
1220             awful.titlebar.widget.floatingbutton (c),
1221             awful.titlebar.widget.maximizedbutton(c),
1222             awful.titlebar.widget.stickybutton   (c),
1223             awful.titlebar.widget.ontopbutton    (c),
1224             awful.titlebar.widget.closebutton    (c),
1225             layout = wibox.layout.fixed.horizontal()
1226         },
1227         layout = wibox.layout.align.horizontal
1228     }
1229 end)
1230
1231 -- Enable sloppy focus, so that focus follows mouse.
1232 client.connect_signal("mouse::enter", function(c)
1233     if awful.layout.get(c.screen) ~= awful.layout.suit.magnifier
1234         and awful.client.focus.filter(c) then
1235         client.focus = c
1236     end
1237 --17 18:03 < psychon> madduck: yes. In the default config at the very end there is code that actives a client on mouse::enter. Just add if c.class == "whatever virt-viewer uses" then return end to that, or 
1238 --                    something like this
1239 end)
1240
1241 client.connect_signal("focus", function(c)
1242     c.border_color = beautiful.border_focus
1243 end)
1244 client.connect_signal("unfocus", function(c)
1245     c.border_color = beautiful.border_normal
1246 end)
1247
1248 awful.ewmh.add_activate_filter(function(c, context, hints)
1249     if context == "ewmh" then
1250         if (c.class == "Firefox-esr" or c.class == "Firefox") then
1251             return false
1252         end
1253     end
1254 end)
1255
1256 client.connect_signal("request::activate", function(c, context, hints)
1257     if gears.table.hasitem({
1258         "client.focus.byidx",
1259         "client.jumpto",
1260         "autofocus.check_focus",
1261         "rules",
1262         "ewmh",
1263     }, context) then
1264         gears.timer.delayed_call(function()
1265             -- we need a delayed call so that we execute *after layout changes
1266             if hints.raise and c == client.focus and client.focus:isvisible() then
1267                 move_mouse_to_area(client.focus)
1268             end
1269         end)
1270     end
1271 end)
1272
1273 -- vim:ft=lua:sw=4:sts=4:ts=4:et