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:

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