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:

better handling for gscan2pdf
[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("/usr/bin/xscreensaver -no-capture-stderr")
908     os.execute("sleep 5")
909     awful.spawn("xscreensaver-command -lock")
910 end),
911 awful.key({ cmdkey, "Shift" }, "x", function () awful.spawn("xscreensaver-command -exit") end),
912
913 awful.key({ cmdkey }, "BackSpace", function () awful.spawn("pkill -USR1 offlineimap") end),
914
915 -- function keys
916 awful.key(nil, "XF86ScreenSaver", function () awful.spawn("xset dpms force off") end),
917 awful.key(nil, "XF86AudioMute", function () awful.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle") end),
918 awful.key({ cmdkey }, "End", function () awful.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle") end),
919 awful.key(nil, "XF86AudioLowerVolume", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ -2%") end),
920 awful.key({ cmdkey }, "Next", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ -2%") end),
921 awful.key(nil, "XF86AudioRaiseVolume", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ +2%") end),
922 awful.key({ cmdkey }, "Prior", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ +2%") end),
923 awful.key(nil, "XF86AudioMicMute", function () awful.spawn("pactl set-source-mute @DEFAULT_SOURCE@ toggle") end),
924 awful.key({ cmdkey }, "Home", function () awful.spawn("pactl set-source-mute @DEFAULT_SOURCE@ toggle") end),
925 awful.key({ cmdkey }, "Insert", function () awful.spawn("pa_cycle_default source") end),
926 awful.key({ cmdkey }, "Delete", function () awful.spawn("pa_cycle_default sink") end),
927 awful.key(nil, "XF86MonBrightnessDown", function () awful.spawn("xbacklight -dec 5%") end),
928 awful.key(nil, "XF86MonBrightnessUp", function () awful.spawn("xbacklight -inc 5%") end),
929 awful.key(nil, "XF86Display", function () awful.spawn("autorandr --change --force"); initialise_to_autorandr_profile() end),
930 awful.key(nil, "XF86WLAN", function () awful.spawn("") end),
931 awful.key(nil, "XF86Tools", function () awful.spawn("") end),
932 awful.key(nil, "XF86Search", function () awful.spawn("") end),
933 awful.key(nil, "XF86LaunchA", function () awful.spawn("") end),
934 awful.key(nil, "XF86Explorer", function () awful.spawn("") end),
935 awful.key(nil, "XF86Favorites", function () awful.spawn("systemctl suspend") end),
936
937 awful.key({ cmdkey }, "Multi_key", function () run_output_notify("flameshot gui", "Output") end),
938 awful.key({ cmdkey, "Shift" }, "Multi_key", function () run_output_notify("flameshot full --delay 2000 --clipboard", "Output") end),
939
940 awful.key({ cmdkey }, "Up", function () awful.spawn("pap prev") end),
941 awful.key({ cmdkey }, "Left", function () awful.spawn("pap seek -10") end),
942 awful.key({ cmdkey, "Shift" }, "Left", function () awful.spawn("pap seek -60") end),
943 awful.key({ cmdkey }, "Down", function () awful.spawn("pap next") end),
944 awful.key({ cmdkey }, "Right", function () awful.spawn("pap seek +10") end),
945 awful.key({ cmdkey, "Shift" }, "Right", function () awful.spawn("pap seek +60") end),
946 awful.key({ cmdkey }, "space", function () awful.spawn("pap pause") end),
947 awful.key({ cmdkey }, "\\", function () run_output_notify("pap info", "Track info") end),
948 awful.key({ cmdkey }, "]", function () run_output_notify("pap list", "Playlist") end)
949 )
950
951 function run_output_notify(cmd, title)
952     awful.spawn.easy_async(cmd, function(stdout, stderr, reason, exit_code)
953         if #stdout > 1 then
954             naughty.notify({
955                 preset = naughty.config.presets.low,
956                 title = title,
957                 text = stdout})
958             end
959         end)
960 end
961
962 -- Set keys
963 root.keys(globalkeys)
964 -- }}}
965
966 -- {{{ Rules
967 -- Rules to apply to new clients (through the "manage" signal).
968
969 local function float_client_in_the_middle_with_margins(client, leftright, topbottom)
970     local wa = client.screen.workarea
971     if topbottom then
972         client.y = wa.y + topbottom
973         client.height = wa.height - 2*topbottom
974     else
975         client.y = wa.y + (wa.height - client.height)/2
976     end
977     if leftright then
978         client.x = wa.x + leftright
979         client.width = wa.width - 2*leftright
980     else
981         client.x = wa.x + (wa.width - client.width)/2
982     end
983 end
984
985 local function move_to_tag_by_name(s, tagname)
986     return function(c)
987         local t = awful.tag.find_by_name(s, tagname)
988         if not t then
989             error("No tag by the name of " .. tagname)
990             return
991         end
992         c:move_to_tag(t)
993     end
994 end
995
996 local function move_to_tag_or_create_volatile(s, tagname)
997     return function(c)
998         local t = awful.tag.find_by_name(s, tagname)
999         if t then
1000             c:move_to_tag(t)
1001         else
1002             th.move_to_new_tag(c, tagname, {}, true, true, true)
1003         end
1004     end
1005 end
1006
1007 awful.rules.rules = {
1008     -- All clients will match this rule.
1009     { rule = { },
1010       properties = { border_width = beautiful.border_width,
1011                      border_color = beautiful.border_normal,
1012                      focus = awful.client.focus.filter,
1013                      raise = true,
1014                      keys = clientkeys,
1015                      buttons = clientbuttons,
1016                      screen = awful.screen.preferred,
1017                      placement = awful.placement.no_overlap+awful.placement.no_offscreen,
1018                      floating = false,
1019                      maximized = false,
1020                  },
1021     },
1022     { rule = { type = "dialog" },
1023       properties = { floating = true,
1024                      ontop = true,
1025                      skip_taskbar = true,
1026                      urgent = true,
1027                      --new_tag = true,
1028                      --switchtotag = true,
1029                      placement = awful.placement.centered
1030                    }
1031     },
1032     { rule = { class = "URxvt" },
1033       properties = { size_hints_honor = false, }
1034     },
1035     { rule = { instance = "irc" },
1036       callback = move_to_tag_by_name(nil, "irc"),
1037     },
1038     { rule = { class = "scrcpy" },
1039       callback = move_to_tag_by_name(nil, "[]"),
1040     },
1041     { rule_any = { class = { "Firefox", "firefox" } },
1042       callback = move_to_tag_by_name(nil, "ffx"),
1043     },
1044     { rule = { class = "Chromium" },
1045       callback = move_to_tag_by_name(nil, "chr"),
1046     },
1047     { rule_any = { class = { "thunderbird", "Thunderbird" } },
1048       callback = move_to_tag_by_name(nil, "cal"),
1049     },
1050     { rule = { instance = "mutt" },
1051       properties = {
1052           new_tag = {
1053               name = "mutt",
1054               layout = awful.layout.suit.fair.horizontal,
1055               volatile = true
1056           },
1057           switchtotag = true,
1058       },
1059     },
1060     { rule_any = { class = { "zoom" } },
1061       callback = move_to_tag_or_create_volatile(nil, "Zoom"),
1062     },
1063     { rule_any = { class = { "Ssvnc.tcl" },
1064                    class = { "Ssvnc" },
1065                    name = { "SSL/SSH VNC Viewer.-" },
1066                  },
1067       callback = move_to_tag_or_create_volatile(nil, "SSVNC"),
1068     },
1069     { rule_any = { class = {
1070         "Gxmessage",
1071         "Pinentry"
1072     }},
1073       properties = { floating = true,
1074                      maximized = false,
1075                      focus = true,
1076                      placement = awful.placement.centered,
1077                    },
1078     },
1079     { rule_any = { instance = {
1080         "tridactyl-edit",
1081         "pdfshuffler",
1082         "vlc",
1083         "pavucontrol"
1084     }},
1085       properties = { floating = true,
1086                      maximized = false,
1087                      focus = true,
1088                      placement = awful.placement.centered,
1089                    },
1090     },
1091     { rule_any = { class = {
1092                         "Gimp",
1093                         "Inkscape",
1094                         "Pitivi",
1095                         "Audacity",
1096                         "Microsoft Teams - Preview",
1097                     },
1098                     instance = {
1099                         "libreoffice",
1100                     }
1101                 },
1102       except_any = { type = { "dialog" } },
1103       properties = { new_tag = {
1104                         layout = layouts.maximised,
1105                         volatile = true,
1106                     },
1107                      --switchtotag = true,
1108                      focus = true,
1109                    },
1110                },
1111     { rule_any = { class = {
1112                         "Gscan2pdf",
1113                     },
1114                 },
1115       except_any = { type = { "dialog" } },
1116       properties = { new_tag = {
1117                         layout = layouts.default,
1118                         volatile = true,
1119                     },
1120                     floating = true,
1121                     maximized = false,
1122                     focus = true,
1123                     placement = awful.placement.centered,
1124                     switchtotag = true,
1125                     focus = true,
1126                    },
1127                },
1128 --XX--    { rule = { class = "Gscan2pdf" },
1129 --XX--               properties = {
1130 --XX--                   switchtotag = true
1131 --XX--               },
1132 --XX--               callback = move_to_tag(1, 5)
1133 --XX--           },
1134 --XX--    { rule = { name = "gscan2pdf .*" },
1135 --XX--               properties = {
1136 --XX--                   floating = false,
1137 --XX--               },
1138 --XX--           },
1139 --XX--    { rule = { class = "Thunar", type = "normal" },
1140 --XX--               properties = {
1141 --XX--                   floating = false,
1142 --XX--               },
1143 --XX--           },
1144 --XX--    { rule = { class = "Pinentry", instance = "pinentry" },
1145 --XX--               properties = {
1146 --XX--                   floating = true,
1147 --XX--               },
1148 --XX--           },
1149 --XX--    { rule = { class = "Gxmessage" },
1150 --XX--               properties = {
1151 --XX--                   floating = true,
1152 --XX--               },
1153 --XX--           },
1154 --XX--}
1155 }
1156 -- }}}
1157
1158 -- {{{ Signals
1159 -- Signal function to execute when a new client appears.
1160 client.connect_signal("manage", function (c)
1161     -- Set the windows at the slave,
1162     -- i.e. put it at the end of others instead of setting it master.
1163     -- if not awesome.startup then awful.client.setslave(c) end
1164     --if not awesome.startup then
1165     --    local t = awful.screen.focused().selected_tag
1166     --    if t.name == "xmutt" then
1167     --        awful.client.setslave(c)
1168     --    end
1169     --end
1170
1171     if awesome.startup and
1172       not c.size_hints.user_position
1173       and not c.size_hints.program_position then
1174         -- Prevent clients from being unreachable after screen count changes.
1175         awful.placement.no_offscreen(c)
1176     end
1177
1178     c.maximized_horizontal = false
1179     c.maximized_vertical = false
1180 end)
1181
1182 -- Add a titlebar if titlebars_enabled is set to true in the rules.
1183 client.connect_signal("request::titlebars", function(c)
1184     -- buttons for the titlebar
1185     local buttons = gears.table.join(
1186         awful.button({ }, 1, function()
1187             client.focus = c
1188             c:raise()
1189             awful.mouse.client.move(c)
1190         end),
1191         awful.button({ }, 3, function()
1192             client.focus = c
1193             c:raise()
1194             awful.mouse.client.resize(c)
1195         end)
1196     )
1197
1198     awful.titlebar(c) : setup {
1199         { -- Left
1200             awful.titlebar.widget.iconwidget(c),
1201             buttons = buttons,
1202             layout  = wibox.layout.fixed.horizontal
1203         },
1204         { -- Middle
1205             { -- Title
1206                 align  = "center",
1207                 widget = awful.titlebar.widget.titlewidget(c)
1208             },
1209             buttons = buttons,
1210             layout  = wibox.layout.flex.horizontal
1211         },
1212         { -- Right
1213             awful.titlebar.widget.floatingbutton (c),
1214             awful.titlebar.widget.maximizedbutton(c),
1215             awful.titlebar.widget.stickybutton   (c),
1216             awful.titlebar.widget.ontopbutton    (c),
1217             awful.titlebar.widget.closebutton    (c),
1218             layout = wibox.layout.fixed.horizontal()
1219         },
1220         layout = wibox.layout.align.horizontal
1221     }
1222 end)
1223
1224 -- Enable sloppy focus, so that focus follows mouse.
1225 client.connect_signal("mouse::enter", function(c)
1226     if awful.layout.get(c.screen) ~= awful.layout.suit.magnifier
1227         and awful.client.focus.filter(c) then
1228         client.focus = c
1229     end
1230 --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 
1231 --                    something like this
1232 end)
1233
1234 client.connect_signal("focus", function(c)
1235     c.border_color = beautiful.border_focus
1236 end)
1237 client.connect_signal("unfocus", function(c)
1238     c.border_color = beautiful.border_normal
1239 end)
1240
1241 awful.ewmh.add_activate_filter(function(c, context, hints)
1242     if context == "ewmh" then
1243         if (c.class == "Firefox-esr" or c.class == "Firefox") then
1244             return false
1245         end
1246     end
1247 end)
1248
1249 client.connect_signal("request::activate", function(c, context, hints)
1250     if gears.table.hasitem({
1251         "client.focus.byidx",
1252         "client.jumpto",
1253         "autofocus.check_focus",
1254         "rules",
1255         "ewmh",
1256     }, context) then
1257         gears.timer.delayed_call(function()
1258             -- we need a delayed call so that we execute *after layout changes
1259             if hints.raise and c == client.focus and client.focus:isvisible() then
1260                 move_mouse_to_area(client.focus)
1261             end
1262         end)
1263     end
1264 end)
1265
1266 -- vim:ft=lua:sw=4:sts=4:ts=4:et