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

madduck's git repository

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

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

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

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

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

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