]> git.madduck.net Git - etc/awesome.git/blob - .config/awesome/rc.lua

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:

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