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:

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