]> 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:

switch to xautolock
[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 globalkeys = gears.table.join(
611     awful.key({ modkey,           }, "s",      hotkeys_popup.show_help,
612               {description="show help", group="awesome"}),
613     awful.key({ modkey,           }, "Left",   awful.tag.viewprev,
614               {description = "view previous", group = "tag"}),
615     awful.key({ modkey,           }, "Right",  awful.tag.viewnext,
616               {description = "view next", group = "tag"}),
617     awful.key({ modkey, "Shift" }, "Left", function () awful.screen.focus_relative( 1) end,
618               {description = "focus the next screen", group = "screen"}),
619     awful.key({ modkey, "Shift" }, "Right", function () awful.screen.focus_relative(-1) end,
620               {description = "focus the previous screen", group = "screen"}),
621     awful.key({ modkey,           }, "Escape", awful.tag.history.restore,
622               {description = "go back", group = "tag"}),
623
624     awful.key({ modkey,           }, "k",
625         function ()
626             awful.client.focus.byidx( 1)
627         end,
628         {description = "focus next by index", group = "client"}
629     ),
630     awful.key({ modkey,           }, "j",
631         function ()
632             awful.client.focus.byidx(-1)
633         end,
634         {description = "focus previous by index", group = "client"}
635     ),
636
637     -- Layout manipulation
638     awful.key({ modkey, "Shift"   }, "k", function () awful.client.swap.byidx(  1)    end,
639               {description = "swap with next client by index", group = "client"}),
640     awful.key({ modkey, "Shift"   }, "j", function () awful.client.swap.byidx( -1)    end,
641               {description = "swap with previous client by index", group = "client"}),
642     awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative( 1) end,
643               {description = "focus the next screen", group = "screen"}),
644     awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative(-1) end,
645               {description = "focus the previous screen", group = "screen"}),
646     awful.key({ modkey, "Shift"   }, "Return", awful.client.urgent.jumpto,
647               {description = "jump to urgent client", group = "client"}),
648     awful.key({ modkey,           }, "Tab",
649         function ()
650             awful.client.focus.history.previous()
651             if client.focus then
652                 client.focus:raise()
653             end
654         end,
655         {description = "go back", group = "client"}),
656
657     -- Standard program
658     awful.key({ modkey,           }, "Return", function () awful.spawn(terminal) end,
659               {description = "open a terminal", group = "launcher"}),
660     awful.key({ modkey,           }, "r", function()
661         package.loaded.rc = nil
662         require("rc")
663     end,
664               {description = "reload rc.lua", group = "awesome"}),
665     awful.key({ modkey, "Control" }, "r", awesome.restart,
666               {description = "reload awesome", group = "awesome"}),
667     awful.key({ modkey, "Shift"   }, "q", awesome.quit,
668               {description = "quit awesome", group = "awesome"}),
669
670     awful.key({ modkey,           }, "l",     function () awful.tag.incmwfact( 0.05)          end,
671               {description = "increase master width factor", group = "layout"}),
672     awful.key({ modkey,           }, "h",     function () awful.tag.incmwfact(-0.05)          end,
673               {description = "decrease master width factor", group = "layout"}),
674     awful.key({ modkey, "Shift"   }, "h",     function () awful.tag.incnmaster( 1, nil, true) end,
675               {description = "increase the number of master clients", group = "layout"}),
676     awful.key({ modkey, "Shift"   }, "l",     function () awful.tag.incnmaster(-1, nil, true) end,
677               {description = "decrease the number of master clients", group = "layout"}),
678     awful.key({ modkey, "Control" }, "h",     function () awful.tag.incncol( 1, nil, true)    end,
679               {description = "increase the number of columns", group = "layout"}),
680     awful.key({ modkey, "Control" }, "l",     function () awful.tag.incncol(-1, nil, true)    end,
681               {description = "decrease the number of columns", group = "layout"}),
682     awful.key({ modkey,           }, "space", function () awful.layout.inc( 1)                end,
683               {description = "select next", group = "layout"}),
684     awful.key({ modkey, "Shift"   }, "space", function () awful.layout.inc(-1)                end,
685               {description = "select previous", group = "layout"}),
686
687     awful.key({ modkey, "Control" }, "n",
688               function ()
689                   local c = awful.client.restore()
690                   -- Focus restored client
691                   if c then
692                       client.focus = c
693                       c:raise()
694                   end
695               end,
696               {description = "restore minimized", group = "client"}),
697
698     -- Prompt
699     awful.key({ cmdkey },            "r",
700               function ()
701                   local widget = awful.screen.focused().mypromptbox.widget
702                   local function spawn(command, args)
703                       gears.debug.dump(args)
704                       awful.spawn(command, args)
705                   end
706
707                   awful.prompt.run {
708                     prompt       = "Exec: ",
709                     bg_cursor    = '#ff0000',
710                     textbox      = widget,
711                     history_path = awful.util.get_cache_dir() .. "/history",
712                     completion_callback = awful.completion.shell,
713                     hooks = {
714                         -- Replace the 'normal' Return with a custom one
715                         {{         }, 'Return', function(command)
716                             spawn(command)
717                         end},
718                         -- Spawn method to spawn in the current tag
719                         {{'Mod1'   }, 'Return', function(command)
720                             spawn(command,{
721                                 intrusive = true,
722                                 tag       = mouse.screen.selected_tag
723                             })
724                         end},
725                         -- Spawn in the current tag as floating and on top
726                         {{'Shift'  }, 'Return', function(command)
727                             spawn(command,{
728                                 ontop     = true,
729                                 floating  = true,
730                                 tag       = mouse.screen.selected_tag
731                             })
732                         end},
733                         -- Spawn in a new tag
734                         {{'Control'}, 'Return', function(command)
735                             spawn(command,{
736                                 new_tag = true,
737                                 layout = layouts.default,
738                                 volatile = true,
739                             })
740                         end},
741                         -- Cancel
742                         {{         }, 'Escape', function(_) return end},
743                     },
744                 }
745         end,
746               {description = "run prompt", group = "launcher"}),
747
748     awful.key({ modkey }, "x",
749               function ()
750                   awful.prompt.run {
751                     prompt       = "Eval: ",
752                     bg_cursor    = '#ff0000',
753                     textbox      = awful.screen.focused().mypromptbox.widget,
754                     exe_callback = awful.util.eval,
755                     history_path = awful.util.get_cache_dir() .. "/history_eval"
756                   }
757               end,
758               {description = "lua execute prompt", group = "awesome"}),
759     -- Menubar
760     awful.key({ modkey }, "w", function() menubar.show() end,
761               {description = "show the menubar", group = "launcher"}),
762
763     -- Tag helpers
764     awful.key({ modkey,           }, "a", function()
765         th.add_tag(nil, {layout=layouts.default} ,true)
766     end,
767     {description = "add a tag", group = "tag"}),
768     awful.key({ modkey,           }, "d", th.delete_tag,
769               {description = "delete the current tag", group = "tag"}),
770     awful.key({ modkey, "Shift",           }, "a", function()
771         th.move_to_new_tag(nil, nil, { layout = layouts.maximised },true,true,true)
772     end,
773               {description = "add a volatile tag with the focused client", group = "tag"}),
774     awful.key({ modkey, "Shift", "Control" }, "a", function()
775         th.move_to_new_tag(nil, nil, { layout = layouts.maximised },false,true,true)
776     end,
777               {description = "add a permanent tag with the focused client", group = "tag"}),
778     awful.key({ modkey, "Mod1"   }, "a", th.copy_tag,
779               {description = "create a copy of the current tag", group = "tag"}),
780     awful.key({ modkey, "Control"   }, "a", th.rename_tag,
781               {description = "rename the current tag", group = "tag"}),
782     awful.key({ modkey, "Control", "Shift", "Mod1" }, "a", th.collect_orphan_clients_to_tag,
783               {description = "collect all orphaned clients", group = "client"}),
784
785     awful.key({ modkey }, "y", toggle_tag_by_name("irc", true),
786               {description = "view tag 'irc'", group = "tag"}),
787     awful.key({ modkey, "Control" }, "y", toggle_tag_by_name("irc"),
788               {description = "toggle tag 'irc'", group = "tag"}),
789     awful.key({ modkey }, "u", toggle_tag_by_name("[]", true),
790               {description = "view tag '[]'", group = "tag"}),
791     awful.key({ modkey, "Control" }, "u", toggle_tag_by_name("[]"),
792               {description = "toggle tag '[]'", group = "tag"}),
793     awful.key({ modkey }, "i", toggle_tag_by_name("cal", true),
794               {description = "view tag 'cal'", group = "tag"}),
795     awful.key({ modkey, "Control" }, "i", toggle_tag_by_name("cal"),
796               {description = "toggle tag 'cal'", group = "tag"}),
797     awful.key({ modkey }, "o", toggle_tag_by_name("chr", true),
798               {description = "view tag 'chr'", group = "tag"}),
799     awful.key({ modkey, "Control" }, "o", toggle_tag_by_name("chr"),
800               {description = "toggle tag 'chr'", group = "tag"}),
801     awful.key({ modkey }, "p", toggle_tag_by_name("ffx", true),
802               {description = "view tag 'ff'", group = "tag"}),
803     awful.key({ modkey, "Control" }, "p", toggle_tag_by_name("ffx"),
804               {description = "toggle tag 'ff'", group = "tag"}),
805 {})
806
807 clientkeys = gears.table.join(
808     awful.key({ modkey,           }, "f",
809         function (c)
810             c.fullscreen = not c.fullscreen
811             c:raise()
812         end,
813         {description = "toggle fullscreen", group = "client"}),
814     awful.key({ modkey, "Shift"   }, "c",      function (c) c:kill()                         end,
815               {description = "close", group = "client"}),
816     awful.key({ modkey, "Control" }, "space",  awful.client.floating.toggle                     ,
817               {description = "toggle floating", group = "client"}),
818     awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end,
819               {description = "move to master", group = "client"}),
820     awful.key({ modkey,           }, "z",      function (c) c:move_to_screen() end,
821               {description = "move to screen", group = "client"}),
822     awful.key({ modkey,           }, "t",      function (c) c.ontop = not c.ontop            end,
823               {description = "toggle keep on top", group = "client"}),
824     awful.key({ modkey,           }, "n",
825         function (c)
826             -- The client currently has the input focus, so it cannot be
827             -- minimized, since minimized clients can't have the focus.
828             c.minimized = true
829         end ,
830         {description = "minimize", group = "client"}),
831     awful.key({ modkey,           }, "m",
832         function (c)
833             c.maximized = not c.maximized
834             c.maximized_horizontal = false
835             c.maximized_vertical = false
836             c:raise()
837         end ,
838         {description = "(un)maximize", group = "client"}),
839     awful.key({ modkey, "Control" }, "m",
840         function (c)
841             c.maximized_vertical = not c.maximized_vertical
842             c:raise()
843         end ,
844         {description = "(un)maximize vertically", group = "client"}),
845     awful.key({ modkey, "Shift"   }, "m",
846         function (c)
847             c.maximized_horizontal = not c.maximized_horizontal
848             c:raise()
849         end ,
850         {description = "(un)maximize horizontally", group = "client"})
851 )
852
853 -- Bind all key numbers to tags.
854 -- Be careful: we use keycodes to make it work on any keyboard layout.
855 -- This should map on the top row of your keyboard, usually 1 to 9.
856 for i = 1, 9 do
857     globalkeys = gears.table.join(globalkeys,
858         -- View tag only.
859         awful.key({ modkey }, "#" .. i + 9, toggle_tag_by_name(tostring(i), true),
860                   {description = "view tag #"..i, group = "tag"}),
861         -- Toggle tag display.
862         awful.key({ modkey, "Control" }, "#" .. i + 9, toggle_tag_by_name(tostring(i)),
863                   {description = "toggle tag #" .. i, group = "tag"}),
864         -- Move client to tag.
865         awful.key({ modkey, "Shift" }, "#" .. i + 9,
866                   function ()
867                       if client.focus then
868                           local tag = awful.tag.find_by_name(screen.primary, tostring(i))
869                           if tag then
870                               client.focus:move_to_tag(tag)
871                           end
872                      end
873                   end,
874                   {description = "move focused client to tag #"..i, group = "tag"}),
875         -- Toggle tag on focused client.
876         awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9,
877                   function ()
878                       if client.focus then
879                           local tag = awful.tag.find_by_name(screen.primary, tostring(i))
880                           if tag then
881                               client.focus:toggle_tag(tag)
882                           end
883                       end
884                   end,
885                   {description = "toggle focused client on tag #" .. i, group = "tag"})
886     )
887 end
888
889 clientbuttons = gears.table.join(
890     awful.button({ }, 1, function (c) client.focus = c; c:raise() end),
891     awful.button({ modkey }, 1, awful.mouse.client.move),
892     awful.button({ modkey }, 3, awful.mouse.client.resize))
893
894 -- misc apps
895 globalkeys = awful.util.table.join(globalkeys,
896 awful.key({ cmdkey }, "n", function () awful.spawn("firefox") end),
897 awful.key({ cmdkey }, "c", function () awful.spawn("chromium --enable-remote-extensions") end),
898 awful.key({ cmdkey }, "y", function () awful.spawn(terminal .. " -e ipython3") end),
899 awful.key({ cmdkey }, "m", function () awful.spawn(terminal .. " -name mutt -e mutt") end),
900 awful.key({ cmdkey }, "t", function () awful.spawn("thunderbird") end),
901 awful.key({ cmdkey }, "g", function () awful.spawn("gscan2pdf") end),
902 awful.key({ cmdkey }, "v", function () awful.spawn("virt-manager") end),
903 awful.key({ cmdkey }, "l", function () awful.spawn("libreoffice") end),
904 awful.key({ cmdkey }, "p", function () awful.spawn("pavucontrol") end),
905 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),
906 awful.key({ cmdkey }, "x", function ()
907     awful.spawn("xautolock -enable")
908     awful.spawn("xautolock -locknow")
909 end),
910 awful.key({ cmdkey, "Shift" }, "x", function () awful.spawn("xautolock -disable") end),
911
912 awful.key({ cmdkey }, "BackSpace", function () awful.spawn("pkill -USR1 offlineimap") end),
913
914 -- function keys
915 awful.key(nil, "XF86ScreenSaver", function () awful.spawn("xset dpms force off") end),
916 awful.key(nil, "XF86AudioMute", function () awful.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle") end),
917 awful.key({ cmdkey }, "End", function () awful.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle") end),
918 awful.key(nil, "XF86AudioLowerVolume", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ -2%") end),
919 awful.key({ cmdkey }, "Next", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ -2%") end),
920 awful.key(nil, "XF86AudioRaiseVolume", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ +2%") end),
921 awful.key({ cmdkey }, "Prior", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ +2%") end),
922 awful.key(nil, "XF86AudioMicMute", function () awful.spawn("pactl set-source-mute @DEFAULT_SOURCE@ toggle") end),
923 awful.key({ cmdkey }, "Home", function () awful.spawn("pactl set-source-mute @DEFAULT_SOURCE@ toggle") end),
924 awful.key({ cmdkey }, "Insert", function () awful.spawn("pa_cycle_default source") end),
925 awful.key({ cmdkey }, "Delete", function () awful.spawn("pa_cycle_default sink") end),
926 awful.key(nil, "XF86MonBrightnessDown", function () awful.spawn("xbacklight -dec 5%") end),
927 awful.key(nil, "XF86MonBrightnessUp", function () awful.spawn("xbacklight -inc 5%") end),
928 awful.key(nil, "XF86Display", function () awful.spawn("autorandr --change --force"); initialise_to_autorandr_profile() end),
929 awful.key(nil, "XF86WLAN", function () awful.spawn("") end),
930 awful.key(nil, "XF86Tools", function () awful.spawn("") end),
931 awful.key(nil, "XF86Search", function () awful.spawn("") end),
932 awful.key(nil, "XF86LaunchA", function () awful.spawn("") end),
933 awful.key(nil, "XF86Explorer", function () awful.spawn("") end),
934 awful.key(nil, "XF86Favorites", function () awful.spawn("systemctl suspend") end),
935
936 awful.key({ cmdkey }, "Multi_key", function () run_output_notify("flameshot gui", "Output") end),
937 awful.key({ cmdkey, "Shift" }, "Multi_key", function () run_output_notify("flameshot full --delay 2000 --clipboard", "Output") end),
938
939 awful.key({ cmdkey }, "Up", function () awful.spawn("pap prev") end),
940 awful.key({ cmdkey }, "Left", function () awful.spawn("pap seek -10") end),
941 awful.key({ cmdkey, "Shift" }, "Left", function () awful.spawn("pap seek -60") end),
942 awful.key({ cmdkey }, "Down", function () awful.spawn("pap next") end),
943 awful.key({ cmdkey }, "Right", function () awful.spawn("pap seek +10") end),
944 awful.key({ cmdkey, "Shift" }, "Right", function () awful.spawn("pap seek +60") end),
945 awful.key({ cmdkey }, "space", function () awful.spawn("pap pause") end),
946 awful.key({ cmdkey }, "\\", function () run_output_notify("pap info", "Track info") end),
947 awful.key({ cmdkey }, "]", function () run_output_notify("pap list", "Playlist") end)
948 )
949
950 function run_output_notify(cmd, title)
951     awful.spawn.easy_async(cmd, function(stdout, stderr, reason, exit_code)
952         if #stdout > 1 then
953             naughty.notify({
954                 preset = naughty.config.presets.low,
955                 title = title,
956                 text = stdout})
957             end
958         end)
959 end
960
961 -- Set keys
962 root.keys(globalkeys)
963 -- }}}
964
965 -- {{{ Rules
966 -- Rules to apply to new clients (through the "manage" signal).
967
968 local function float_client_in_the_middle_with_margins(client, leftright, topbottom)
969     local wa = client.screen.workarea
970     if topbottom then
971         client.y = wa.y + topbottom
972         client.height = wa.height - 2*topbottom
973     else
974         client.y = wa.y + (wa.height - client.height)/2
975     end
976     if leftright then
977         client.x = wa.x + leftright
978         client.width = wa.width - 2*leftright
979     else
980         client.x = wa.x + (wa.width - client.width)/2
981     end
982 end
983
984 local function move_to_tag_by_name(s, tagname)
985     return function(c)
986         local t = awful.tag.find_by_name(s, tagname)
987         if not t then
988             error("No tag by the name of " .. tagname)
989             return
990         end
991         c:move_to_tag(t)
992     end
993 end
994
995 local function move_to_tag_or_create_volatile(s, tagname)
996     return function(c)
997         local t = awful.tag.find_by_name(s, tagname)
998         if t then
999             c:move_to_tag(t)
1000         else
1001             th.move_to_new_tag(c, tagname, {}, true, true, true)
1002         end
1003     end
1004 end
1005
1006 awful.rules.rules = {
1007     -- All clients will match this rule.
1008     { rule = { },
1009       properties = { border_width = beautiful.border_width,
1010                      border_color = beautiful.border_normal,
1011                      focus = awful.client.focus.filter,
1012                      raise = true,
1013                      keys = clientkeys,
1014                      buttons = clientbuttons,
1015                      screen = awful.screen.preferred,
1016                      placement = awful.placement.no_overlap+awful.placement.no_offscreen,
1017                      floating = false,
1018                      maximized = false,
1019                  },
1020     },
1021     { rule = { type = "dialog" },
1022       properties = { floating = true,
1023                      ontop = true,
1024                      skip_taskbar = true,
1025                      urgent = true,
1026                      --new_tag = true,
1027                      --switchtotag = true,
1028                      placement = awful.placement.centered
1029                    }
1030     },
1031     { rule = { class = "URxvt" },
1032       properties = { size_hints_honor = false, }
1033     },
1034     { rule = { instance = "irc" },
1035       callback = move_to_tag_by_name(nil, "irc"),
1036     },
1037     { rule = { class = "scrcpy" },
1038       callback = move_to_tag_by_name(nil, "[]"),
1039     },
1040     { rule_any = { class = { "Firefox", "firefox" } },
1041       callback = move_to_tag_by_name(nil, "ffx"),
1042     },
1043     { rule = { class = "Chromium" },
1044       callback = move_to_tag_by_name(nil, "chr"),
1045     },
1046     { rule_any = { class = { "thunderbird", "Thunderbird" } },
1047       callback = move_to_tag_by_name(nil, "cal"),
1048     },
1049     { rule = { instance = "mutt" },
1050       properties = {
1051           new_tag = {
1052               name = "mutt",
1053               layout = awful.layout.suit.fair.horizontal,
1054               volatile = true
1055           },
1056           switchtotag = true,
1057       },
1058     },
1059     { rule_any = { class = { "zoom" } },
1060       callback = move_to_tag_or_create_volatile(nil, "Zoom"),
1061     },
1062     { rule_any = { class = { "Ssvnc.tcl" },
1063                    class = { "Ssvnc" },
1064                    name = { "SSL/SSH VNC Viewer.-" },
1065                  },
1066       callback = move_to_tag_or_create_volatile(nil, "SSVNC"),
1067     },
1068     { rule_any = { class = {
1069         "Gxmessage",
1070         "Pinentry"
1071     }},
1072       properties = { floating = true,
1073                      maximized = false,
1074                      focus = true,
1075                      placement = awful.placement.centered,
1076                    },
1077     },
1078     { rule_any = { instance = {
1079         "tridactyl-edit",
1080         "pdfshuffler",
1081         "vlc",
1082         "pavucontrol"
1083     }},
1084       properties = { floating = true,
1085                      maximized = false,
1086                      focus = true,
1087                      placement = awful.placement.centered,
1088                    },
1089     },
1090     { rule_any = { class = {
1091                         "Gimp",
1092                         "Inkscape",
1093                         "Pitivi",
1094                         "Audacity",
1095                         "Microsoft Teams - Preview",
1096                     },
1097                     instance = {
1098                         "libreoffice",
1099                     }
1100                 },
1101       except_any = { type = { "dialog" } },
1102       properties = { new_tag = {
1103                         layout = layouts.maximised,
1104                         volatile = true,
1105                     },
1106                      --switchtotag = true,
1107                      focus = true,
1108                    },
1109                },
1110     { rule_any = { class = {
1111                         "Gscan2pdf",
1112                     },
1113                 },
1114       except_any = { type = { "dialog" } },
1115       properties = { new_tag = {
1116                         layout = layouts.default,
1117                         volatile = true,
1118                     },
1119                     floating = true,
1120                     maximized = false,
1121                     focus = true,
1122                     placement = awful.placement.centered,
1123                     switchtotag = true,
1124                     focus = true,
1125                    },
1126                },
1127 --XX--    { rule = { class = "Gscan2pdf" },
1128 --XX--               properties = {
1129 --XX--                   switchtotag = true
1130 --XX--               },
1131 --XX--               callback = move_to_tag(1, 5)
1132 --XX--           },
1133 --XX--    { rule = { name = "gscan2pdf .*" },
1134 --XX--               properties = {
1135 --XX--                   floating = false,
1136 --XX--               },
1137 --XX--           },
1138 --XX--    { rule = { class = "Thunar", type = "normal" },
1139 --XX--               properties = {
1140 --XX--                   floating = false,
1141 --XX--               },
1142 --XX--           },
1143 --XX--    { rule = { class = "Pinentry", instance = "pinentry" },
1144 --XX--               properties = {
1145 --XX--                   floating = true,
1146 --XX--               },
1147 --XX--           },
1148 --XX--    { rule = { class = "Gxmessage" },
1149 --XX--               properties = {
1150 --XX--                   floating = true,
1151 --XX--               },
1152 --XX--           },
1153 --XX--}
1154 }
1155 -- }}}
1156
1157 -- {{{ Signals
1158 -- Signal function to execute when a new client appears.
1159 client.connect_signal("manage", function (c)
1160     -- Set the windows at the slave,
1161     -- i.e. put it at the end of others instead of setting it master.
1162     -- if not awesome.startup then awful.client.setslave(c) end
1163     --if not awesome.startup then
1164     --    local t = awful.screen.focused().selected_tag
1165     --    if t.name == "xmutt" then
1166     --        awful.client.setslave(c)
1167     --    end
1168     --end
1169
1170     if awesome.startup and
1171       not c.size_hints.user_position
1172       and not c.size_hints.program_position then
1173         -- Prevent clients from being unreachable after screen count changes.
1174         awful.placement.no_offscreen(c)
1175     end
1176
1177     c.maximized_horizontal = false
1178     c.maximized_vertical = false
1179 end)
1180
1181 -- Add a titlebar if titlebars_enabled is set to true in the rules.
1182 client.connect_signal("request::titlebars", function(c)
1183     -- buttons for the titlebar
1184     local buttons = gears.table.join(
1185         awful.button({ }, 1, function()
1186             client.focus = c
1187             c:raise()
1188             awful.mouse.client.move(c)
1189         end),
1190         awful.button({ }, 3, function()
1191             client.focus = c
1192             c:raise()
1193             awful.mouse.client.resize(c)
1194         end)
1195     )
1196
1197     awful.titlebar(c) : setup {
1198         { -- Left
1199             awful.titlebar.widget.iconwidget(c),
1200             buttons = buttons,
1201             layout  = wibox.layout.fixed.horizontal
1202         },
1203         { -- Middle
1204             { -- Title
1205                 align  = "center",
1206                 widget = awful.titlebar.widget.titlewidget(c)
1207             },
1208             buttons = buttons,
1209             layout  = wibox.layout.flex.horizontal
1210         },
1211         { -- Right
1212             awful.titlebar.widget.floatingbutton (c),
1213             awful.titlebar.widget.maximizedbutton(c),
1214             awful.titlebar.widget.stickybutton   (c),
1215             awful.titlebar.widget.ontopbutton    (c),
1216             awful.titlebar.widget.closebutton    (c),
1217             layout = wibox.layout.fixed.horizontal()
1218         },
1219         layout = wibox.layout.align.horizontal
1220     }
1221 end)
1222
1223 -- Enable sloppy focus, so that focus follows mouse.
1224 client.connect_signal("mouse::enter", function(c)
1225     if awful.layout.get(c.screen) ~= awful.layout.suit.magnifier
1226         and awful.client.focus.filter(c) then
1227         client.focus = c
1228     end
1229 --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 
1230 --                    something like this
1231 end)
1232
1233 client.connect_signal("focus", function(c)
1234     c.border_color = beautiful.border_focus
1235 end)
1236 client.connect_signal("unfocus", function(c)
1237     c.border_color = beautiful.border_normal
1238 end)
1239
1240 awful.ewmh.add_activate_filter(function(c, context, hints)
1241     if context == "ewmh" then
1242         if (c.class == "Firefox-esr" or c.class == "Firefox") then
1243             return false
1244         end
1245     end
1246 end)
1247
1248 client.connect_signal("request::activate", function(c, context, hints)
1249     if gears.table.hasitem({
1250         "client.focus.byidx",
1251         "client.jumpto",
1252         "autofocus.check_focus",
1253         "rules",
1254         "ewmh",
1255     }, context) then
1256         gears.timer.delayed_call(function()
1257             -- we need a delayed call so that we execute *after layout changes
1258             if hints.raise and c == client.focus and client.focus:isvisible() then
1259                 move_mouse_to_area(client.focus)
1260             end
1261         end)
1262     end
1263 end)
1264
1265 -- vim:ft=lua:sw=4:sts=4:ts=4:et