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:

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