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:

add lehel layout for tags
[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 function make_spacer(text)
177     local spacer = wibox.widget.textbox()
178     spacer:set_text(text or " │ ")
179     return spacer
180 end
181
182 -- Keyboard map indicator and switcher
183 mykeyboardlayout = awful.widget.keyboardlayout()
184
185 local lain_bat = lain.widget.bat({
186     batteries = {"BAT0", "BAT1"},
187     settings = function()
188         local delim = "↓"
189         if bat_now.status == "Charging" then delim = "↑"
190         elseif bat_now.status == "Unknown" then delim = "٭" end
191         widget:set_text(bat_now.perc .. "% " .. delim .. " " .. bat_now.time)
192     end,
193 })
194
195 -- Create a textclock widget
196 clocksarray = clocksarray.get_clocksarray("%a %d %b %H:%M:%S %Z", {
197         ["NZ"] = "Pacific/Auckland",
198         ["DE"] = "Europe/Berlin"
199     }, make_spacer())
200
201 -- Create a wibox for each screen and add it
202 local taglist_buttons = gears.table.join(
203                     awful.button({ }, 1, function(t) t:view_only() end),
204                     awful.button({ modkey }, 1, function(t)
205                                               if client.focus then
206                                                   client.focus:move_to_tag(t)
207                                               end
208                                           end),
209                     awful.button({ }, 3, awful.tag.viewtoggle),
210                     awful.button({ modkey }, 3, function(t)
211                                               if client.focus then
212                                                   client.focus:toggle_tag(t)
213                                               end
214                                           end),
215                     awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end),
216                     awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end)
217                 )
218
219 local tasklist_buttons = gears.table.join(
220                      awful.button({ }, 1, function (c)
221                                               if c == client.focus then
222                                                   -- I don't like click-minimising
223                                                   -- c.minimized = true
224                                               else
225                                                   -- Without this, the following
226                                                   -- :isvisible() makes no sense
227                                                   c.minimized = false
228                                                   if not c:isvisible() and c.first_tag then
229                                                       c.first_tag:view_only()
230                                                   end
231                                                   -- This will also un-minimize
232                                                   -- the client, if needed
233                                                   client.focus = c
234                                                   c:raise()
235                                               end
236                                           end),
237                      awful.button({ }, 3, client_menu_toggle_fn()),
238                      awful.button({ }, 4, function ()
239                                               awful.client.focus.byidx(1)
240                                           end),
241                      awful.button({ }, 5, function ()
242                                               awful.client.focus.byidx(-1)
243                                           end))
244 -- }}}
245
246 -- {{{ Screens
247
248 -- Re-set wallpaper when a screen's geometry changes (e.g. different resolution)
249 screen.connect_signal("property::geometry", set_wallpaper)
250
251 -- {{{ Basic setup for screens
252 local function screen_set_profile(s, profile)
253     s.profile = profile
254     s.outputstr = table.concat(gears.table.keys(s.outputs), "+")
255     s.name = s.profile .. "/" .. s.outputstr
256 end
257
258 awful.screen.connect_for_each_screen(function(s)
259
260     s.set_profile = screen_set_profile
261
262     -- Wallpaper
263     set_wallpaper(s)
264
265     -- Create a text widget to display screen name
266     s.namebox = wibox.container.background(wibox.widget.textbox(s.name),
267       beautiful.bg_minimize)
268
269     -- Create a promptbox for each screen
270     s.mypromptbox = awful.widget.prompt()
271     -- Create an imagebox widget which will contains an icon indicating which layout we're using.
272     -- We need one layoutbox per screen.
273     s.mylayoutbox = awful.widget.layoutbox(s)
274     s.mylayoutbox:buttons(awful.util.table.join(
275                            awful.button({ }, 1, function () awful.layout.inc( 1) end),
276                            awful.button({ }, 3, function () awful.layout.inc(-1) end),
277                            awful.button({ }, 4, function () awful.layout.inc( 1) end),
278                            awful.button({ }, 5, function () awful.layout.inc(-1) end)))
279     -- Create a taglist widget
280     s.mytaglist = awful.widget.taglist(s, awful.widget.taglist.filter.all, taglist_buttons)
281
282     -- Create a tasklist widget
283     s.mytasklist = awful.widget.tasklist(s, awful.widget.tasklist.filter.currenttags, tasklist_buttons)
284
285     -- Create the wibox, but only if there isn't one yet
286     if not s.mywibox then
287         s.mywibox = awful.wibar({ position = "top", screen = s })
288     end
289
290     -- Add widgets to the wibox
291     local right_widgets = gears.table.join(clocksarray, {
292         make_spacer(" "),
293         wibox.widget.systray(),
294         s.mylayoutbox,
295         layout = wibox.layout.fixed.horizontal,
296     })
297
298     if s == screen.primary then
299         right_widgets = gears.table.join({
300             make_spacer(" "),
301             ccwidgets.btc_widget,
302             make_spacer(),
303             ccwidgets.eth_widget,
304             make_spacer(),
305             lain_bat.widget,
306             make_spacer(),
307         }, right_widgets)
308     end
309
310     s.mywibox:setup {
311         layout = wibox.layout.align.horizontal,
312         { -- Left widgets
313             layout = wibox.layout.fixed.horizontal,
314             --s.namebox,
315             s.mytaglist,
316             make_spacer(" "),
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", "lehel/DisplayPort-2" },
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", "lehel/DisplayPort-2" },
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", "lehel/DisplayPort-1" },
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", "lehel/DisplayPort-1" },
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", "lehel/DisplayPort-1" },
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                     completion_callback = awful.completion.shell,
699                     hooks = {
700                         -- Replace the 'normal' Return with a custom one
701                         {{         }, 'Return', function(command)
702                             spawn(command)
703                         end},
704                         -- Spawn method to spawn in the current tag
705                         {{'Mod1'   }, 'Return', function(command)
706                             spawn(command,{
707                                 intrusive = true,
708                                 tag       = mouse.screen.selected_tag
709                             })
710                         end},
711                         -- Spawn in the current tag as floating and on top
712                         {{'Shift'  }, 'Return', function(command)
713                             spawn(command,{
714                                 ontop     = true,
715                                 floating  = true,
716                                 tag       = mouse.screen.selected_tag
717                             })
718                         end},
719                         -- Spawn in a new tag
720                         {{'Control'}, 'Return', function(command)
721                             spawn(command,{
722                                 new_tag = true,
723                                 layout = layouts.default,
724                                 volatile = true,
725                             })
726                         end},
727                         -- Cancel
728                         {{         }, 'Escape', function(_) return end},
729                     },
730                 }
731         end,
732               {description = "run prompt", group = "launcher"}),
733
734     awful.key({ modkey }, "x",
735               function ()
736                   awful.prompt.run {
737                     prompt       = "Eval: ",
738                     bg_cursor    = '#ff0000',
739                     textbox      = awful.screen.focused().mypromptbox.widget,
740                     exe_callback = awful.util.eval,
741                     history_path = awful.util.get_cache_dir() .. "/history_eval"
742                   }
743               end,
744               {description = "lua execute prompt", group = "awesome"}),
745     -- Menubar
746     awful.key({ modkey }, "w", function() menubar.show() end,
747               {description = "show the menubar", group = "launcher"}),
748
749     -- Tag helpers
750     awful.key({ modkey,           }, "a", function()
751         th.add_tag(nil, {layout=layouts.default} ,true)
752     end,
753     {description = "add a tag", group = "tag"}),
754     awful.key({ modkey,           }, "d", th.delete_tag,
755               {description = "delete the current tag", group = "tag"}),
756     awful.key({ modkey, "Shift",           }, "a", function()
757         th.move_to_new_tag(nil,nil,true,true,true)
758     end,
759               {description = "add a volatile tag with the focused client", group = "tag"}),
760     awful.key({ modkey, "Shift", "Control" }, "a", function()
761         th.move_to_new_tag(nil,nil,false,true,true)
762     end,
763               {description = "add a permanent tag with the focused client", group = "tag"}),
764     awful.key({ modkey, "Mod1"   }, "a", th.copy_tag,
765               {description = "create a copy of the current tag", group = "tag"}),
766     awful.key({ modkey, "Control"   }, "a", th.rename_tag,
767               {description = "rename the current tag", group = "tag"}),
768     awful.key({ modkey, "Control", "Shift", "Mod1" }, "a", th.collect_orphan_clients_to_tag,
769               {description = "collect all orphaned clients", group = "client"}),
770
771     awful.key({ modkey }, "y", toggle_tag_by_name("irc", true),
772               {description = "view tag 'irc'", group = "tag"}),
773     awful.key({ modkey, "Control" }, "y", toggle_tag_by_name("irc"),
774               {description = "toggle tag 'irc'", group = "tag"}),
775     awful.key({ modkey }, "u", toggle_tag_by_name("[m]", true),
776               {description = "view tag '[m]'", group = "tag"}),
777     awful.key({ modkey, "Control" }, "u", toggle_tag_by_name("[m]"),
778               {description = "toggle tag '[m]'", group = "tag"}),
779     awful.key({ modkey }, "i", toggle_tag_by_name("cal", true),
780               {description = "view tag 'cal'", group = "tag"}),
781     awful.key({ modkey, "Control" }, "i", toggle_tag_by_name("cal"),
782               {description = "toggle tag 'cal'", group = "tag"}),
783     awful.key({ modkey }, "o", toggle_tag_by_name("chr", true),
784               {description = "view tag 'chr'", group = "tag"}),
785     awful.key({ modkey, "Control" }, "o", toggle_tag_by_name("chr"),
786               {description = "toggle tag 'chr'", group = "tag"}),
787     awful.key({ modkey }, "p", toggle_tag_by_name("ffx", true),
788               {description = "view tag 'ff'", group = "tag"}),
789     awful.key({ modkey, "Control" }, "p", toggle_tag_by_name("ffx"),
790               {description = "toggle tag 'ff'", group = "tag"}),
791 {})
792
793 clientkeys = gears.table.join(
794     awful.key({ modkey,           }, "f",
795         function (c)
796             c.fullscreen = not c.fullscreen
797             c:raise()
798         end,
799         {description = "toggle fullscreen", group = "client"}),
800     awful.key({ modkey, "Shift"   }, "c",      function (c) c:kill()                         end,
801               {description = "close", group = "client"}),
802     awful.key({ modkey, "Control" }, "space",  awful.client.floating.toggle                     ,
803               {description = "toggle floating", group = "client"}),
804     awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end,
805               {description = "move to master", group = "client"}),
806     awful.key({ modkey,           }, "t",      function (c) c.ontop = not c.ontop            end,
807               {description = "toggle keep on top", group = "client"}),
808     awful.key({ modkey,           }, "n",
809         function (c)
810             -- The client currently has the input focus, so it cannot be
811             -- minimized, since minimized clients can't have the focus.
812             c.minimized = true
813         end ,
814         {description = "minimize", group = "client"}),
815     awful.key({ modkey,           }, "m",
816         function (c)
817             c.maximized = not c.maximized
818             c.maximized_horizontal = false
819             c.maximized_vertical = false
820             c:raise()
821         end ,
822         {description = "(un)maximize", group = "client"}),
823     awful.key({ modkey, "Control" }, "m",
824         function (c)
825             c.maximized_vertical = not c.maximized_vertical
826             c:raise()
827         end ,
828         {description = "(un)maximize vertically", group = "client"}),
829     awful.key({ modkey, "Shift"   }, "m",
830         function (c)
831             c.maximized_horizontal = not c.maximized_horizontal
832             c:raise()
833         end ,
834         {description = "(un)maximize horizontally", group = "client"})
835 )
836
837 -- Bind all key numbers to tags.
838 -- Be careful: we use keycodes to make it work on any keyboard layout.
839 -- This should map on the top row of your keyboard, usually 1 to 9.
840 for i = 1, 9 do
841     globalkeys = gears.table.join(globalkeys,
842         -- View tag only.
843         awful.key({ modkey }, "#" .. i + 9, toggle_tag_by_name(tostring(i), true),
844                   {description = "view tag #"..i, group = "tag"}),
845         -- Toggle tag display.
846         awful.key({ modkey, "Control" }, "#" .. i + 9, toggle_tag_by_name(tostring(i)),
847                   {description = "toggle tag #" .. i, group = "tag"}),
848         -- Move client to tag.
849         awful.key({ modkey, "Shift" }, "#" .. i + 9,
850                   function ()
851                       if client.focus then
852                           local tag = awful.tag.find_by_name(screen.primary, tostring(i))
853                           if tag then
854                               client.focus:move_to_tag(tag)
855                           end
856                      end
857                   end,
858                   {description = "move focused client to tag #"..i, group = "tag"}),
859         -- Toggle tag on focused client.
860         awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9,
861                   function ()
862                       if client.focus then
863                           local tag = awful.tag.find_by_name(screen.primary, tostring(i))
864                           if tag then
865                               client.focus:toggle_tag(tag)
866                           end
867                       end
868                   end,
869                   {description = "toggle focused client on tag #" .. i, group = "tag"})
870     )
871 end
872
873 clientbuttons = gears.table.join(
874     awful.button({ }, 1, function (c) client.focus = c; c:raise() end),
875     awful.button({ modkey }, 1, awful.mouse.client.move),
876     awful.button({ modkey }, 3, awful.mouse.client.resize))
877
878 -- misc apps
879 globalkeys = awful.util.table.join(globalkeys,
880 awful.key({ cmdkey }, "n", function () awful.spawn("firefox") end),
881 awful.key({ cmdkey }, "m", function () awful.spawn("chromium --enable-remote-extensions") end),
882 awful.key({ cmdkey }, "y", function () awful.spawn(terminal .. " -e python") end),
883 awful.key({ cmdkey }, "c", function () awful.spawn("thunderbird") end),
884 awful.key({ cmdkey }, "g", function () awful.spawn("gscan2pdf") end),
885 awful.key({ cmdkey }, "v", function () awful.spawn("virt-manager") end),
886 awful.key({ cmdkey }, "l", function () awful.spawn("libreoffice") end),
887 awful.key({ cmdkey }, "f", function () awful.spawn("thunar") end),
888 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),
889 awful.key({ cmdkey }, "x", function ()
890     awful.spawn("/usr/bin/xscreensaver -no-capture-stderr")
891     os.execute("sleep 5")
892     awful.spawn("xscreensaver-command -lock")
893 end),
894 awful.key({ cmdkey, "Shift" }, "x", function () awful.spawn("xscreensaver-command -exit") end),
895
896 -- function keys
897 awful.key(nil, "XF86ScreenSaver", function () awful.spawn("xset dpms force off") end),
898 awful.key(nil, "XF86AudioMute", function () awful.spawn("pactl set-sink-mute 0 toggle") end),
899 awful.key({ cmdkey }, "End", function () awful.spawn("pactl set-sink-mute 0 toggle") end),
900 awful.key(nil, "XF86AudioLowerVolume", function () awful.spawn("pactl set-sink-volume 0 -2%") end),
901 awful.key({ cmdkey }, "Next", function () awful.spawn("pactl set-sink-volume 0 -2%") end),
902 awful.key(nil, "XF86AudioRaiseVolume", function () awful.spawn("pactl set-sink-volume 0 +2%") end),
903 awful.key({ cmdkey }, "Prior", function () awful.spawn("pactl set-sink-volume 0 +2%") end),
904 awful.key(nil, "XF86AudioMicMute", function () awful.spawn("pactl set-source-mute 1 toggle") end),
905 awful.key({ cmdkey }, "Home", function () awful.spawn("pactl set-source-mute 1 toggle") end),
906 awful.key(nil, "XF86MonBrightnessDown", function () awful.spawn("xbacklight -dec 5%") end),
907 awful.key(nil, "XF86MonBrightnessUp", function () awful.spawn("xbacklight -inc 5%") end),
908 awful.key(nil, "XF86Display", function () awful.spawn("") end),
909 awful.key(nil, "XF86WLAN", function () awful.spawn("") end),
910 awful.key(nil, "XF86Tools", function () awful.spawn("") end),
911 awful.key(nil, "XF86Search", function () awful.spawn("") end),
912 awful.key(nil, "XF86LaunchA", function () awful.spawn("") end),
913 awful.key(nil, "XF86Explorer", function () awful.spawn("") end)
914 )
915
916 -- Set keys
917 root.keys(globalkeys)
918 -- }}}
919
920 -- {{{ Rules
921 -- Rules to apply to new clients (through the "manage" signal).
922
923 local function float_client_in_the_middle_with_margins(client, leftright, topbottom)
924     local wa = client.screen.workarea
925     if topbottom then
926         client.y = wa.y + topbottom
927         client.height = wa.height - 2*topbottom
928     else
929         client.y = wa.y + (wa.height - client.height)/2
930     end
931     if leftright then
932         client.x = wa.x + leftright
933         client.width = wa.width - 2*leftright
934     else
935         client.x = wa.x + (wa.width - client.width)/2
936     end
937 end
938
939 local function move_to_tag_by_name(s, tagname)
940     return function(c)
941         local t = awful.tag.find_by_name(s, tagname)
942         if not t then
943             error("No tag by the name of " .. tagname)
944             return
945         end
946         c:move_to_tag(t)
947     end
948 end
949
950 awful.rules.rules = {
951     -- All clients will match this rule.
952     { rule = { },
953       properties = { border_width = beautiful.border_width,
954                      border_color = beautiful.border_normal,
955                      focus = awful.client.focus.filter,
956                      raise = true,
957                      keys = clientkeys,
958                      buttons = clientbuttons,
959                      screen = awful.screen.preferred,
960                      placement = awful.placement.no_overlap+awful.placement.no_offscreen,
961                      --floating = false
962                  },
963     },
964     { rule = { type = "dialog" },
965       properties = { floating = true,
966                      ontop = true,
967                      skip_taskbar = true,
968                      urgent = true,
969                      --new_tag = true,
970                      --switchtotag = true,
971                      placement = awful.placement.centered
972                    }
973     },
974     { rule = { class = "URxvt" },
975       properties = { size_hints_honor = false, }
976     },
977     { rule = { instance =  "irc" },
978       callback = move_to_tag_by_name(nil, "irc"),
979     },
980     { rule = { class = "Revolt" },
981       callback = move_to_tag_by_name(nil, "[m]"),
982     },
983     { rule = { class = "Firefox" },
984       callback = move_to_tag_by_name(nil, "ffx"),
985     },
986     { rule = { class = "Chromium" },
987       callback = move_to_tag_by_name(nil, "chr"),
988     },
989     { rule = { class = "Thunderbird" },
990       callback = move_to_tag_by_name(nil, "cal"),
991     },
992     { rule_any = { class = {
993         "MuPDF",
994         "Wicd-client.py",
995     }},
996       properties = { floating = true,
997                      focus = true,
998                      placement = function(c)
999                          float_client_in_the_middle_with_margins(c, 50, 25)
1000                      end,
1001                    },
1002     },
1003 --    { rule_any = { class = {
1004 --                        "Gscan2pdf",
1005 --                        "Gimp",
1006 --                    },
1007 --                    instance = {
1008 --                        "libreoffice",
1009 --                    }
1010 --                },
1011 --      properties = { new_tag = {
1012 --                        layout = layouts.maximised,
1013 --                        volatile = true,
1014 --                    },
1015 --                     switchtotag = true,
1016 --                     focus = true,
1017 --                   },
1018 --    },
1019 --XX--    { rule = { class = "Gscan2pdf" },
1020 --XX--               properties = {
1021 --XX--                   switchtotag = true
1022 --XX--               },
1023 --XX--               callback = move_to_tag(1, 5)
1024 --XX--           },
1025 --XX--    { rule = { name = "gscan2pdf .*" },
1026 --XX--               properties = {
1027 --XX--                   floating = false,
1028 --XX--               },
1029 --XX--           },
1030 --XX--    { rule = { class = "Thunar", type = "normal" },
1031 --XX--               properties = {
1032 --XX--                   floating = false,
1033 --XX--               },
1034 --XX--           },
1035 --XX--    { rule = { class = "Pinentry", instance = "pinentry" },
1036 --XX--               properties = {
1037 --XX--                   floating = true,
1038 --XX--               },
1039 --XX--           },
1040 --XX--    { rule = { class = "Gxmessage" },
1041 --XX--               properties = {
1042 --XX--                   floating = true,
1043 --XX--               },
1044 --XX--           },
1045 --XX--}
1046 }
1047 -- }}}
1048
1049 -- {{{ Signals
1050 -- Signal function to execute when a new client appears.
1051 client.connect_signal("manage", function (c)
1052     -- Set the windows at the slave,
1053     -- i.e. put it at the end of others instead of setting it master.
1054     -- if not awesome.startup then awful.client.setslave(c) end
1055
1056     if awesome.startup and
1057       not c.size_hints.user_position
1058       and not c.size_hints.program_position then
1059         -- Prevent clients from being unreachable after screen count changes.
1060         awful.placement.no_offscreen(c)
1061     end
1062
1063     c.maximized_horizontal = false
1064     c.maximized_vertical = false
1065 end)
1066
1067 -- Add a titlebar if titlebars_enabled is set to true in the rules.
1068 client.connect_signal("request::titlebars", function(c)
1069     -- buttons for the titlebar
1070     local buttons = gears.table.join(
1071         awful.button({ }, 1, function()
1072             client.focus = c
1073             c:raise()
1074             awful.mouse.client.move(c)
1075         end),
1076         awful.button({ }, 3, function()
1077             client.focus = c
1078             c:raise()
1079             awful.mouse.client.resize(c)
1080         end)
1081     )
1082
1083     awful.titlebar(c) : setup {
1084         { -- Left
1085             awful.titlebar.widget.iconwidget(c),
1086             buttons = buttons,
1087             layout  = wibox.layout.fixed.horizontal
1088         },
1089         { -- Middle
1090             { -- Title
1091                 align  = "center",
1092                 widget = awful.titlebar.widget.titlewidget(c)
1093             },
1094             buttons = buttons,
1095             layout  = wibox.layout.flex.horizontal
1096         },
1097         { -- Right
1098             awful.titlebar.widget.floatingbutton (c),
1099             awful.titlebar.widget.maximizedbutton(c),
1100             awful.titlebar.widget.stickybutton   (c),
1101             awful.titlebar.widget.ontopbutton    (c),
1102             awful.titlebar.widget.closebutton    (c),
1103             layout = wibox.layout.fixed.horizontal()
1104         },
1105         layout = wibox.layout.align.horizontal
1106     }
1107 end)
1108
1109 -- Enable sloppy focus, so that focus follows mouse.
1110 client.connect_signal("mouse::enter", function(c)
1111     if awful.layout.get(c.screen) ~= awful.layout.suit.magnifier
1112         and awful.client.focus.filter(c) then
1113         client.focus = c
1114     end
1115 end)
1116
1117 client.connect_signal("focus", function(c)
1118     c.border_color = beautiful.border_focus
1119 end)
1120 client.connect_signal("unfocus", function(c)
1121     c.border_color = beautiful.border_normal
1122 end)
1123
1124 awful.ewmh.add_activate_filter(function(c, context, hints)
1125     if context == "ewmh" then
1126         if (c.class == "Firefox-esr" or c.class == "Firefox") then
1127             return false
1128         end
1129     end
1130 end)
1131
1132 client.connect_signal("request::activate", function(c, context, hints)
1133     if gears.table.hasitem({
1134         "client.focus.byidx",
1135         "client.jumpto",
1136         "autofocus.check_focus",
1137         "rules",
1138         "ewmh",
1139     }, context) then
1140         gears.timer.delayed_call(function()
1141             -- we need a delayed call so that we execute *after layout changes
1142             if hints.raise and c == client.focus and client.focus:isvisible() then
1143                 move_mouse_to_area(client.focus)
1144             end
1145         end)
1146     end
1147 end)
1148
1149 -- vim:ft=lua:sw=4:sts=4:ts=4:et