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:

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