-- {{{ Imports -- Standard awesome library local gears = require("gears") local awful = require("awful") require("awful.autofocus") -- Widget and layout library local wibox = require("wibox") -- Theme handling library local beautiful = require("beautiful") -- Notification library local naughty = require("naughty") local menubar = require("menubar") local hotkeys_popup = require("awful.hotkeys_popup").widget local lain = require("lain") local dkjson = require("lain.util").dkjson local math = require("math") local freedesktop = require("freedesktop") local luatz = require("luatz") -- }}} -- {{{ Error handling -- Check if awesome encountered an error during startup and fell back to -- another config (This code will only ever execute for the fallback config) if awesome.startup_errors then naughty.notify({ preset = naughty.config.presets.critical, title = "Oops, there were errors during startup!", text = awesome.startup_errors }) end -- Handle runtime errors after startup do local in_error = false awesome.connect_signal("debug::error", function (err) -- Make sure we don't go into an endless error loop if in_error then return end in_error = true naughty.notify({ preset = naughty.config.presets.critical, title = "Oops, an error happened!", text = tostring(err) }) in_error = false end) end -- }}} -- {{{ Variable definitions -- Themes define colours, icons, and wallpapers beautiful.init(awful.util.get_themes_dir() .. "default/theme.lua") -- This is used later as the default terminal and editor to run. terminal = "x-terminal-emulator" editor = "sensible-editor" editor_cmd = terminal .. " -e " .. editor -- Default modkey. -- Usually, Mod4 is the key with a logo between Control and Alt. -- If you do not like this or do not have such a key, -- I suggest you to remap Mod4 to another key using xmodmap or other tools. -- However, you can use another modifier like Mod1, but it may interact with others. modkey = "Mod4" cmdkey = "Mod3" -- Table of layouts to cover with awful.layout.inc, order matters. awful.layout.layouts = { awful.layout.suit.fair, awful.layout.suit.tile, -- awful.layout.suit.tile.left, -- awful.layout.suit.tile.bottom, awful.layout.suit.tile.top, -- awful.layout.suit.spiral, -- awful.layout.suit.spiral.dwindle, awful.layout.suit.max, -- awful.layout.suit.max.fullscreen, -- awful.layout.suit.magnifier, -- awful.layout.suit.corner.nw, -- awful.layout.suit.corner.ne, -- awful.layout.suit.corner.sw, -- awful.layout.suit.corner.se, awful.layout.suit.floating, } layout_default = awful.layout.layouts[1] layout_tiled = awful.layout.layouts[2] layout_maximised = awful.layout.layouts[4] layout_floating = awful.layout.layouts[5] -- }}} -- {{{ Helper functions local function client_menu_toggle_fn() local instance = nil return function () if instance and instance.wibox.visible then instance:hide() instance = nil else instance = awful.menu.clients({ theme = { width = 250 } }) end end end local function sorted_pairs(t, f) local a = {} for n in pairs(t) do table.insert(a, n) end table.sort(a, f) local i = 0 -- iterator variable local iter = function () -- iterator function i = i + 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end return iter end local function print_table(tbl, indent) if not indent then indent = 0 end for k, v in pairs(tbl) do formatting = string.rep(" ", indent) .. k .. ": " if type(v) == "table" then print(formatting) print_table(v, indent+1) else print(formatting .. tostring(v)) end end end local lain_bat = lain.widget.bat({ batteries = {"BAT0", "BAT1"}, settings = function() local delim = "↓" if bat_now.status == "Charging" then delim = "↑" elseif bat_now.status == "Unknown" then delim = "٭" end widget:set_text(bat_now.perc .. "% " .. delim .. " " .. bat_now.time) end, }) local function poloniex_price(output, pair, prec) local xc, pos, err = dkjson.decode(output, 1, nil) if not prec then prec = 4 end val = (xc and xc[pair]["last"]) or 0 val = math.floor(val*10^prec+0.5)/10^prec return (not err and val) or "n/a" end local eth_widget = lain.widget.watch({ cmd = "curl -m5 -s 'https://poloniex.com/public?command=returnTicker'", timeout = 600, settings = function() widget:set_text(poloniex_price(output, 'BTC_ETH') .. " Ƀ/Ξ") end }) local function coindesk_price(output, base, prec) local xc, pos, err = dkjson.decode(output, 1, nil) if not prec then prec = 4 end val = (xc and xc["bpi"][base]["rate_float"]) or 0 val = math.floor(val*10^prec+0.5)/10^prec return (not err and val) or "n/a" end local btc_widget = lain.widget.watch({ cmd = "curl -m5 -Ls 'https://api.coindesk.com/v1/bpi/currentprice/EUR.json'", timeout = 600, settings = function() widget:set_text(coindesk_price(output, "EUR", 2) .. " €/Ƀ") end }) -- }}} -- {{{ Menu -- Create a launcher widget and a main menu myawesomemenu = { { "hotkeys", function() return false, hotkeys_popup.show_help end}, { "manual", terminal .. " -e man awesome" }, { "edit config", editor_cmd .. " " .. awesome.conffile }, { "restart", awesome.restart }, { "quit", awesome.quit } } mymainmenu = freedesktop.menu.build({ before = { { "awesome", myawesomemenu, beautiful.awesome_icon }, { "terminal", terminal }, }, after = { } }) mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon, menu = mymainmenu }) -- }}} -- {{{ Menubar configuration menubar.utils.terminal = terminal -- Set the terminal for applications that require it -- }}} -- {{{ Wibox local spacer = wibox.widget.textbox() spacer:set_text(' │ ') -- Create a textclock widget clocks = { wibox.widget.textclock("%a %d %b %H:%M:%S %Z", 1) } ZONES = { ["NZ"] = "Pacific/Auckland", ["DE"] = "Europe/Berlin" } local now = luatz.time_in(nil) for c, tz in sorted_pairs(ZONES) do local t = luatz.time_in(tz) if math.abs(os.difftime(t, now)) > 10 then local widget = wibox.widget.textclock(c .. ": %H:%M (%a)", 60, tz) table.insert(clocks, 1, spacer) table.insert(clocks, 1, widget) end end -- Create a wibox for each screen and add it mywibox = {} mypromptbox = {} mylayoutbox = {} mytaglist = {} mytaglist.buttons = awful.util.table.join( awful.button({ }, 1, function(t) t:view_only() end), awful.button({ modkey }, 1, function(t) if client.focus then client.focus:move_to_tag(t) end end), awful.button({ }, 3, awful.tag.viewtoggle), awful.button({ modkey }, 3, function(t) if client.focus then client.focus:toggle_tag(t) end end), awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end), awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end) ) mytasklist = {} mytasklist.buttons = awful.util.table.join( awful.button({ }, 1, function (c) if c == client.focus then -- I don't like click-minimising -- c.minimized = true else -- Without this, the following -- :isvisible() makes no sense c.minimized = false if not c:isvisible() and c.first_tag then c.first_tag:view_only() end -- This will also un-minimize -- the client, if needed client.focus = c c:raise() end end), awful.button({ }, 3, client_menu_toggle_fn()), awful.button({ }, 4, function () awful.client.focus.byidx(1) end), awful.button({ }, 5, function () awful.client.focus.byidx(-1) end)) -- }}} -- {{{ Tags tags = {} tags.config = {} tags.config["main"] = { t1 = { layout = layout_default, selected = true }, t2 = { layout = layout_default }, t3 = { layout = layout_tiled }, t4 = { layout = layout_tiled }, t5 = { layout = layout_tiled }, t6 = { layout = layout_floating }, t7 = { layout = layout_maximised }, t8 = { layout = layout_maximised }, t9 = { layout = layout_maximised }, } tags.config["aux"] = { t1 = { layout = layout_default, selected = true }, t2 = { layout = layout_default }, t3 = { layout = layout_tiled }, t4 = { layout = layout_floating }, t5 = { layout = layout_floating }, t6 = { layout = layout_floating }, t7 = { layout = layout_floating }, t8 = { layout = layout_floating }, t9 = { layout = layout_maximised }, } screentags = {} screentags[1] = tags.config["main"] if screen.count() == 2 then -- aux screen is on the right screentags[2] = tags.config["aux"] elseif screen.count() == 3 then -- main screen is still #1 in the middle screentags[2] = tags.config["aux"] screentags[3] = tags.config["aux"] end awful.screen.connect_for_each_screen(function(s) -- Wallpaper --DISABLED--if beautiful.wallpaper then --DISABLED-- local wallpaper = beautiful.wallpaper --DISABLED-- -- If wallpaper is a function, call it with the screen --DISABLED-- if type(wallpaper) == "function" then --DISABLED-- wallpaper = wallpaper(s) --DISABLED-- end --DISABLED-- gears.wallpaper.maximized(wallpaper, s, true) --DISABLED--end if not tags[s.index] then tags[s.index] = {} end for n,p in sorted_pairs(screentags[s.index]) do p["screen"] = s n = string.sub(n, 2) -- remove leading 't' needed for syntax in table table.insert(tags[s.index], awful.tag.add(n, p)) end -- Create a promptbox for each screen mypromptbox[s] = awful.widget.prompt() -- Create an imagebox widget which will contains an icon indicating which layout we're using. -- We need one layoutbox per screen. mylayoutbox[s] = awful.widget.layoutbox(s) mylayoutbox[s]:buttons(awful.util.table.join( awful.button({ }, 1, function () awful.layout.inc( 1) end), awful.button({ }, 3, function () awful.layout.inc(-1) end), awful.button({ }, 4, function () awful.layout.inc( 1) end), awful.button({ }, 5, function () awful.layout.inc(-1) end))) -- Create a taglist widget mytaglist[s] = awful.widget.taglist(s, awful.widget.taglist.filter.all, mytaglist.buttons) -- Create a tasklist widget mytasklist[s] = awful.widget.tasklist(s, awful.widget.tasklist.filter.currenttags, mytasklist.buttons) -- Create the wibox mywibox[s] = awful.wibar({ position = "top", screen = s }) -- Add widgets to the wibox mywibox[s]:setup { layout = wibox.layout.align.horizontal, { -- Left widgets layout = wibox.layout.fixed.horizontal, -- mylauncher, mytaglist[s], mypromptbox[s], }, mytasklist[s], -- Middle widget awful.util.table.join( -- Right widgets { layout = wibox.layout.fixed.horizontal, mykeyboardlayout, wibox.widget.systray(), btc_widget, spacer, lain_bat.widget, spacer, }, clocks, { mylayoutbox[s], } ), } end) -- }}} -- {{{ Mouse bindings root.buttons(awful.util.table.join( awful.button({ }, 3, function () mymainmenu:toggle() end), awful.button({ }, 4, awful.tag.viewnext), awful.button({ }, 5, awful.tag.viewprev) )) -- }}} -- {{{ Key bindings globalkeys = awful.util.table.join( awful.key({ modkey, }, "s", hotkeys_popup.show_help, {description="show help", group="awesome"}), awful.key({ modkey, }, "Left", awful.tag.viewprev, {description = "view previous", group = "tag"}), awful.key({ modkey, }, "Right", awful.tag.viewnext, {description = "view next", group = "tag"}), awful.key({ modkey, }, "Escape", awful.tag.history.restore, {description = "go back", group = "tag"}), awful.key({ modkey, }, "k", function () awful.client.focus.byidx( 1) end, {description = "focus next by index", group = "client"} ), awful.key({ modkey, }, "j", function () awful.client.focus.byidx(-1) end, {description = "focus previous by index", group = "client"} ), awful.key({ modkey, }, "w", function () mymainmenu:show() end, {description = "show main menu", group = "awesome"}), -- Layout manipulation awful.key({ modkey, "Shift" }, "k", function () awful.client.swap.byidx( 1) end, {description = "swap with next client by index", group = "client"}), awful.key({ modkey, "Shift" }, "j", function () awful.client.swap.byidx( -1) end, {description = "swap with previous client by index", group = "client"}), awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative( 1) end, {description = "focus the next screen", group = "screen"}), awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative(-1) end, {description = "focus the previous screen", group = "screen"}), awful.key({ modkey, }, "u", awful.client.urgent.jumpto, {description = "jump to urgent client", group = "client"}), awful.key({ modkey, }, "Tab", function () awful.client.focus.history.previous() if client.focus then client.focus:raise() end end, {description = "go back", group = "client"}), -- Standard program awful.key({ modkey, }, "Return", function () awful.spawn(terminal) end, {description = "open a terminal", group = "launcher"}), awful.key({ modkey, "Control" }, "r", awesome.restart, {description = "reload awesome", group = "awesome"}), awful.key({ modkey, "Shift" }, "q", awesome.quit, {description = "quit awesome", group = "awesome"}), awful.key({ modkey, }, "l", function () awful.tag.incmwfact( 0.05) end, {description = "increase master width factor", group = "layout"}), awful.key({ modkey, }, "h", function () awful.tag.incmwfact(-0.05) end, {description = "decrease master width factor", group = "layout"}), awful.key({ modkey, "Shift" }, "h", function () awful.tag.incnmaster( 1, nil, true) end, {description = "increase the number of master clients", group = "layout"}), awful.key({ modkey, "Shift" }, "l", function () awful.tag.incnmaster(-1, nil, true) end, {description = "decrease the number of master clients", group = "layout"}), awful.key({ modkey, "Control" }, "h", function () awful.tag.incncol( 1, nil, true) end, {description = "increase the number of columns", group = "layout"}), awful.key({ modkey, "Control" }, "l", function () awful.tag.incncol(-1, nil, true) end, {description = "decrease the number of columns", group = "layout"}), awful.key({ modkey, }, "space", function () awful.layout.inc( 1) end, {description = "select next", group = "layout"}), awful.key({ modkey, "Shift" }, "space", function () awful.layout.inc(-1) end, {description = "select previous", group = "layout"}), awful.key({ modkey, "Control" }, "n", function () local c = awful.client.restore() -- Focus restored client if c then client.focus = c c:raise() end end, {description = "restore minimized", group = "client"}), -- Prompt awful.key({ modkey }, "r", function () mypromptbox[awful.screen.focused()]:run() end, {description = "run prompt", group = "launcher"}), awful.key({ modkey }, "x", function () awful.prompt.run({ prompt = "Run Lua code: " }, mypromptbox[awful.screen.focused()].widget, awful.util.eval, nil, awful.util.get_cache_dir() .. "/history_eval") end, {description = "lua execute prompt", group = "awesome"}), -- Menubar awful.key({ modkey }, "p", function() menubar.show() end, {description = "show the menubar", group = "launcher"}) ) clientkeys = awful.util.table.join( awful.key({ modkey, }, "f", function (c) c.fullscreen = not c.fullscreen c:raise() end, {description = "toggle fullscreen", group = "client"}), awful.key({ modkey, "Shift" }, "c", function (c) c:kill() end, {description = "close", group = "client"}), awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle , {description = "toggle floating", group = "client"}), awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end, {description = "move to master", group = "client"}), awful.key({ modkey, }, "o", function (c) c:move_to_screen() end, {description = "move to screen", group = "client"}), awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end, {description = "toggle keep on top", group = "client"}), awful.key({ modkey, }, "n", function (c) -- The client currently has the input focus, so it cannot be -- minimized, since minimized clients can't have the focus. c.minimized = true end , {description = "minimize", group = "client"}), awful.key({ modkey, }, "m", function (c) c.maximized = not c.maximized c.maximized_horizontal = false c.maximized_vertical = false c:raise() end , {description = "maximize", group = "client"}) ) -- Bind all key numbers to tags. -- Be careful: we use keycodes to make it works on any keyboard layout. -- This should map on the top row of your keyboard, usually 1 to 9. for i = 1, 9 do globalkeys = awful.util.table.join(globalkeys, -- View tag only. awful.key({ modkey }, "#" .. i + 9, function () local screen = awful.screen.focused() local tag = screen.tags[i] if tag then tag:view_only() end end, {description = "view tag #"..i, group = "tag"}), -- Toggle tag. awful.key({ modkey, "Control" }, "#" .. i + 9, function () local screen = awful.screen.focused() local tag = screen.tags[i] if tag then awful.tag.viewtoggle(tag) end end, {description = "toggle tag #" .. i, group = "tag"}), -- Move client to tag. awful.key({ modkey, "Shift" }, "#" .. i + 9, function () if client.focus then local tag = client.focus.screen.tags[i] if tag then client.focus:move_to_tag(tag) end end end, {description = "move focused client to tag #"..i, group = "tag"}), -- Toggle tag on focused client. awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9, function () if client.focus then local tag = client.focus.screen.tags[i] if tag then client.focus:toggle_tag(tag) end end end, {description = "toggle focused client on tag #" .. i, group = "tag"}) ) end clientbuttons = awful.util.table.join( awful.button({ }, 1, function (c) client.focus = c; c:raise() end), awful.button({ modkey }, 1, awful.mouse.client.move), awful.button({ modkey }, 3, awful.mouse.client.resize)) -- misc apps globalkeys = awful.util.table.join(globalkeys, awful.key({ cmdkey }, "n", function () awful.spawn("firefox") end), awful.key({ cmdkey }, "m", function () awful.spawn("chromium --enable-remote-extensions") end), awful.key({ cmdkey }, "y", function () awful.spawn(terminal .. " -e python") end), awful.key({ cmdkey }, "c", function () awful.spawn("thunderbird") end), awful.key({ cmdkey }, "r", function () mypromptbox[mouse.screen]:run() end), awful.key({ cmdkey }, "g", function () awful.spawn("gscan2pdf") end), awful.key({ cmdkey }, "v", function () awful.spawn("virt-manager") end), awful.key({ cmdkey }, "l", function () awful.spawn("libreoffice") end), awful.key({ cmdkey }, "f", function () awful.spawn("thunar") end), 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), awful.key({ cmdkey }, "x", function () awful.spawn.with_shell("/sbin/start-stop-daemon --start --background --exec /usr/bin/xscreensaver -- -no-capture-stderr; sleep 2; xscreensaver-command -lock") end), awful.key({ cmdkey, "Shift" }, "x", function () awful.spawn("xscreensaver-command -exit") end), -- function keys awful.key(nil, "XF86ScreenSaver", function () awful.spawn("xset dpms force off") end), awful.key(nil, "XF86AudioMute", function () awful.spawn("pactl set-sink-mute 0 toggle") end), awful.key({ cmdkey }, "End", function () awful.spawn("pactl set-sink-mute 0 toggle") end), awful.key(nil, "XF86AudioLowerVolume", function () awful.spawn("pactl set-sink-volume 0 -2%") end), awful.key({ cmdkey }, "Next", function () awful.spawn("pactl set-sink-volume 0 -2%") end), awful.key(nil, "XF86AudioRaiseVolume", function () awful.spawn("pactl set-sink-volume 0 +2%") end), awful.key({ cmdkey }, "Prior", function () awful.spawn("pactl set-sink-volume 0 +2%") end), awful.key(nil, "XF86AudioMicMute", function () awful.spawn("pactl set-source-mute 1 toggle") end), awful.key({ cmdkey }, "Home", function () awful.spawn("pactl set-source-mute 1 toggle") end), awful.key(nil, "XF86MonBrightnessDown", function () awful.spawn("xbacklight -dec 5%") end), awful.key(nil, "XF86MonBrightnessUp", function () awful.spawn("xbacklight -inc 5%") end), awful.key(nil, "XF86Display", function () awful.spawn("") end), awful.key(nil, "XF86WLAN", function () awful.spawn("") end), awful.key(nil, "XF86Tools", function () awful.spawn("") end), awful.key(nil, "XF86Search", function () awful.spawn("") end), awful.key(nil, "XF86LaunchA", function () awful.spawn("") end), awful.key(nil, "XF86Explorer", function () awful.spawn("") end) ) -- Set keys root.keys(globalkeys) -- }}} -- {{{ Rules -- Rules to apply to new clients (through the "manage" signal). local function move_to_tag(s, t) return function(c) c:move_to_tag(tags[s][t]) end end awful.rules.rules = { -- All clients will match this rule. { rule = { }, properties = { border_width = beautiful.border_width, border_color = beautiful.border_normal, focus = awful.client.focus.filter, raise = true, keys = clientkeys, buttons = clientbuttons, screen = awful.screen.preferred, placement = awful.placement.no_overlap+awful.placement.no_offscreen, floating = false }, }, -- Add titlebars to normal clients and dialogs --DISABLED-- { rule_any = {type = { "normal", "dialog" } --DISABLED-- }, properties = { titlebars_enabled = true } --DISABLED-- }, { rule = { type = "dialog" }, properties = { floating = true, placement = awful.placement.centered } }, { rule = { class = "URxvt" }, properties = { -- floating = false, size_hints_honor = false } }, { rule = { class = "URxvt", instance = "irc" }, properties = { switchtotag = true }, callback = move_to_tag(screen.count(), screen.count() == 1 and 2 or 1) }, { rule = { class = "Firefox", instance = "Navigator" }, properties = { floating = false, }, callback = move_to_tag(screen.count() == 1 and 1 or 2, 9) }, { rule = { class = "Firefox-esr", instance = "Navigator" }, properties = { floating = false, }, callback = move_to_tag(screen.count() == 1 and 1 or 2, 9) }, { rule = { class = "Thunderbird", instance = "Mail" }, properties = { floating = false, }, callback = move_to_tag(screen.count() == 1 and 1 or 2, 8) }, { rule = { class = "Chromium", instance = "chromium" }, properties = { floating = false, }, callback = move_to_tag(screen.count() == 1 and 1 or 2, 9) }, { rule = { class = "Gscan2pdf" }, properties = { switchtotag = true }, callback = move_to_tag(1, 5) }, { rule = { name = "gscan2pdf .*" }, properties = { floating = false, }, }, { rule = { class = "Thunar", type = "normal" }, properties = { floating = false, }, }, { rule = { class = "MuPDF", instance = "mupdf" }, properties = { floating = true, }, }, { rule = { class = "Pinentry", instance = "pinentry" }, properties = { floating = true, }, }, { rule = { class = "Gxmessage" }, properties = { floating = true, }, }, } -- }}} -- {{{ Signals -- Signal function to execute when a new client appears. client.connect_signal("manage", function (c) -- Set the windows at the slave, -- i.e. put it at the end of others instead of setting it master. if not awesome.startup then awful.client.setslave(c) end if awesome.startup and not c.size_hints.user_position and not c.size_hints.program_position then -- Prevent clients from being unreachable after screen count changes. awful.placement.no_offscreen(c) end c.maximized_horizontal = false c.maximized_vertical = false end) -- Add a titlebar if titlebars_enabled is set to true in the rules. client.connect_signal("request::titlebars", function(c) -- buttons for the titlebar local buttons = awful.util.table.join( awful.button({ }, 1, function() client.focus = c c:raise() awful.mouse.client.move(c) end), awful.button({ }, 3, function() client.focus = c c:raise() awful.mouse.client.resize(c) end) ) awful.titlebar(c) : setup { { -- Left awful.titlebar.widget.iconwidget(c), buttons = buttons, layout = wibox.layout.fixed.horizontal }, { -- Middle { -- Title align = "center", widget = awful.titlebar.widget.titlewidget(c) }, buttons = buttons, layout = wibox.layout.flex.horizontal }, { -- Right awful.titlebar.widget.floatingbutton (c), awful.titlebar.widget.maximizedbutton(c), awful.titlebar.widget.stickybutton (c), awful.titlebar.widget.ontopbutton (c), awful.titlebar.widget.closebutton (c), layout = wibox.layout.fixed.horizontal() }, layout = wibox.layout.align.horizontal } end) -- Enable sloppy focus client.connect_signal("mouse::enter", function(c) if awful.layout.get(c.screen) ~= awful.layout.suit.magnifier and awful.client.focus.filter(c) then client.focus = c end end) client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end) client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end) awful.ewmh.add_activate_filter(function(c, context, hints) if context == "ewmh" and (c.class == "Firefox-esr" or c.class == "Firefox") then return false end end) -- vim:ft=lua:sw=4:sts=4:ts=4:et