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:

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