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:

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