--- /dev/null
+local gears = require("gears")
+local wibox = require("wibox")
+local math = require("math")
+local luatz = require("luatz")
+local tblutils = require("tblutils")
+
+local module = {}
+
+local function get_textclocks_for_timezones(zones)
+ local now = math.floor(luatz.time_in(nil))
+ local ret = {}
+ for c, tz in tblutils.sorted_pairs(zones) do
+ local t = math.floor(luatz.time_in(tz))
+ if math.abs(os.difftime(t, now)) > 10 then
+ local widget = wibox.widget.textclock(c .. ": %H:%M (%a)", 60, tz)
+ ret[#ret+1] = widget
+ end
+ end
+ return ret
+end
+
+function module.get_clocksarray(strftime, zones, spacer)
+ local ret = {}
+ local zoneclocks = get_textclocks_for_timezones(zones)
+ for i, c in ipairs(zoneclocks) do
+ ret[#ret+1] = c
+ if spacer then
+ ret[#ret+1] = spacer
+ end
+ end
+ ret[#ret+1] = wibox.widget.textclock(strftime, 1)
+ return ret
+end
+
+return module
--- /dev/null
+local dkjson = require("lain.util").dkjson
+local math = require("math")
+local lain = require("lain")
+local beautiful = require("beautiful")
+
+local widgets = {}
+
+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
+
+widgets.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
+
+widgets.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
+})
+
+return widgets
--- /dev/null
+gears = require("gears")
+
+local module = {}
+
+local function massage_args_for_debug_output(...)
+ local args = table.pack(...)
+ for i = 1,select('#', ...) do
+ args[i] = (args[i] and gears.debug.dump_return(args[i], i, 65535))
+ or string.format('%d : %s', i, args[i])
+ end
+ return args
+end
+
+function module.sprintf(s, ...)
+ local args = massage_args_for_debug_output(...)
+ return string.format(s, table.unpack(args))
+end
+
+function module.printf(s, ...)
+ print(dsprintf(s, ...) or "(nil)")
+end
+
+function module.dump(...)
+ local args = massage_args_for_debug_output(...)
+ for _,v in ipairs(args) do
+ print(tostring(v))
+ end
+end
+
+return module
--- /dev/null
+local dkjson = require("lain.util").dkjson
+local math = require("math")
+local lain = require("lain")
+local beautiful = require("beautiful")
+
+local widgets = {}
+
+local function parse_ecb_rates(output, symbol, prec)
+ local xc, pos, err = dkjson.decode(output, 1, nil)
+ if not prec then prec = 2 end
+ val = (xc and xc["rates"][symbol]) or 0
+ print (val)
+ val = math.floor(val*10^prec+0.5)/10^prec
+ return (not err and val) or "n/a"
+end
+
+widgets.ecb_widget = lain.widget.watch({
+ cmd = "curl -m5 -s 'https://api.exchangeratesapi.io/latest?base=EUR'",
+ timeout = 600,
+ settings = function()
+ widget:set_text(parse_ecb_rates(output, 'NZD') .. " NZD = "
+ .. parse_ecb_rates(output, 'CHF') .. " CHF")
+ end,
+})
+
+return widgets
--- /dev/null
+modules/luatz/luatz
\ No newline at end of file
--- /dev/null
+return {
+ default = {
+ lpath = "./?.lua";
+ };
+}
--- /dev/null
+luatz-*.rock
+doc/luatz.3
+doc/luatz.html
+doc/luatz.pdf
--- /dev/null
+std = "min"
+files["spec"] = {std = "+busted"}
--- /dev/null
+return {
+ statsfile = "luacov.stats.out";
+ reportfile = "luacov.report.out";
+ deletestats = true;
+ include = {
+ "^./luatz/";
+ };
+ exclude = {
+ };
+}
--- /dev/null
+language: python
+
+sudo: false
+
+env:
+ matrix:
+ - LUA="lua 5.1" SOCKET=true
+ - LUA="lua 5.1"
+ - LUA="lua 5.2" SOCKET=true
+ - LUA="lua 5.2"
+ - LUA="lua 5.3" SOCKET=true
+ - LUA="lua 5.3"
+ - LUA="luajit 2.0"
+ - LUA="luajit 2.1" SOCKET=true SYSCALL=true
+ - LUA="luajit 2.1" SYSCALL=true
+ - LUA="luajit 2.1"
+ - LUA="luajit @"
+
+before_install:
+ - pip install hererocks
+ - hererocks here -r^ --$LUA # Install latest LuaRocks version
+ # plus the Lua version for this build job
+ # into 'here' subdirectory
+ - export PATH=$PATH:$PWD/here/bin # Add directory with all installed binaries to PATH
+ - eval `luarocks path --bin`
+ - luarocks install luacov-coveralls
+ - luarocks install busted
+
+install:
+ - luarocks make
+ - if [ "$SOCKET" = "true" ]; then luarocks install luasocket; fi
+ - if [ "$SYSCALL" = "true" ]; then luarocks install ljsyscall; fi
+
+script:
+ - busted -c
+
+after_success:
+ - luacov-coveralls -v
+
+notifications:
+ email:
+ on_success: change
+ on_failure: always
--- /dev/null
+The MIT License (MIT)
+
+Copyright (c) 2013-2017 Daurnimator
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+UNRELEASED
+
+
+0.4 - 2017-12-09
+
+ - Fix timetable normalisation carry bugs (#10, #13)
+ - Clean up of docs
+ - No longer throw errors in parse module on error (now return nil, err)
+ - Support version 3 tzfiles
+
+
+0.3 - 2015-01-02
+
+ - Lua 5.3 support
+ - Fix bug in rfc-3339 serialisation (#4)
+
+
+0.2 - 2014-08-29
+
+ - Support for fractional timetable component normalisation
+ e.g. .month=6.5, .day=1 (which could be read as "the first day after the middle of June") normalises to .month=2, .day=16
+ - Top level aliases for common operations
+ - Own implementation of stftime formatting (locales are not yet complete)
+ - Uses ljsyscall for more accurate time when available
+
+
+0.1 - 2013-11-23
+
+ - provides a `os.date` compatible class "timetable"
+ - timezone conversion
+ - rfc3339 parsing
--- /dev/null
+# luatz
+
+A lua library for time and date manipulation.
+
+Features include:
+
+ - Normalisation of broken down date objects
+ - allows for complex time/date manipulation logic e.g. "What day is it in 2 days, 5 hours from now?"
+ - Conversion between locations (time zones) using your local [zoneinfo](https://www.iana.org/time-zones) database.
+ - `strftime` style formatting
+
+
+[![Build Status](https://travis-ci.org/daurnimator/luatz.png)](https://travis-ci.org/daurnimator/luatz) [![Coverage Status](https://coveralls.io/repos/github/daurnimator/luatz/badge.svg?branch=master)](https://coveralls.io/github/daurnimator/luatz?branch=master)
+
+Supported under Lua 5.1, 5.2, 5.3 and LuaJIT.
+
+
+## Documentation
+
+Documentation can be found in the `doc` sub-directory.
+
+An online version can be found at https://daurnimator.github.io/luatz/
+
+
+## Installation
+
+### via [luarocks](https://luarocks.org/modules/daurnimator/luatz)
+
+ luarocks install luatz
--- /dev/null
+FILES = \
+ index.md \
+ gettime.md \
+ parse.md \
+ timetable.md \
+ tzinfo.md \
+ links.md
+
+all: luatz.html luatz.pdf luatz.3
+
+luatz.html: template.html site.css metadata.yaml $(FILES)
+ pandoc -o $@ -t html5 -s --toc --template=template.html --section-divs --self-contained -c site.css metadata.yaml $(FILES)
+
+luatz.pdf: metadata.yaml $(FILES)
+ pandoc -o $@ -t latex -s --toc --toc-depth=2 -V documentclass=article -V classoption=oneside -V links-as-notes -V geometry=a4paper,includeheadfoot,margin=2.54cm metadata.yaml $(FILES)
+
+luatz.3: metadata.yaml $(FILES)
+ pandoc -o $@ -t man -s metadata.yaml $(FILES)
+
+man: luatz.3
+ man -l $^
+
+clean:
+ rm -f luatz.html luatz.pdf luatz.3
+
+.PHONY: all man install clean
--- /dev/null
+Documentation in this directory is intended to be converted to other formats using [pandoc](http://pandoc.org/).
+
+An online HTML version can be found at [https://daurnimator.github.io/luatz/](https://daurnimator.github.io/luatz/)
+
+The *Makefile* in this directory should be used to compile the documentation.
--- /dev/null
+## `luatz.gettime` <!-- --> {#gettime}
+
+A module to get the current time.
+
+Uses the most precise method available (in order:)
+
+ - Use [ljsyscall](http://www.myriabit.com/ljsyscall/) to access `clock_gettime(2)` called with `CLOCK_REALTIME`
+ - [lunix](http://25thandclement.com/~william/projects/lunix.html)'s `unix.clock_gettime()` (Only on non-Apple systems)
+ - Use [ljsyscall](http://www.myriabit.com/ljsyscall/) to access `gettimeofday(2)`
+ - [lunix](http://25thandclement.com/~william/projects/lunix.html)'s `unix.gettimeofday()`
+ - [luasocket](http://w3.impa.br/~diego/software/luasocket/)'s `socket.gettime`
+ - [Openresty](http://openresty.org/)'s [`ngx.now`](http://wiki.nginx.org/HttpLuaModule#ngx.now)
+ - [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time)
+
+### `source` <!-- --> {#gettime.source}
+
+The library/function currently in use by [`gettime()`](#gettime.gettime).
+
+
+### `resolution` <!-- --> {#gettime.resolution}
+
+The smallest time resolution (in seconds) available from [`gettime()`](#gettime.gettime).
+
+
+### `gettime()` <!-- --> {#gettime.gettime}
+
+Returns the number of seconds since unix epoch (1970-01-01T00:00:00Z) as a lua number
--- /dev/null
+## `luatz`
+
+Requiring the base luatz module will give you a table of commonly used functions and submodules.
+
+The table includes the following sub modules, which have their own documentation:
+
+ - [`parse`](#parse): Parses common date/time formats
+ - [`timetable`](#timetable): Class for date/time objects supporting normalisation
+
+### `time()` <!-- --> {#luatz.time}
+
+Returns the current unix timestamp using the most precise source available.
+See [`gettime`](#gettime) for more information.
+
+
+### `now()` <!-- --> {#luatz.now}
+
+Returns the current time as a timetable object
+See `timetable` for more information
+
+
+### `get_tz([timezone_name])` <!-- --> {#luatz.get_tz}
+
+Returns a timezone object (see `tzinfo` documentation) for the given `timezone_name`.
+If `timezone_name` is `nil` then the local timezone is used.
+If `timezone_name` is an absolute path, then that `tzinfo` file is used
+
+This uses the local [zoneinfo database](https://www.iana.org/time-zones);
+names are usually of the form `Country/Largest_City` e.g. "America/New_York".
+Check [wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for an example list.
+
+
+### `time_in(timezone_name[, utc_ts])` <!-- --> {#luatz.time_in}
+
+Returns the current time in seconds since 1970-01-01 0:00:00 in the given timezone as a string,
+(same semantics as [`get_tz`](#luatz.get_tz)) at the given UTC time (defaults to now).
+
+
+### `gmtime(ts)` <!-- --> {#luatz.gmtime}
+
+As in the C standard library
+
+
+### `localtime(ts)` <!-- --> {#luatz.localtime}
+
+As in the C standard library
+
+
+### `ctime(ts)` <!-- --> {#luatz.ctime}
+
+As in the C standard library
--- /dev/null
+# Links
+
+ - [Github](https://github.com/daurnimator/luatz)
+ - [Issue tracker](https://github.com/daurnimator/luatz/issues)
+ - [luarocks](https://luarocks.org/modules/daurnimator/luatz)
--- /dev/null
+---
+title: luatz
+subtitle: A lua library for time and date manipulation
+author: Daurnimator <quae@daurnimator.com>
+section: 3
+...
--- /dev/null
+## `luatz.parse` <!-- --> {#parse}
+
+Provides parsers for common time and date formats.
+
+Functions take the source string and an optional initial postition.
+
+### `rfc_3339(string[, init])` <!-- --> {#parse.rfc_3339}
+
+If the string is a valid RFC-3339 timestamp,
+returns a luatz timetable and the (optional) time zone offset in seconds.
+
+Otherwise returns `nil` and an error message
--- /dev/null
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box
+}
+html,
+body {
+ height: 100%
+}
+article,
+aside,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block
+}
+body {
+ margin: 0
+}
+h1,
+h2,
+h3 {
+ margin: 1rem 0
+}
+h4,
+h5,
+h6,
+ul,
+ol,
+dl,
+blockquote,
+address,
+p,
+figure {
+ margin: 0 0 1rem 0
+}
+img {
+ max-width: 100%
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-weight: 700
+}
+h1 {
+ font-size: 2.5rem;
+ line-height: 3rem
+}
+h2 {
+ font-size: 1.5rem;
+ line-height: 2rem
+}
+h3 {
+ font-size: 1.25rem;
+ line-height: 1.5rem
+}
+h4,
+h5,
+h6 {
+ font-size: 1rem;
+ line-height: 1.25rem
+}
+hr {
+ border: 0;
+ border-bottom: 1px solid;
+ margin-top: -1px;
+ margin-bottom: 1rem
+}
+a:hover {
+ color: inherit
+}
+small {
+ font-size: .875rem
+}
+ul,
+ol {
+ padding-left: 1rem
+}
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin: 0
+}
+dt {
+ font-weight: 700
+}
+dd {
+ margin: 0
+}
+blockquote {
+ border-left: 1px solid;
+ padding-left: 1rem
+}
+address {
+ font-style: normal
+}
+html {
+ color: #333;
+ font: 100%/1.5 Avenir, 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ background: #FFF;
+}
+a {
+ color: #999;
+ text-decoration: none;
+ transition: color 0.3s;
+}
+a > h1,
+a > h2,
+a > h3 {
+ color: #333;
+}
+
+body > * {
+ padding: 0 1rem;
+}
+.subtitle {
+ font-size: 1rem;
+ line-height: 1.5rem
+}
+.author {
+ display: none
+}
+@media screen and (min-width: 55rem) {
+ .meta {
+ position: fixed;
+ width: 20rem;
+ height: 100%;
+ overflow: auto;
+ background: #FFF;
+ z-index: 1;
+ }
+ main {
+ display: block; /* required for e.g. konqueror */
+ margin-left: 20rem;
+ overflow: auto;
+ }
+}
+@media print {
+ section.level1 {
+ page-break-inside: avoid
+ }
+ nav a::after {
+ content: leader('.') target-counter(attr(href url), page, decimal)
+ }
+}
--- /dev/null
+<!DOCTYPE html>
+<html$if(lang)$ lang="$lang$"$endif$$if(dir)$ dir="$dir$"$endif$>
+<head>
+ <meta charset="utf-8">
+ <meta name="generator" content="pandoc">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+$for(author-meta)$
+ <meta name="author" content="$author-meta$">
+$endfor$
+$if(date-meta)$
+ <meta name="dcterms.date" content="$date-meta$">
+$endif$
+$if(keywords)$
+ <meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$">
+$endif$
+ <title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title>
+ <style type="text/css">code{white-space: pre;}</style>
+$if(quotes)$
+ <style type="text/css">q { quotes: "“" "”" "‘" "’"; }</style>
+$endif$
+$if(highlighting-css)$
+ <style type="text/css">
+$highlighting-css$
+ </style>
+$endif$
+$for(css)$
+ <link rel="stylesheet" href="$css$">
+$endfor$
+$if(math)$
+ $math$
+$endif$
+ <!--[if lt IE 9]>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
+ <![endif]-->
+$for(header-includes)$
+ $header-includes$
+$endfor$
+</head>
+<body>
+$for(include-before)$
+$include-before$
+$endfor$
+<div class="meta">
+$if(title)$
+<header>
+<h1 class="title">$title$</h1>
+$if(subtitle)$
+<h1 class="subtitle">$subtitle$</h1>
+$endif$
+$for(author)$
+<h2 class="author">$author$</h2>
+$endfor$
+$if(date)$
+<h3 class="date">$date$</h3>
+$endif$
+</header>
+$endif$
+$if(toc)$
+<nav id="$idprefix$TOC">
+$toc$
+</nav>
+$endif$
+</div>
+<main>
+$body$
+</main>
+$for(include-after)$
+$include-after$
+$endfor$
+</body>
+</html>
--- /dev/null
+## `luatz.timetable` <!-- --> {#timetable}
+
+Provides an class to represent a time and date.
+Objects have no concept of timezone or utc offset.
+
+The fields are intentionally compatible with the lua standard library's `os.date` and `os.time`. Objects have fields:
+
+ - `year`
+ - `month`
+ - `day`
+ - `hour`
+ - `min`
+ - `sec`
+ - `yday` (optional)
+ - `wday` (optional)
+
+timetable components may be outside of their standard range (e.g. a month component of
+14) to facilitate arithmetic operations on date components. `:normalise()` can be
+called to modify components to return to their standard range.
+
+Equality and comparisons should work between timetable objects.
+
+
+### `new(year, month, day, hour, min, sec[, yday[, [wday]])` <!-- --> {#timetable.new}
+
+Returns a new timetable with the given contents.
+
+
+### `new_from_timestamp(timestamp)` <!-- --> {#timetable.new_from_timestamp}
+
+Returns a new (normalised) timetable, given a timestamp in seconds since the unix epoch of
+1970-01-01.
+
+
+### `timetable:clone()` <!-- --> {#timetable:clone}
+
+Returns a new independent instance of an existing timetable object.
+
+
+### `timetable:normalise()` <!-- --> {#timetable:normalise}
+
+Mutates the current object's time and date components so that are integers within 'normal'
+ranges e.g. `month` is `1`-`12`; `min` is `0`-`59`
+
+First, fractional parts are propagated down.
+e.g. `.month=6.5` `.day=1` (which could be read as "the first day after the middle of June")
+normalises to `.month=2` `.day=16`
+
+Second, any fields outside of their normal ranges are propagated up
+e.g. `.hour=10` `.min=100` (100 minutes past 10am)
+normalises to `.hour=11` `.min=40`
+
+
+### `timetable:rfc_3339()` <!-- --> {#timetable:rfc_3339}
+
+Returns the timetable formatted as an rfc-3339 style string.
+The timezone offset (or Z) is not appended.
+The ranges of components are not checked, if you want a valid timestamp,
+[`:normalise()`](#timetable:normalise) should be called first.
+
+This function is also the `__tostring` metamethod for timetable objects
+
+
+### `timetable:timestamp()` <!-- --> {#timetable:timestamp}
+
+Returns the timetable as the number of seconds since unix epoch (1970-01-01) as a lua number.
+
+
+### `timetable:unpack()` <!-- --> {#timetable:unpack}
+
+Unpacks the timetable object; returns `year`, `month`, `day`, `hour`, `min`, `sec`, `yday`, `wday`
--- /dev/null
+## `luatz.tzinfo` <!-- --> {#tzinfo}
+
+Provides a metatable for the timezone class.
+
+Created in `luatz.tzfile` and managed by `luatz.tzcache`;
+a timezone object contains information about a timezone.
+These objects are based on the information available in a "zoneinfo" file.
+
+Timezone objects should be considered opaque and immutable;
+so the following details can be skipped over.
+
+------------------------------------------------------------------------------
+
+The table contains a sequence of tables that describe the timezone at a given point
+using a `transition_time`: the unix timestamp (in UTC) that this definition starts, and
+a `tt_info` object.
+
+A `tt_info` object contains information about a time offset;
+and contains the following fields:
+
+ - `gmtoff` (number) The offset from GMT (UTC) in seconds
+ - `isdst` (boolean): If this change was declared as daylight savings
+ - `abbrind` (number, abbreviation id)
+ - `abbr` (string): short name for this gmt offset
+ - `isstd` (boolean)
+ - `isgmt` (boolean)
+
+
+### `tzinfo:find_current(utc_ts)` <!-- --> {#tzinfo:find_current}
+
+Returns the relevant `tt_info` object for the given UTC timestamp in the timezone.
+
+
+### `tzinfo:localise(utc_ts)` and `tzinfo:localize(utc_ts)` <!-- --> {#tzinfo:localise}
+
+Convert the given UTC timestamp to the timezone.
+Returns the number of seconds since unix epoch in the given timezone.
+
+
+### `tzinfo:utctime(local_ts)` <!-- --> {#tzinfo:utctime}
+
+Convert the given local timestamp (seconds since unix epoch in the time zone) to a UTC timestamp.
+This may result in ambigous results, in which case multiple values are returned.
+
+e.g. consider that when daylight savings rewinds your local clock from 3am to 2am there will be two 2:30ams.
--- /dev/null
+local luatz = require "luatz"
+
+-- We do this a few times ==> Convert a timestamp to timetable and normalise
+local function ts2tt(ts)
+ return luatz.timetable.new_from_timestamp(ts)
+end
+
+-- Get the current time in UTC
+local utcnow = luatz.time()
+local now = ts2tt(utcnow)
+print(now, "now (UTC)")
+
+-- Get a new time object 6 months from now
+local x = now:clone()
+x.month = x.month + 6
+x:normalise()
+print(x, "6 months from now")
+
+-- Find out what time it is in Melbourne at the moment
+local melbourne = luatz.get_tz("Australia/Melbourne")
+local now_in_melbourne = ts2tt(melbourne:localise(utcnow))
+print(now_in_melbourne, "Melbourne")
+
+-- Six months from now in melbourne (so month is incremented; but still the same time)
+local m = now_in_melbourne:clone()
+m.month = m.month + 6
+m:normalise()
+print(m, "6 months from now in melbourne")
+
+-- Convert time back to utc; a daylight savings transition may have taken place!
+-- There may be 2 results, but for we'll ignore the second possibility
+local c, _ = melbourne:utctime(m:timestamp())
+print(ts2tt(c), "6 months from now in melbourne converted to utc")
--- /dev/null
+--[[
+Re-implementation of `os.date` from the standard lua library
+]]
+
+local gettime = require "luatz.gettime".gettime
+local new_from_timestamp = require "luatz.timetable".new_from_timestamp
+local get_tz = require "luatz.tzcache".get_tz
+
+local function os_date(format_string, timestamp)
+ format_string = format_string or "%c"
+ timestamp = timestamp or gettime()
+ if format_string:sub(1, 1) == "!" then -- UTC
+ format_string = format_string:sub(2)
+ else -- Localtime
+ timestamp = get_tz():localise(timestamp)
+ end
+ local tt = new_from_timestamp(timestamp)
+ if format_string == "*t" then
+ return tt
+ else
+ return tt:strftime(format_string)
+ end
+end
+
+return os_date
--- /dev/null
+package = "luatz"
+version = "scm-0"
+
+description = {
+ summary = "library for time and date manipulation.";
+ detailed = [[
+ A lua library for time and date manipulation.
+
+ Features include:
+ - Normalisation of broken down date objects
+ - allows for complex time/date manipulation logic e.g. "what day is it in 2 days, 5 hours from now?"
+ - Conversion between locations (time zones) using your local zoneinfo database.
+ - strftime style formatting
+
+ All operations are possible without C extensions, though if available they may be used to increase accuracy.
+ ]];
+ license = "MIT";
+}
+
+dependencies = {
+ "lua >= 5.1";
+}
+
+source = {
+ url = "git://github.com/daurnimator/luatz.git";
+}
+
+build = {
+ type = "builtin";
+ modules = {
+ ["luatz.init"] = "luatz/init.lua";
+ ["luatz.gettime"] = "luatz/gettime.lua";
+ ["luatz.parse"] = "luatz/parse.lua";
+ ["luatz.timetable"] = "luatz/timetable.lua";
+ ["luatz.strftime"] = "luatz/strftime.lua";
+ ["luatz.tzcache"] = "luatz/tzcache.lua";
+ ["luatz.tzfile"] = "luatz/tzfile.lua";
+ ["luatz.tzinfo"] = "luatz/tzinfo.lua";
+ };
+}
--- /dev/null
+local _M = {}
+
+_M.source, _M.resolution, _M.gettime = (function()
+ local has_syscall, syscall = pcall(require, "syscall")
+ if has_syscall and syscall.clock_gettime and syscall.c.CLOCK then
+ local clock_id = syscall.c.CLOCK.REALTIME
+ local function timespec_to_number(timespec)
+ return tonumber(timespec.tv_sec) + tonumber(timespec.tv_nsec) * 1e-9
+ end
+ return "syscall.clock_gettime(CLOCK_REALTIME)",
+ syscall.clock_getres and timespec_to_number(syscall.clock_getres(clock_id)) or 1e-9,
+ function()
+ return timespec_to_number(syscall.clock_gettime(clock_id))
+ end
+ end
+
+ local has_unix, unix = pcall(require, "unix")
+ -- On Apple devices lunix only uses gettimeofday()
+ if has_unix and unix.clock_gettime and unix.uname and unix.uname().sysname ~= "Darwin" then
+ return "unix.clock_gettime(CLOCK_REALTIME)", 1e-9, function()
+ return unix.clock_gettime()
+ end
+ end
+
+ if has_syscall and syscall.gettimeofday then
+ local function timeval_to_number(timeval)
+ return tonumber(timeval.tv_sec) + tonumber(timeval.tv_nsec) * 1e-6
+ end
+ return "syscall.gettimeofday()", 1e-6,
+ function()
+ return timeval_to_number(syscall.gettimeofday())
+ end
+ end
+
+ if has_unix and unix.gettimeofday then
+ return "unix.gettimeofday()", 1e-6, unix.gettimeofday
+ end
+
+ local has_socket, socket = pcall(require, "socket")
+ if has_socket and socket.gettime then
+ -- on windows, this uses GetSystemTimeAsFileTime, which has resolution of 1e-7
+ -- on linux, this uses gettimeofday, which has resolution of 1e-6
+ return "socket.gettime()", 1e-6, socket.gettime
+ end
+
+ if ngx and ngx.now then -- luacheck: ignore 113
+ return "ngx.now()", 1e-3, ngx.now -- luacheck: ignore 113
+ end
+
+ return "os.time()", 1, os.time
+end)()
+
+return _M
--- /dev/null
+local _M = {
+ gettime = require "luatz.gettime";
+ parse = require "luatz.parse";
+ strftime = require "luatz.strftime";
+ timetable = require "luatz.timetable";
+ tzcache = require "luatz.tzcache";
+}
+
+--- Top-level aliases for common functions
+
+_M.time = _M.gettime.gettime
+_M.get_tz = _M.tzcache.get_tz
+
+--- Handy functions
+
+_M.time_in = function(tz, now)
+ return _M.get_tz(tz):localize(now)
+end
+
+_M.now = function()
+ local ts = _M.gettime.gettime()
+ return _M.timetable.new_from_timestamp(ts)
+end
+
+--- C-like functions
+
+_M.gmtime = function(ts)
+ return _M.timetable.new_from_timestamp(ts)
+end
+
+_M.localtime = function(ts)
+ ts = _M.time_in(nil, ts)
+ return _M.gmtime(ts)
+end
+
+_M.ctime = function(ts)
+ return _M.strftime.asctime(_M.localtime(ts))
+end
+
+return _M
--- /dev/null
+local new_timetable = require "luatz.timetable".new
+
+--- Parse an RFC 3339 datetime at the given position
+-- Returns a time table and the `tz_offset`
+-- Return value is not normalised (this preserves a leap second)
+-- If the timestamp is only partial (i.e. missing "Z" or time offset) then `tz_offset` will be nil
+-- TODO: Validate components are within their boundarys (e.g. 1 <= month <= 12)
+local function rfc_3339(str, init)
+ local year, month, day, hour, min, sec, patt_end = str:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)()", init) -- luacheck: ignore 631
+ if not year then
+ return nil, "Invalid RFC 3339 timestamp"
+ end
+ year = tonumber(year, 10)
+ month = tonumber(month, 10)
+ day = tonumber(day, 10)
+ hour = tonumber(hour, 10)
+ min = tonumber(min, 10)
+ sec = tonumber(sec, 10)
+
+ local tt = new_timetable(year, month, day, hour, min, sec)
+
+ local tz_offset
+ if str:match("^[Zz]", patt_end) then
+ tz_offset = 0
+ else
+ local hour_offset, min_offset = str:match("^([+-]%d%d):(%d%d)", patt_end)
+ if hour_offset then
+ tz_offset = tonumber(hour_offset, 10) * 3600 + tonumber(min_offset, 10) * 60
+ else -- luacheck: ignore 542
+ -- Invalid RFC 3339 timestamp offset (should be Z or (+/-)hour:min)
+ -- tz_offset will be nil
+ end
+ end
+
+ return tt, tz_offset
+end
+
+return {
+ rfc_3339 = rfc_3339;
+}
--- /dev/null
+local strformat = string.format
+local floor = math.floor
+local function idiv(n, d)
+ return floor(n / d)
+end
+
+local c_locale = {
+ abday = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+ day = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
+ abmon = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+ mon = {"January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"};
+ am_pm = {"AM", "PM"};
+}
+
+--- ISO-8601 week logic
+-- ISO 8601 weekday as number with Monday as 1 (1-7)
+local function iso_8601_weekday(wday)
+ if wday == 1 then
+ return 7
+ else
+ return wday - 1
+ end
+end
+local iso_8601_week do
+ -- Years that have 53 weeks according to ISO-8601
+ local long_years = {}
+ for _, v in ipairs {
+ 4, 9, 15, 20, 26, 32, 37, 43, 48, 54, 60, 65, 71, 76, 82,
+ 88, 93, 99, 105, 111, 116, 122, 128, 133, 139, 144, 150, 156, 161, 167,
+ 172, 178, 184, 189, 195, 201, 207, 212, 218, 224, 229, 235, 240, 246, 252,
+ 257, 263, 268, 274, 280, 285, 291, 296, 303, 308, 314, 320, 325, 331, 336,
+ 342, 348, 353, 359, 364, 370, 376, 381, 387, 392, 398
+ } do
+ long_years[v] = true
+ end
+ local function is_long_year(year)
+ return long_years[year % 400]
+ end
+ function iso_8601_week(self)
+ local wday = iso_8601_weekday(self.wday)
+ local n = self.yday - wday
+ local year = self.year
+ if n < -3 then
+ year = year - 1
+ if is_long_year(year) then
+ return year, 53, wday
+ else
+ return year, 52, wday
+ end
+ elseif n >= 361 and not is_long_year(year) then
+ return year + 1, 1, wday
+ else
+ return year, idiv(n + 10, 7), wday
+ end
+ end
+end
+
+--- Specifiers
+local t = {}
+function t:a(locale)
+ return "%s", locale.abday[self.wday]
+end
+function t:A(locale)
+ return "%s", locale.day[self.wday]
+end
+function t:b(locale)
+ return "%s", locale.abmon[self.month]
+end
+function t:B(locale)
+ return "%s", locale.mon[self.month]
+end
+function t:c(locale)
+ return "%.3s %.3s%3d %.2d:%.2d:%.2d %d",
+ locale.abday[self.wday], locale.abmon[self.month],
+ self.day, self.hour, self.min, self.sec, self.year
+end
+-- Century
+function t:C()
+ return "%02d", idiv(self.year, 100)
+end
+function t:d()
+ return "%02d", self.day
+end
+-- Short MM/DD/YY date, equivalent to %m/%d/%y
+function t:D()
+ return "%02d/%02d/%02d", self.month, self.day, self.year % 100
+end
+function t:e()
+ return "%2d", self.day
+end
+-- Short YYYY-MM-DD date, equivalent to %Y-%m-%d
+function t:F()
+ return "%d-%02d-%02d", self.year, self.month, self.day
+end
+-- Week-based year, last two digits (00-99)
+function t:g()
+ return "%02d", iso_8601_week(self) % 100
+end
+-- Week-based year
+function t:G()
+ return "%d", iso_8601_week(self)
+end
+t.h = t.b
+function t:H()
+ return "%02d", self.hour
+end
+function t:I()
+ return "%02d", (self.hour-1) % 12 + 1
+end
+function t:j()
+ return "%03d", self.yday
+end
+function t:m()
+ return "%02d", self.month
+end
+function t:M()
+ return "%02d", self.min
+end
+-- New-line character ('\n')
+function t:n() -- luacheck: ignore 212
+ return "\n"
+end
+function t:p(locale)
+ return self.hour < 12 and locale.am_pm[1] or locale.am_pm[2]
+end
+-- TODO: should respect locale
+function t:r(locale)
+ return "%02d:%02d:%02d %s",
+ (self.hour-1) % 12 + 1, self.min, self.sec,
+ self.hour < 12 and locale.am_pm[1] or locale.am_pm[2]
+end
+-- 24-hour HH:MM time, equivalent to %H:%M
+function t:R()
+ return "%02d:%02d", self.hour, self.min
+end
+function t:s()
+ return "%d", self:timestamp()
+end
+function t:S()
+ return "%02d", self.sec
+end
+-- Horizontal-tab character ('\t')
+function t:t() -- luacheck: ignore 212
+ return "\t"
+end
+-- ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S
+function t:T()
+ return "%02d:%02d:%02d", self.hour, self.min, self.sec
+end
+function t:u()
+ return "%d", iso_8601_weekday(self.wday)
+end
+-- Week number with the first Sunday as the first day of week one (00-53)
+function t:U()
+ return "%02d", idiv(self.yday - self.wday + 7, 7)
+end
+-- ISO 8601 week number (00-53)
+function t:V()
+ return "%02d", select(2, iso_8601_week(self))
+end
+-- Weekday as a decimal number with Sunday as 0 (0-6)
+function t:w()
+ return "%d", self.wday - 1
+end
+-- Week number with the first Monday as the first day of week one (00-53)
+function t:W()
+ return "%02d", idiv(self.yday - iso_8601_weekday(self.wday) + 7, 7)
+end
+-- TODO make t.x and t.X respect locale
+t.x = t.D
+t.X = t.T
+function t:y()
+ return "%02d", self.year % 100
+end
+function t:Y()
+ return "%d", self.year
+end
+-- TODO timezones
+function t:z() -- luacheck: ignore 212
+ return "+0000"
+end
+function t:Z() -- luacheck: ignore 212
+ return "GMT"
+end
+-- A literal '%' character.
+t["%"] = function(self) -- luacheck: ignore 212
+ return "%%"
+end
+
+local function strftime(format_string, timetable)
+ return (string.gsub(format_string, "%%([EO]?)(.)", function(locale_modifier, specifier)
+ local func = t[specifier]
+ if func then
+ return strformat(func(timetable, c_locale))
+ else
+ error("invalid conversation specifier '%"..locale_modifier..specifier.."'", 3)
+ end
+ end))
+end
+
+local function asctime(timetable)
+ -- Equivalent to the format string "%c\n"
+ return strformat(t.c(timetable, c_locale)) .. "\n"
+end
+
+return {
+ strftime = strftime;
+ asctime = asctime;
+}
--- /dev/null
+local strftime = require "luatz.strftime".strftime
+local strformat = string.format
+local floor = math.floor
+local idiv do
+ -- Try and use actual integer division when available (Lua 5.3+)
+ local idiv_loader = (loadstring or load)([[return function(n,d) return n//d end]], "idiv") -- luacheck: ignore 113
+ if idiv_loader then
+ idiv = idiv_loader()
+ else
+ idiv = function(n, d)
+ return floor(n/d)
+ end
+ end
+end
+
+
+local mon_lengths = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+-- Number of days in year until start of month; not corrected for leap years
+local months_to_days_cumulative = {0}
+for i = 2, 12 do
+ months_to_days_cumulative[i] = months_to_days_cumulative[i-1] + mon_lengths[i-1]
+end
+-- For Sakamoto's Algorithm (day of week)
+local sakamoto = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+
+local function is_leap(y)
+ if (y % 4) ~= 0 then
+ return false
+ elseif (y % 100) ~= 0 then
+ return true
+ else
+ return (y % 400) == 0
+ end
+end
+
+local function month_length(m, y)
+ if m == 2 then
+ return is_leap(y) and 29 or 28
+ else
+ return mon_lengths[m]
+ end
+end
+
+local function leap_years_since(year)
+ return idiv(year, 4) - idiv(year, 100) + idiv(year, 400)
+end
+
+local function day_of_year(day, month, year)
+ local yday = months_to_days_cumulative[month]
+ if month > 2 and is_leap(year) then
+ yday = yday + 1
+ end
+ return yday + day
+end
+
+local function day_of_week(day, month, year)
+ if month < 3 then
+ year = year - 1
+ end
+ return(year + leap_years_since(year) + sakamoto[month] + day) % 7 + 1
+end
+
+local function borrow(tens, units, base)
+ local frac = tens % 1
+ units = units + frac * base
+ tens = tens - frac
+ return tens, units
+end
+
+local function carry(tens, units, base)
+ if units >= base then
+ tens = tens + idiv(units, base)
+ units = units % base
+ elseif units < 0 then
+ tens = tens + idiv(units, base)
+ units = (base + units) % base
+ end
+ return tens, units
+end
+
+-- Modify parameters so they all fit within the "normal" range
+local function normalise(year, month, day, hour, min, sec)
+ -- `month` and `day` start from 1, need -1 and +1 so it works modulo
+ month, day = month - 1, day - 1
+
+ -- Convert everything (except seconds) to an integer
+ -- by propagating fractional components down.
+ year , month = borrow(year , month, 12)
+ -- Carry from month to year first, so we get month length correct in next line around leap years
+ year , month = carry(year, month, 12)
+ month, day = borrow(month, day , month_length(floor(month + 1), year))
+ day , hour = borrow(day , hour , 24)
+ hour , min = borrow(hour , min , 60)
+ min , sec = borrow(min , sec , 60)
+
+ -- Propagate out of range values up
+ -- e.g. if `min` is 70, `hour` increments by 1 and `min` becomes 10
+ -- This has to happen for all columns after borrowing, as lower radixes may be pushed out of range
+ min , sec = carry(min , sec , 60) -- TODO: consider leap seconds?
+ hour , min = carry(hour, min , 60)
+ day , hour = carry(day , hour, 24)
+ -- Ensure `day` is not underflowed
+ -- Add a whole year of days at a time, this is later resolved by adding months
+ -- TODO[OPTIMIZE]: This could be slow if `day` is far out of range
+ while day < 0 do
+ month = month - 1
+ if month < 0 then
+ year = year - 1
+ month = 11
+ end
+ day = day + month_length(month + 1, year)
+ end
+ year, month = carry(year, month, 12)
+
+ -- TODO[OPTIMIZE]: This could potentially be slow if `day` is very large
+ while true do
+ local i = month_length(month + 1, year)
+ if day < i then break end
+ day = day - i
+ month = month + 1
+ if month >= 12 then
+ month = 0
+ year = year + 1
+ end
+ end
+
+ -- Now we can place `day` and `month` back in their normal ranges
+ -- e.g. month as 1-12 instead of 0-11
+ month, day = month + 1, day + 1
+
+ return year, month, day, hour, min, sec
+end
+
+local leap_years_since_1970 = leap_years_since(1970)
+local function timestamp(year, month, day, hour, min, sec)
+ year, month, day, hour, min, sec = normalise(year, month, day, hour, min, sec)
+
+ local days_since_epoch = day_of_year(day, month, year)
+ + 365 * (year - 1970)
+ -- Each leap year adds one day
+ + (leap_years_since(year - 1) - leap_years_since_1970) - 1
+
+ return days_since_epoch * (60*60*24)
+ + hour * (60*60)
+ + min * 60
+ + sec
+end
+
+
+local timetable_methods = {}
+
+function timetable_methods:unpack()
+ return assert(self.year , "year required"),
+ assert(self.month, "month required"),
+ assert(self.day , "day required"),
+ self.hour or 12,
+ self.min or 0,
+ self.sec or 0,
+ self.yday,
+ self.wday
+end
+
+function timetable_methods:normalise()
+ local year, month, day
+ year, month, day, self.hour, self.min, self.sec = normalise(self:unpack())
+
+ self.day = day
+ self.month = month
+ self.year = year
+ self.yday = day_of_year(day, month, year)
+ self.wday = day_of_week(day, month, year)
+
+ return self
+end
+timetable_methods.normalize = timetable_methods.normalise -- American English
+
+function timetable_methods:timestamp()
+ return timestamp(self:unpack())
+end
+
+function timetable_methods:rfc_3339()
+ local year, month, day, hour, min, fsec = self:unpack()
+ local sec, msec = borrow(fsec, 0, 1000)
+ msec = math.floor(msec)
+ return strformat("%04u-%02u-%02uT%02u:%02u:%02d.%03d", year, month, day, hour, min, sec, msec)
+end
+
+function timetable_methods:strftime(format_string)
+ return strftime(format_string, self)
+end
+
+local timetable_mt
+
+local function coerce_arg(t)
+ if getmetatable(t) == timetable_mt then
+ return t:timestamp()
+ end
+ return t
+end
+
+timetable_mt = {
+ __index = timetable_methods;
+ __tostring = timetable_methods.rfc_3339;
+ __eq = function(a, b)
+ return a:timestamp() == b:timestamp()
+ end;
+ __lt = function(a, b)
+ return a:timestamp() < b:timestamp()
+ end;
+ __sub = function(a, b)
+ return coerce_arg(a) - coerce_arg(b)
+ end;
+}
+
+local function cast_timetable(tm)
+ return setmetatable(tm, timetable_mt)
+end
+
+local function new_timetable(year, month, day, hour, min, sec, yday, wday)
+ return cast_timetable {
+ year = year;
+ month = month;
+ day = day;
+ hour = hour;
+ min = min;
+ sec = sec;
+ yday = yday;
+ wday = wday;
+ }
+end
+
+function timetable_methods:clone()
+ return new_timetable(self:unpack())
+end
+
+local function new_from_timestamp(ts)
+ if type(ts) ~= "number" then
+ error("bad argument #1 to 'new_from_timestamp' (number expected, got " .. type(ts) .. ")", 2)
+ end
+ return new_timetable(1970, 1, 1, 0, 0, ts):normalise()
+end
+
+return {
+ is_leap = is_leap;
+ day_of_year = day_of_year;
+ day_of_week = day_of_week;
+ normalise = normalise;
+ timestamp = timestamp;
+
+ new = new_timetable;
+ new_from_timestamp = new_from_timestamp;
+ cast = cast_timetable;
+ timetable_mt = timetable_mt;
+}
--- /dev/null
+local read_tzfile = require "luatz.tzfile".read_tzfile
+
+local base_zoneinfo_path = "/usr/share/zoneinfo/"
+local local_zoneinfo_path = "/etc/localtime"
+local tz_cache = {}
+
+local function name_to_zoneinfo_path(name)
+ if name == nil then
+ return local_zoneinfo_path
+ elseif name:sub(1, 1) == "/" then
+ return name
+ else
+ return base_zoneinfo_path .. name
+ end
+end
+
+local function clear_tz_cache(name)
+ tz_cache[name_to_zoneinfo_path(name)] = nil
+end
+
+local function get_tz(name)
+ local path = name_to_zoneinfo_path(name)
+ -- TODO: stat path
+ local tzinfo = tz_cache[path]
+ if tzinfo == nil then
+ tzinfo = read_tzfile(path)
+ tz_cache[path] = tzinfo
+ end
+ return tzinfo
+end
+
+return {
+ get_tz = get_tz;
+ clear_tz_cache = clear_tz_cache;
+}
--- /dev/null
+local tz_info_mt = require "luatz.tzinfo".tz_info_mt
+local tt_info_mt = require "luatz.tzinfo".tt_info_mt
+
+local read_int32be, read_int64be
+
+-- luacheck: push std max
+if string.unpack then
+ -- Only available in Lua 5.3+
+ function read_int32be(fd)
+ local data, err = fd:read(4)
+ if data == nil then return nil, err end
+ return string.unpack(">i4", data)
+ end
+
+ function read_int64be(fd)
+ local data, err = fd:read(8)
+ if data == nil then return nil, err end
+ return string.unpack(">i8", data)
+ end
+else -- luacheck: pop
+ function read_int32be(fd)
+ local data, err = fd:read(4)
+ if data == nil then return nil, err end
+ local o1, o2, o3, o4 = data:byte(1, 4)
+
+ local unsigned = o4 + o3*2^8 + o2*2^16 + o1*2^24
+ if unsigned >= 2^31 then
+ return unsigned - 2^32
+ else
+ return unsigned
+ end
+ end
+
+ function read_int64be(fd)
+ local data, err = fd:read(8)
+ if data == nil then return nil, err end
+ local o1, o2, o3, o4, o5, o6, o7, o8 = data:byte(1, 8)
+
+ local unsigned = o8 + o7*2^8 + o6*2^16 + o5*2^24 + o4*2^32 + o3*2^40 + o2*2^48 + o1*2^56
+ if unsigned >= 2^63 then
+ return unsigned - 2^64
+ else
+ return unsigned
+ end
+ end
+end
+
+local function read_flags(fd, n)
+ local data, err = fd:read(n)
+ if data == nil then return nil, err end
+
+ local res = {}
+ for i=1, n do
+ res[i] = data:byte(i,i) ~= 0
+ end
+ return res
+end
+
+local fifteen_nulls = ("\0"):rep(15)
+local function read_tz(fd)
+ assert(fd:read(4) == "TZif", "Invalid TZ file")
+ local version = assert(fd:read(1))
+ if version == "\0" or version == "2" or version == "3" then
+ local MIN_TIME = -2^32+1
+
+ assert(assert(fd:read(15)) == fifteen_nulls, "Expected 15 nulls")
+
+ -- The number of UTC/local indicators stored in the file.
+ local tzh_ttisgmtcnt = assert(read_int32be(fd))
+
+ -- The number of standard/wall indicators stored in the file.
+ local tzh_ttisstdcnt = assert(read_int32be(fd))
+
+ -- The number of leap seconds for which data is stored in the file.
+ local tzh_leapcnt = assert(read_int32be(fd))
+
+ -- The number of "transition times" for which data is stored in the file.
+ local tzh_timecnt = assert(read_int32be(fd))
+
+ -- The number of "local time types" for which data is stored in the file (must not be zero).
+ local tzh_typecnt = assert(read_int32be(fd))
+
+ -- The number of characters of "timezone abbreviation strings" stored in the file.
+ local tzh_charcnt = assert(read_int32be(fd))
+
+ local transition_times = {}
+ for i=1, tzh_timecnt do
+ transition_times[i] = assert(read_int32be(fd))
+ end
+ local transition_time_ind = {assert(fd:read(tzh_timecnt)):byte(1, -1)}
+
+ local ttinfos = {}
+ for i=1, tzh_typecnt do
+ ttinfos[i] = {
+ gmtoff = assert(read_int32be(fd));
+ isdst = assert(fd:read(1)) ~= "\0";
+ abbrind = assert(fd:read(1)):byte();
+ }
+ end
+
+ local abbreviations = assert(fd:read(tzh_charcnt))
+
+ local leap_seconds = {} -- luacheck: ignore 241
+ for i=1, tzh_leapcnt do
+ leap_seconds[i] = {
+ offset = assert(read_int32be(fd));
+ n = assert(read_int32be(fd));
+ }
+ end
+
+ local isstd = assert(read_flags(fd, tzh_ttisstdcnt))
+
+ local isgmt = assert(read_flags(fd, tzh_ttisgmtcnt))
+
+ local TZ
+
+ if version == "2" or version == "3" then
+ --[[
+ For version-2-format timezone files, the above header and data is followed by a second header and data,
+ identical in format except that eight bytes are used for each transition time or leap-second time.
+ ]]
+ assert(fd:read(4) == "TZif")
+ assert(fd:read(1) == version)
+ assert(assert(fd:read(15)) == fifteen_nulls, "Expected 15 nulls")
+
+ MIN_TIME = -2^64+1
+
+ -- The number of UTC/local indicators stored in the file.
+ tzh_ttisgmtcnt = assert(read_int32be(fd))
+
+ -- The number of standard/wall indicators stored in the file.
+ tzh_ttisstdcnt = assert(read_int32be(fd))
+
+ -- The number of leap seconds for which data is stored in the file.
+ tzh_leapcnt = assert(read_int32be(fd))
+
+ -- The number of "transition times" for which data is stored in the file.
+ tzh_timecnt = assert(read_int32be(fd))
+
+ -- The number of "local time types" for which data is stored in the file (must not be zero).
+ tzh_typecnt = assert(read_int32be(fd))
+
+ -- The number of characters of "timezone abbreviation strings" stored in the file.
+ tzh_charcnt = assert(read_int32be(fd))
+
+ transition_times = {}
+ for i=1, tzh_timecnt do
+ transition_times[i] = assert(read_int64be(fd))
+ end
+ transition_time_ind = {assert(fd:read(tzh_timecnt)):byte(1, -1)}
+
+ ttinfos = {}
+ for i=1, tzh_typecnt do
+ ttinfos[i] = {
+ gmtoff = assert(read_int32be(fd));
+ isdst = assert(fd:read(1)) ~= "\0";
+ abbrind = assert(fd:read(1)):byte();
+ }
+ end
+
+ abbreviations = assert(fd:read(tzh_charcnt))
+
+ leap_seconds = {}
+ for i=1, tzh_leapcnt do
+ leap_seconds[i] = {
+ offset = assert(read_int64be(fd));
+ n = assert(read_int32be(fd));
+ }
+ end
+
+ isstd = assert(read_flags(fd, tzh_ttisstdcnt))
+
+ isgmt = assert(read_flags(fd, tzh_ttisgmtcnt))
+
+ --[[
+ After the second header and data comes a newline-enclosed, POSIX-TZ-environment-variable-style string
+ for use in handling instants after the last transition time stored in the file
+ (with nothing between the newlines if there is no POSIX representation for such instants).
+ ]]
+
+ --[[
+ For version-3-format time zone files, the POSIX-TZ-style string may
+ use two minor extensions to the POSIX TZ format, as described in newtzset (3).
+ First, the hours part of its transition times may be signed and range from
+ -167 through 167 instead of the POSIX-required unsigned values
+ from 0 through 24. Second, DST is in effect all year if it starts
+ January 1 at 00:00 and ends December 31 at 24:00 plus the difference
+ between daylight saving and standard time.
+ ]]
+
+ assert(assert(fd:read(1)) == "\n", "Expected newline at end of version 2 header")
+
+ TZ = assert(fd:read("*l"))
+ if #TZ == 0 then
+ TZ = nil
+ end
+ end
+
+ for i=1, tzh_typecnt do
+ local v = ttinfos[i]
+ v.abbr = abbreviations:sub(v.abbrind+1, v.abbrind+3)
+ v.isstd = isstd[i] or false
+ v.isgmt = isgmt[i] or false
+ setmetatable(v, tt_info_mt)
+ end
+
+ --[[
+ Use the first standard-time ttinfo structure in the file
+ (or simply the first ttinfo structure in the absence of a standard-time structure)
+ if either tzh_timecnt is zero or the time argument is less than the first transition time recorded in the file.
+ ]]
+ local first = 1
+ do
+ for i=1, tzh_ttisstdcnt do
+ if isstd[i] then
+ first = i
+ break
+ end
+ end
+ end
+
+ local res = {
+ future = TZ;
+ [0] = {
+ transition_time = MIN_TIME;
+ info = ttinfos[first];
+ }
+ }
+ for i=1, tzh_timecnt do
+ res[i] = {
+ transition_time = transition_times[i];
+ info = ttinfos[transition_time_ind[i]+1];
+ }
+ end
+ return setmetatable(res, tz_info_mt)
+ else
+ error("Unsupported version")
+ end
+end
+
+local function read_tzfile(path)
+ local fd = assert(io.open(path, "rb"))
+ local tzinfo = read_tz(fd)
+ fd:close()
+ return tzinfo
+end
+
+return {
+ read_tz = read_tz;
+ read_tzfile = read_tzfile;
+}
--- /dev/null
+local gettime = require "luatz.gettime".gettime
+local timetable_mt = require "luatz.timetable".timetable_mt
+
+local function to_timestamp(o)
+ if type(o) == "number" then
+ return o
+ elseif getmetatable(o) == timetable_mt then
+ return o:timestamp()
+ end
+end
+
+local tz_info_methods = { }
+local tz_info_mt = {
+ __name = "luatz.tz_info";
+ __index = tz_info_methods;
+}
+local tt_info_mt = {
+ __name = "luatz.tt_info";
+ __tostring = function(self)
+ return string.format("tt_info:%s=%d", self.abbr, self.gmtoff)
+ end;
+}
+
+-- Binary search
+local function find_current(tzinfo, target, i, j)
+ if i >= j then return j end
+
+ local half = math.ceil((j+i) / 2)
+
+ if target >= tzinfo[half].transition_time then
+ return find_current(tzinfo, target, half, j)
+ else
+ return find_current(tzinfo, target, i, half-1)
+ end
+end
+
+local function find_current_local(tzinfo, ts_local)
+ -- Find two best possibilities by searching back and forward a day (assumes transition is never by more than 24 hours)
+ local tz_first = find_current(tzinfo, ts_local-86400, 0, #tzinfo)
+ local tz_last = find_current(tzinfo, ts_local+86400, 0, #tzinfo)
+
+ local n_candidates = tz_last - tz_first + 1
+
+ if n_candidates == 1 then
+ return tz_first
+ elseif n_candidates == 2 then
+ local tz_first_ob = tzinfo[tz_first]
+ local tz_last_ob = tzinfo[tz_last]
+
+ local first_gmtoffset = tz_first_ob.info.gmtoff
+ local last_gmtoffset = tz_last_ob .info.gmtoff
+
+ local t_start = tz_last_ob.transition_time + first_gmtoffset
+ local t_end = tz_last_ob.transition_time + last_gmtoffset
+
+ -- If timestamp is before start or after end
+ if ts_local < t_start then
+ return tz_first
+ elseif ts_local > t_end then
+ return tz_last
+ end
+
+ -- If we get this far, the local time is ambiguous
+ return tz_first, tz_last
+ else
+ error("Too many transitions in a 2 day period")
+ end
+end
+
+function tz_info_methods:find_current(current)
+ current = assert(to_timestamp(current), "invalid timestamp to :find_current")
+ return self[find_current(self, current, 0, #self)].info
+end
+
+function tz_info_methods:localise(utc_ts)
+ utc_ts = utc_ts or gettime()
+ return utc_ts + self:find_current(utc_ts).gmtoff
+end
+tz_info_methods.localize = tz_info_methods.localise
+
+function tz_info_methods:utctime(ts_local)
+ ts_local = assert(to_timestamp(ts_local), "invalid timestamp to :utctime")
+ local tz1, tz2 = find_current_local(self, ts_local)
+ tz1 = self[tz1].info
+ if tz2 == nil then
+ return ts_local - tz1.gmtoff
+ else -- Local time is ambiguous
+ tz2 = self[tz2].info
+
+ return ts_local - tz2.gmtoff, ts_local - tz2.gmtoff
+ end
+end
+
+return {
+ tz_info_mt = tz_info_mt;
+ tt_info_mt = tt_info_mt;
+}
--- /dev/null
+describe("Time parsing library", function()
+ local timetable = require "luatz.timetable"
+ local parse = require "luatz.parse"
+
+ it("#RFC3339 parsing", function()
+ assert.same(timetable.new(2013,10,22,14,17,02), (parse.rfc_3339 "2013-10-22T14:17:02Z"))
+
+ -- Numeric offsets accepted
+ assert.same({timetable.new(2013,10,22,14,17,02), 10*3600 }, {parse.rfc_3339 "2013-10-22T14:17:02+10:00" })
+
+ -- Missing offsets parse
+ assert.same(timetable.new(2013,10,22,14,17,02), (parse.rfc_3339 "2013-10-22T14:17:02"))
+
+ -- Invalid
+ assert.same(nil, (parse.rfc_3339 "an invalid timestamp"))
+ end)
+end)
--- /dev/null
+local luatz = require "luatz.init"
+local time = 1234567890
+local base_tt = luatz.gmtime(time)
+describe("#strftime works the same as os.date", function()
+ local strftime = luatz.strftime.strftime
+ for _, spec in ipairs {
+ "a", "A", "b", "B", "c", "C", "d", "D", "e", "F",
+ "g", "G", "H", "I", "j", "m", "M", "n", "p", "r",
+ "R", --[["s",]] "S", "t", "T", "u", "U", "V", "w", "W",
+ "y", "Y", "z", "Z" , "%"
+ } do
+ local tt = base_tt:clone()
+ local f = "%"..spec
+ local osdf = "!%"..spec
+ it("format specifier '"..f.."' is equivalent to os.date('"..osdf.."')", function()
+ for i=1, 365*12 do
+ local t = time + 60*60*24*i
+ tt.day = tt.day + 1
+ tt:normalise()
+ assert.are.same(os.date(osdf,t), strftime(f,tt))
+ end
+ end)
+ end
+end)
+describe("#asctime", function()
+ local asctime = luatz.strftime.asctime
+ it("should format correctly", function()
+ assert.are.same("Fri Feb 13 23:31:30 2009\n", asctime(base_tt))
+ end)
+end)
--- /dev/null
+describe("Timetable library", function()
+ local timetable = require "luatz.timetable"
+
+ local function native_normalise(year, month, day)
+ return os.date("*t",os.time {
+ year = year;
+ month = month;
+ day = day;
+ })
+ end
+
+ it("#is_leap is correct", function()
+ assert.same(false, timetable.is_leap(1))
+ assert.same(false, timetable.is_leap(3))
+ assert.same(true , timetable.is_leap(4))
+ assert.same(true , timetable.is_leap(2000))
+ assert.same(true , timetable.is_leap(2004))
+ assert.same(true , timetable.is_leap(2012))
+ assert.same(false, timetable.is_leap(2013))
+ assert.same(false, timetable.is_leap(2014))
+ assert.same(false, timetable.is_leap(2100))
+ assert.same(true , timetable.is_leap(2400))
+ end)
+
+ it("#normalise gets #wday (day of week) correct", function()
+
+ local function assert_same_wday(year, month, day)
+ return assert.are.same(
+ native_normalise(year, month, day).wday,
+ timetable.new(year, month, day):normalise().wday
+ )
+ end
+
+ assert_same_wday(2013, 7, 23)
+ assert_same_wday(2013, 7, 24)
+ assert_same_wday(2013, 7, 25)
+ assert_same_wday(2013, 7, 26)
+ assert_same_wday(2013, 7, 27)
+ assert_same_wday(2013, 7, 28)
+ assert_same_wday(2013, 7, 29)
+ assert_same_wday(2014, 1, 1)
+ assert_same_wday(2014, 1, 6)
+ assert_same_wday(2016, 2, 28)
+ assert_same_wday(2016, 2, 29)
+ assert_same_wday(2016, 3, 1)
+ end)
+
+ local function native_timestamp(year, month, day)
+ return assert(tonumber(assert(io.popen(
+ string.format('date -u -d "%d-%d-%d" +%%s', year, month, day)
+ )):read "*l"))
+ end
+
+ it("#timestamp creation is valid", function()
+ for y=1950,2013 do
+ for m=1,12 do
+ assert.same(native_timestamp(y,m,1), timetable.timestamp(y,m,1,0,0,0))
+ end
+ end
+ end)
+
+ it("#normalise handles out of range days in a year", function()
+ assert.same({2014,1,1,0,0,0}, {timetable.normalise(2013,1,366,0,0,0)})
+ assert.same({2014,2,4,0,0,0}, {timetable.normalise(2013,1,400,0,0,0)})
+ assert.same({2017,2,3,0,0,0}, {timetable.normalise(2016,1,400,0,0,0)})
+ assert.same({2016,3,5,0,0,0}, {timetable.normalise(2015,1,430,0,0,0)})
+ assert.same({2017,3,5,0,0,0}, {timetable.normalise(2016,1,430,0,0,0)})
+ assert.same({2027,5,18,0,0,0}, {timetable.normalise(2000,1,10000,0,0,0)})
+ assert.same({29379,1,25,0,0,0}, {timetable.normalise(2000,1,10000000,0,0,0)})
+ end)
+
+ it("#normalise handles out of range days in a #month", function()
+ assert.same({2012,12,1,0,0,0}, {timetable.normalise(2013,0,1,0,0,0)})
+ assert.same({2016,6,1,0,0,0}, {timetable.normalise(2013,42,1,0,0,0)})
+
+ -- Correct behaviour around leap days
+ assert.same({2012,3,23,0,0,0}, {timetable.normalise(2012,2,52,0,0,0)})
+ assert.same({2013,3,24,0,0,0}, {timetable.normalise(2013,2,52,0,0,0)})
+
+ assert.same({2012,2,27,0,0,0}, {timetable.normalise(2012,3,-2,0,0,0)})
+ assert.same({2013,2,26,0,0,0}, {timetable.normalise(2013,3,-2,0,0,0)})
+
+ -- Also when more fields are out of range
+ assert.same({2016,7,22,0,0,0}, {timetable.normalise(2013,42,52,0,0,0)})
+ assert.same({2016,7,24,2,0,0}, {timetable.normalise(2013,42,52,50,0,0)})
+ end)
+
+ it("#normalise handles fractional #month", function()
+ assert.same({2015,2,15,0,0,0}, {timetable.normalise(2014,14.5,1,0,0,0)})
+ assert.same({2016,2,15,12,0,0}, {timetable.normalise(2015,14.5,1,0,0,0)}) -- leap year, so hours is 12
+ assert.same({2017,2,15,0,0,0}, {timetable.normalise(2016,14.5,1,0,0,0)})
+ end)
+
+ it("#normalise handles negative carry (issue #10)", function()
+ assert.same({1970,01,01,00,59,00}, {timetable.normalise(1970,01,01,01,00,-60)})
+ assert.same({1970,01,01,00,58,58}, {timetable.normalise(1970,01,01,01,00,-62)})
+ assert.same({1969,12,31,23,55,58}, {timetable.normalise(1970,01,01,01,-63,-62)})
+ assert.same({2017,02,3,0,0,0}, {timetable.normalise(2017,02,13,0,-14400,0)})
+ end)
+
+ it("#normalise handles negative day carry (issue #13)", function()
+ assert.same({2016,11,30,00,00,00}, {timetable.normalise(2016,12,0,0,0,0)})
+ assert.same({2017,11,30,00,00,00}, {timetable.normalise(2017,12,0,0,0,0)})
+ assert.same({2018,11,30,00,00,00}, {timetable.normalise(2018,12,0,0,0,0)})
+
+ assert.same({2017,2,13,0,0,0}, {timetable.normalise(2017,3,-15,0,0,0)})
+ assert.same({2016,10,1,0,0,0}, {timetable.normalise(2017,3,-150,0,0,0)})
+ assert.same({2013,1,20,0,0,0}, {timetable.normalise(2017,3,-1500,0,0,0)})
+ assert.same({1976,2,4,0,0,0}, {timetable.normalise(2017,3,-15000,0,0,0)})
+ assert.same({1606,6,23,0,0,0}, {timetable.normalise(2017,3,-150000,0,0,0)})
+ end)
+
+ local function round_trip_add(t, field, x)
+ local before = t:clone()
+ t[field]=t[field]+x;
+ t:normalise();
+ t[field]=t[field]-x;
+ t:normalise();
+ assert.same(0, t-before)
+ end
+ it("#normalise round trips", function()
+ round_trip_add(timetable.new(2000,2,28,0,0,0), "month", 0.5)
+ round_trip_add(timetable.new(2014,8,28,19,23,0), "month", 0.4)
+ round_trip_add(timetable.new(2014,14.5,28,0,0,0), "month", 0.4)
+ end)
+
+ it("#rfc_3339 works with fractional milliseconds", function()
+ -- on lua 5.3 this used to throw an error due to milliseconds not being an integer
+ timetable.new_from_timestamp(1415141759.999911111):rfc_3339()
+ end)
+
+ it("#rfc_3339 doesn't round seconds up to 60 (issue #4)", function()
+ assert.same("2014-11-04T22:55:59.999", timetable.new_from_timestamp(1415141759.999911111):rfc_3339())
+ assert.same("1970-01-01T00:00:59.999", timetable.new_from_timestamp(59.9999999):rfc_3339())
+ assert.same("1969-12-31T23:59:59.999", timetable.new_from_timestamp(-0.001):rfc_3339())
+ assert.same("1969-12-31T23:59:00.000", timetable.new_from_timestamp(-59.9999999):rfc_3339())
+ end)
+end)
--- /dev/null
+describe("Opening/reading system files", function()
+ local tzcache = require "luatz.tzcache"
+ it("should have a localtime", function()
+ tzcache.get_tz()
+ end)
+ it("should be able to open UTC", function()
+ tzcache.get_tz("UTC")
+ end)
+ it("should re-use results from cache", function()
+ -- If cached it should return the same table
+ local localtime = tzcache.get_tz()
+ assert.are.equal(localtime, tzcache.get_tz())
+ -- Once cache is cleared it should return a new table
+ tzcache.clear_tz_cache()
+ assert._not.equal(localtime, tzcache.get_tz())
+ end)
+end)
--- /dev/null
+describe("Opening/reading tz files", function()
+ local tzfile = require "luatz.tzfile"
+ it("should be able to open a version 3 file", function()
+ -- The tz file for America/Godthab from 2015g
+ -- One of the smallest tzif3 files I have
+ tzfile.read_tzfile("spec/Godthab.tz")
+ end)
+end)
--- /dev/null
+-- {{{ Imports
+-- Standard awesome library
+local gears = require("gears")
+local awful = require("awful")
+require("awful.autofocus")
+-- Widget and layout library
+local wibox = require("wibox")
+-- Tyrannical tab handling
+--local tyrannical = require("tyrannical")
+-- Theme handling library
+local beautiful = require("beautiful")
+local xrdb = beautiful.xresources
+-- Notification library
+local naughty = require("naughty")
+local menubar = require("menubar")
+local hotkeys_popup = require("awful.hotkeys_popup").widget
+-- Enable hotkeys help widget for VIM and other apps
+-- when client with a matching name is opened:
+require("awful.hotkeys_popup.keys")
+
+-- Load Debian menu entries
+local debian = require("debian.menu")
+local has_fdo, freedesktop = pcall(require, "freedesktop")
+-- Other libraries
+local lain = require("lain")
+local ccwidgets = require("cryptocoin_widgets")
+local fxwidgets = require("forex_widgets")
+local clocksarray = require("clocksarray")
+local dbg = require("debugfunc")
+local th = require("taghelpers")
+-- }}}
+
+-- {{{ 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
+--xrdb.set_dpi(95, screen[1])
+--xrdb.set_dpi(120, screen[2])
+
+-- Themes define colours, icons, font and wallpapers.
+beautiful.init(gears.filesystem.get_configuration_dir () .. "theme/theme.lua")
+
+-- This is used later as the default terminal and editor to run.
+terminal = "rxvt-unicode"
+editor = os.getenv("EDITOR") or "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.
+local layouts = {
+ default = awful.layout.suit.fair,
+ default_horiz = awful.layout.suit.fair.horizontal,
+ tiled = awful.layout.suit.tile,
+ tiled_horiz = awful.layout.suit.tile.top,
+ floating = awful.layout.suit.floating,
+ maximised = awful.layout.suit.max
+}
+awful.layout.layouts = {
+ layouts.default,
+ layouts.tiled,
+ layouts.maximised,
+ layouts.floating,
+ layouts.default_horiz,
+ layouts.tiled_horiz,
+}
+-- }}}
+
+-- {{{ 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 set_wallpaper(s)
+ -- Wallpaper
+ if beautiful.wallpaper then
+ local wallpaper = beautiful.wallpaper
+ -- If wallpaper is a function, call it with the screen
+ if type(wallpaper) == "function" then
+ wallpaper = wallpaper(s)
+ end
+ gears.wallpaper.maximized(wallpaper, s, true)
+ end
+end
+
+local function move_mouse_to_area(a)
+ local coords = mouse.coords()
+ if (coords.x < a.x or
+ coords.x > (a.x+a.width) or
+ coords.y < a.y or
+ coords.y > (a.y+a.height)) then
+
+ mouse.coords({
+ x = a.x + a.width/2,
+ y = a.y + a.height/2,
+ }, true)
+ end
+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", function() awesome.quit() end}
+}
+
+local menu_awesome = { "awesome", myawesomemenu, beautiful.awesome_icon }
+local menu_terminal = { "open terminal", terminal }
+
+if has_fdo then
+ mymainmenu = freedesktop.menu.build({
+ before = { menu_awesome },
+ after = { menu_terminal }
+ })
+else
+ mymainmenu = awful.menu({
+ items = {
+ menu_awesome,
+ { "Debian", debian.menu.Debian_menu.Debian },
+ menu_terminal,
+ }
+ })
+end
+
+
+mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon,
+ menu = mymainmenu })
+
+-- Menubar configuration
+menubar.utils.terminal = terminal -- Set the terminal for applications that require it
+-- }}}
+
+-- {{{ Wibar
+--local spacer = wibox.widget {
+-- color = beautiful.bg_minimize,
+-- forced_width = 4,
+-- widget = wibox.widget.separator
+--}
+local function make_spacer(text)
+ local spacer = wibox.widget.textbox()
+ spacer:set_text(text or " │ ")
+ return spacer
+end
+
+-- Keyboard map indicator and switcher
+mykeyboardlayout = awful.widget.keyboardlayout()
+
+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,
+})
+
+-- Create a textclock widget
+clocksarray = clocksarray.get_clocksarray("%a %d %b %H:%M:%S %Z", {
+ ["NZ"] = "Pacific/Auckland",
+ ["DE"] = "Europe/Berlin"
+ }, make_spacer())
+
+-- Create a wibox for each screen and add it
+local taglist_buttons = gears.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)
+ )
+
+local tasklist_buttons = gears.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))
+-- }}}
+
+-- {{{ Screens
+
+-- Re-set wallpaper when a screen's geometry changes (e.g. different resolution)
+screen.connect_signal("property::geometry", set_wallpaper)
+
+-- {{{ Basic setup for screens
+local function screen_set_profile(s, profile)
+ s.profile = profile
+ s.outputstr = table.concat(gears.table.keys(s.outputs), "+")
+ s.name = s.profile .. "/" .. s.outputstr
+end
+
+awful.screen.connect_for_each_screen(function(s)
+
+ s.set_profile = screen_set_profile
+
+ -- Wallpaper
+ set_wallpaper(s)
+
+ -- Create a text widget to display screen name
+ s.namebox = wibox.container.background(wibox.widget.textbox(s.name),
+ beautiful.bg_minimize)
+
+ -- Create a promptbox for each screen
+ s.mypromptbox = awful.widget.prompt()
+ -- Create an imagebox widget which will contains an icon indicating which layout we're using.
+ -- We need one layoutbox per screen.
+ s.mylayoutbox = awful.widget.layoutbox(s)
+ s.mylayoutbox: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
+ s.mytaglist = awful.widget.taglist(s, awful.widget.taglist.filter.all, taglist_buttons)
+
+ -- Create a tasklist widget
+ s.mytasklist = awful.widget.tasklist(s, awful.widget.tasklist.filter.currenttags, tasklist_buttons)
+
+ -- Create the wibox, but only if there isn't one yet
+ if not s.mywibox then
+ s.mywibox = awful.wibar({ position = "top", screen = s })
+ end
+
+ -- Add widgets to the wibox
+ local right_widgets = gears.table.join(clocksarray, {
+ make_spacer(" "),
+ wibox.widget.systray(),
+ s.mylayoutbox,
+ layout = wibox.layout.fixed.horizontal,
+ })
+
+-- if s == screen.primary then
+ right_widgets = gears.table.join({
+ make_spacer(" "),
+ ccwidgets.btc_widget,
+ make_spacer(),
+ ccwidgets.eth_widget,
+ make_spacer(),
+ fxwidgets.ecb_widget,
+ make_spacer(),
+ lain_bat.widget,
+ make_spacer(),
+ }, right_widgets)
+-- end
+
+ s.mywibox:setup {
+ layout = wibox.layout.align.horizontal,
+ { -- Left widgets
+ layout = wibox.layout.fixed.horizontal,
+ --s.namebox,
+ s.mytaglist,
+ make_spacer(" "),
+ s.mypromptbox,
+ },
+ s.mytasklist, -- Middle widget
+ right_widgets,
+ }
+end) -- }}}
+
+-- {{{ autorandr integration
+local function find_screen_by_name(name)
+ for s in screen do
+ if s.name == name then
+ return s
+ end
+ end
+end
+
+local function get_target_screen_for_tag(tag)
+ local function primary_screen(reason)
+ local s = screen.primary
+ local msg = " → primary screen \"" .. s.name .. "\""
+ if reason then msg = msg .. " (" .. reason .. ")" end
+ print(msg)
+ return s
+ end
+
+ print("Figuring out target screen for tag " .. tag.name .. "…")
+ if tag.targets then
+ if type(tag.targets) == "table" then
+ for _,target in ipairs(tag.targets) do
+ local s = find_screen_by_name(target)
+ if s then
+ print(" → screen " .. s.name)
+ return s
+ end
+ end
+ elseif tag.targets == "primary" then
+ return primary_screen("explicit request")
+ end
+ return primary_screen("no matching target in " .. table.concat(tag.targets, ","))
+ else
+ return primary_screen("no targets specified")
+ end
+end
+
+local function move_tag_to_target_screen(tag)
+ tag.screen = get_target_screen_for_tag(tag)
+end
+
+local function move_tags_to_target_screens()
+ for _,tag in ipairs(root.tags()) do
+ move_tag_to_target_screen(tag)
+ end
+end
+
+tag.connect_signal("request::screen", function(t)
+ -- throw the tag onto any other screen, it'll get reassigned later when
+ -- a new profile has been processed.
+ for s in screen do
+ if s ~= t.screen then
+ t.screen = s
+ t.selected = false
+ break
+ end
+ end
+ naughty.notify({
+ title = "Screen removed",
+ text = "Salvaged tab " .. t.name,
+ })
+end)
+
+function handle_new_autorandr_profile(newprofile)
+ -- The main idea here is that autorandr invokes this via awesome-client
+ -- after switching to a new profile. Awesome will have already set up all
+ -- the screens long before this function is called. Therefore, we just do
+ -- the necessary modifications to the existing screens, and move tags
+ -- around.
+
+ if not newprofile then
+ error("Missing new profile name")
+ end
+
+ naughty.notify({
+ preset = naughty.config.presets.low,
+ title = "New autorandr profile",
+ text = "Reconfiguring for profile <b>" .. newprofile .. "</b>",
+ })
+
+ for s in screen do
+ s:set_profile(newprofile)
+ end
+ move_tags_to_target_screens()
+end
+
+local function initialise_to_autorandr_profile()
+ local profile
+ profile = nil
+
+ local function process_line(line)
+ if profile then return end
+ local match = string.match(line, "^([^%s]+) %(detected%)")
+ if match then
+ profile = match
+ end
+ end
+
+ local function output_done()
+ if not profile then
+ error("autorandr detected no profile")
+ profile = "awesome"
+ end
+ handle_new_autorandr_profile(profile)
+ end
+
+ local function handle_exit(reason, code)
+ if not (reason == "exit" and code == 0) then
+ error("autorandr error: " .. reason .. ": " .. tostring(code))
+ end
+ end
+
+ awful.spawn.with_line_callback('autorandr', {
+ stdout = process_line,
+ output_done = output_done,
+ exit = handle_exit
+ })
+end
+awesome.connect_signal("startup", initialise_to_autorandr_profile)
+-- }}}
+
+-- }}}
+
+-- {{{ Tags
+
+local default_tag = {
+ name = nil,
+ init = true,
+ layout = layouts.default,
+ fallback = true,
+ targets = "primary",
+}
+local default_tags = {}
+for i = 1, 9 do
+ default_tags[i] = {}
+ for k,v in pairs(default_tag) do
+ default_tags[i][k] = v
+ end
+ default_tags[i].name = tostring(i)
+end
+default_tags[1].selected = true
+
+default_tags = gears.table.join(default_tags, {
+ {
+ name = "irc",
+ init = true,
+ exclusive = true,
+ master_width_factor = 0.33,
+ layout = layouts.tiled,
+ selected = true,
+ exec_once = { terminal .. " -name irc -e env MOSH_TITLE_NOPREFIX=true mosh -4 -- irc-host tmux new -As irc irssi" },
+ instance = { "irc" },
+ targets = { "catalyst/eDP1", "mtvic/eDP1", "gauting/eDP1", "lehel/DisplayPort-2" },
+ },
+ {
+ name = "[m]",
+ init = true,
+ exclusive = true,
+ master_width_factor = 0.67,
+ layout = layouts.tiled,
+ selected = true,
+ exec_once = { "revolt" },
+ instance = { "Revolt" },
+ targets = { "catalyst/eDP1", "mtvic/eDP1", "gauting/eDP1", "lehel/DisplayPort-2" },
+ },
+ {
+ name = "dflt",
+ init = false,
+ fallback = true,
+ layout = layouts.floating,
+ volatile = true,
+ selected = true,
+ },
+ {
+ name = "cal",
+ init = true,
+ exclusive = true,
+ layout = layouts.default,
+ exec_once = { "thunderbird" },
+ class = { "Thunderbird" },
+ targets = { "catalyst/DP2-2", "mtvic/eDP1", "gauting/eDP1", "lehel/DisplayPort-1" },
+ },
+ {
+ name = "chr",
+ init = true,
+ exclusive = true,
+ layout = layouts.default,
+ exec_once = { "chromium" },
+ class = { "Chromium" },
+ targets = { "catalyst/DP2-2", "mtvic/eDP1", "gauting/eDP1", "lehel/DisplayPort-1", "present/HDMI1" },
+ },
+ {
+ name = "ffx",
+ init = true,
+ exclusive = true,
+ layout = layouts.default,
+ exec_once = { "firefox" },
+ class = { "Firefox" },
+ targets = { "catalyst/DP2-2", "mtvic/eDP1", "gauting/eDP1", "lehel/DisplayPort-1", "present/HDMI1" },
+ },
+})
+
+if not tyrannical then
+
+for _,t in ipairs(default_tags) do
+ if t.init then
+ t.screen = t.screen or screen.primary
+ t.layout = t.layout or layouts.default
+ local newt = th.add_tag(t.name, t, false)
+ end
+end
+
+else -- {{{ tyrannical is loaded
+tyrannical.settings.default_layout = layouts.default
+tyrannical.settings.master_width_factor = 0.5
+tyrannical.settings.block_children_focus_stealing = true
+tyrannical.settings.group_children = true
+
+tyrannical.tags = default_tags
+
+tyrannical.properties.size_hints_honor = { URxvt = false }
+
+--XX---- Ignore the tag "exclusive" property for the following clients (matched by classes)
+--XX--tyrannical.properties.intrusive = {
+--XX-- "ksnapshot" , "pinentry" , "gtksu" , "kcalc" , "xcalc" ,
+--XX-- "feh" , "Gradient editor", "About KDE" , "Paste Special", "Background color" ,
+--XX-- "kcolorchooser" , "plasmoidviewer" , "Xephyr" , "kruler" , "plasmaengineexplorer",
+--XX--}
+--XX--
+--XX---- Ignore the tiled layout for the matching clients
+--XX--tyrannical.properties.floating = {
+--XX-- "MPlayer" , "pinentry" , "ksnapshot" , "pinentry" , "gtksu" ,
+--XX-- "xine" , "feh" , "kmix" , "kcalc" , "xcalc" ,
+--XX-- "yakuake" , "Select Color$" , "kruler" , "kcolorchooser", "Paste Special" ,
+--XX-- "New Form" , "Insert Picture" , "kcharselect", "mythfrontend" , "plasmoidviewer"
+--XX--}
+--XX--
+--XX---- Make the matching clients (by classes) on top of the default layout
+--XX--tyrannical.properties.ontop = {
+--XX-- "Xephyr" , "ksnapshot" , "kruler"
+--XX--}
+--XX--
+--XX---- Force the matching clients (by classes) to be centered on the screen on init
+--XX--tyrannical.properties.centered = {
+--XX-- "kcalc"
+--XX--}
+end -- }}}
+
+-- }}}
+
+-- {{{ Mouse bindings
+root.buttons(gears.table.join(
+ awful.button({ }, 3, function () mymainmenu:toggle() end),
+ awful.button({ }, 4, awful.tag.viewnext),
+ awful.button({ }, 5, awful.tag.viewprev)
+))
+-- }}}
+
+-- {{{ Key bindings
+
+local function toggle_tag_by_name(tagname, exclusive)
+ return function()
+ local t = awful.tag.find_by_name(nil, tagname)
+ if t then
+ if exclusive then
+ t:view_only()
+ else
+ awful.tag.viewtoggle(t)
+ end
+ cf = awful.client.getmaster(t.screen)
+ if cf then
+ cf:jump_to()
+ end
+ end
+ end
+end
+
+globalkeys = gears.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, "Shift" }, "Left", function () awful.screen.focus_relative( 1) end,
+ {description = "focus the next screen", group = "screen"}),
+ awful.key({ modkey, "Shift" }, "Right", function () awful.screen.focus_relative(-1) end,
+ {description = "focus the previous screen", group = "screen"}),
+ 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"}
+ ),
+
+ -- 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, "Shift" }, "Return", 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, }, "r", function()
+ package.loaded.rc = nil
+ require("rc")
+ end,
+ {description = "reload rc.lua", group = "awesome"}),
+ 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({ cmdkey }, "r",
+ function ()
+ local widget = awful.screen.focused().mypromptbox.widget
+ local function spawn(command, args)
+ gears.debug.dump(args)
+ awful.spawn(command, args)
+ end
+
+ awful.prompt.run {
+ prompt = "Exec: ",
+ bg_cursor = '#ff0000',
+ textbox = widget,
+ history_path = awful.util.get_cache_dir() .. "/history",
+ completion_callback = awful.completion.shell,
+ hooks = {
+ -- Replace the 'normal' Return with a custom one
+ {{ }, 'Return', function(command)
+ spawn(command)
+ end},
+ -- Spawn method to spawn in the current tag
+ {{'Mod1' }, 'Return', function(command)
+ spawn(command,{
+ intrusive = true,
+ tag = mouse.screen.selected_tag
+ })
+ end},
+ -- Spawn in the current tag as floating and on top
+ {{'Shift' }, 'Return', function(command)
+ spawn(command,{
+ ontop = true,
+ floating = true,
+ tag = mouse.screen.selected_tag
+ })
+ end},
+ -- Spawn in a new tag
+ {{'Control'}, 'Return', function(command)
+ spawn(command,{
+ new_tag = true,
+ layout = layouts.default,
+ volatile = true,
+ })
+ end},
+ -- Cancel
+ {{ }, 'Escape', function(_) return end},
+ },
+ }
+ end,
+ {description = "run prompt", group = "launcher"}),
+
+ awful.key({ modkey }, "x",
+ function ()
+ awful.prompt.run {
+ prompt = "Eval: ",
+ bg_cursor = '#ff0000',
+ textbox = awful.screen.focused().mypromptbox.widget,
+ exe_callback = awful.util.eval,
+ history_path = awful.util.get_cache_dir() .. "/history_eval"
+ }
+ end,
+ {description = "lua execute prompt", group = "awesome"}),
+ -- Menubar
+ awful.key({ modkey }, "w", function() menubar.show() end,
+ {description = "show the menubar", group = "launcher"}),
+
+ -- Tag helpers
+ awful.key({ modkey, }, "a", function()
+ th.add_tag(nil, {layout=layouts.default} ,true)
+ end,
+ {description = "add a tag", group = "tag"}),
+ awful.key({ modkey, }, "d", th.delete_tag,
+ {description = "delete the current tag", group = "tag"}),
+ awful.key({ modkey, "Shift", }, "a", function()
+ th.move_to_new_tag(nil,nil,true,true,true)
+ end,
+ {description = "add a volatile tag with the focused client", group = "tag"}),
+ awful.key({ modkey, "Shift", "Control" }, "a", function()
+ th.move_to_new_tag(nil,nil,false,true,true)
+ end,
+ {description = "add a permanent tag with the focused client", group = "tag"}),
+ awful.key({ modkey, "Mod1" }, "a", th.copy_tag,
+ {description = "create a copy of the current tag", group = "tag"}),
+ awful.key({ modkey, "Control" }, "a", th.rename_tag,
+ {description = "rename the current tag", group = "tag"}),
+ awful.key({ modkey, "Control", "Shift", "Mod1" }, "a", th.collect_orphan_clients_to_tag,
+ {description = "collect all orphaned clients", group = "client"}),
+
+ awful.key({ modkey }, "y", toggle_tag_by_name("irc", true),
+ {description = "view tag 'irc'", group = "tag"}),
+ awful.key({ modkey, "Control" }, "y", toggle_tag_by_name("irc"),
+ {description = "toggle tag 'irc'", group = "tag"}),
+ awful.key({ modkey }, "u", toggle_tag_by_name("[m]", true),
+ {description = "view tag '[m]'", group = "tag"}),
+ awful.key({ modkey, "Control" }, "u", toggle_tag_by_name("[m]"),
+ {description = "toggle tag '[m]'", group = "tag"}),
+ awful.key({ modkey }, "i", toggle_tag_by_name("cal", true),
+ {description = "view tag 'cal'", group = "tag"}),
+ awful.key({ modkey, "Control" }, "i", toggle_tag_by_name("cal"),
+ {description = "toggle tag 'cal'", group = "tag"}),
+ awful.key({ modkey }, "o", toggle_tag_by_name("chr", true),
+ {description = "view tag 'chr'", group = "tag"}),
+ awful.key({ modkey, "Control" }, "o", toggle_tag_by_name("chr"),
+ {description = "toggle tag 'chr'", group = "tag"}),
+ awful.key({ modkey }, "p", toggle_tag_by_name("ffx", true),
+ {description = "view tag 'ff'", group = "tag"}),
+ awful.key({ modkey, "Control" }, "p", toggle_tag_by_name("ffx"),
+ {description = "toggle tag 'ff'", group = "tag"}),
+{})
+
+clientkeys = gears.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, }, "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 = "(un)maximize", group = "client"}),
+ awful.key({ modkey, "Control" }, "m",
+ function (c)
+ c.maximized_vertical = not c.maximized_vertical
+ c:raise()
+ end ,
+ {description = "(un)maximize vertically", group = "client"}),
+ awful.key({ modkey, "Shift" }, "m",
+ function (c)
+ c.maximized_horizontal = not c.maximized_horizontal
+ c:raise()
+ end ,
+ {description = "(un)maximize horizontally", group = "client"})
+)
+
+-- Bind all key numbers to tags.
+-- Be careful: we use keycodes to make it work on any keyboard layout.
+-- This should map on the top row of your keyboard, usually 1 to 9.
+for i = 1, 9 do
+ globalkeys = gears.table.join(globalkeys,
+ -- View tag only.
+ awful.key({ modkey }, "#" .. i + 9, toggle_tag_by_name(tostring(i), true),
+ {description = "view tag #"..i, group = "tag"}),
+ -- Toggle tag display.
+ awful.key({ modkey, "Control" }, "#" .. i + 9, toggle_tag_by_name(tostring(i)),
+ {description = "toggle tag #" .. i, group = "tag"}),
+ -- Move client to tag.
+ awful.key({ modkey, "Shift" }, "#" .. i + 9,
+ function ()
+ if client.focus then
+ local tag = awful.tag.find_by_name(screen.primary, tostring(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 = awful.tag.find_by_name(screen.primary, tostring(i))
+ if tag then
+ client.focus:toggle_tag(tag)
+ end
+ end
+ end,
+ {description = "toggle focused client on tag #" .. i, group = "tag"})
+ )
+end
+
+clientbuttons = gears.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 }, "c", function () awful.spawn("chromium --enable-remote-extensions") end),
+awful.key({ cmdkey }, "y", function () awful.spawn(terminal .. " -e python") end),
+awful.key({ cmdkey }, "m", function () awful.spawn(terminal .. " -name mutt -e mutt") end),
+awful.key({ cmdkey }, "t", function () awful.spawn("thunderbird") 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("/usr/bin/xscreensaver -no-capture-stderr")
+ os.execute("sleep 5")
+ awful.spawn("xscreensaver-command -lock")
+end),
+awful.key({ cmdkey, "Shift" }, "x", function () awful.spawn("xscreensaver-command -exit") end),
+
+awful.key({ cmdkey }, "BackSpace", function () awful.spawn("pkill -USR1 offlineimap") 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 @DEFAULT_SINK@ toggle") end),
+awful.key({ cmdkey }, "End", function () awful.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle") end),
+awful.key(nil, "XF86AudioLowerVolume", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ -2%") end),
+awful.key({ cmdkey }, "Next", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ -2%") end),
+awful.key(nil, "XF86AudioRaiseVolume", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ +2%") end),
+awful.key({ cmdkey }, "Prior", function () awful.spawn("pactl set-sink-volume @DEFAULT_SINK@ +2%") end),
+awful.key(nil, "XF86AudioMicMute", function () awful.spawn("pactl set-source-mute @DEFAULT_SOURCE@ toggle") end),
+awful.key({ cmdkey }, "Home", function () awful.spawn("pactl set-source-mute @DEFAULT_SOURCE@ 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("autorandr --change --force"); initialise_to_autorandr_profile() 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),
+
+awful.key({ cmdkey }, "Left", function () awful.spawn("xmms2 prev") end),
+awful.key({ cmdkey }, "Right", function () awful.spawn("xmms2 next") end),
+awful.key({ cmdkey }, "space", function () awful.spawn("xmms2 toggle") end),
+awful.key({ cmdkey }, "\\", function () run_output_notify("xmms2 list") end)
+)
+
+function run_output_notify(cmd)
+ awful.spawn.easy_async(cmd, function(stdout, stderr, reason, exit_code)
+ naughty.notify({
+ preset = naughty.config.presets.low,
+ title = "XMMS2 playlist",
+ text = stdout})
+ end)
+end
+
+-- Set keys
+root.keys(globalkeys)
+-- }}}
+
+-- {{{ Rules
+-- Rules to apply to new clients (through the "manage" signal).
+
+local function float_client_in_the_middle_with_margins(client, leftright, topbottom)
+ local wa = client.screen.workarea
+ if topbottom then
+ client.y = wa.y + topbottom
+ client.height = wa.height - 2*topbottom
+ else
+ client.y = wa.y + (wa.height - client.height)/2
+ end
+ if leftright then
+ client.x = wa.x + leftright
+ client.width = wa.width - 2*leftright
+ else
+ client.x = wa.x + (wa.width - client.width)/2
+ end
+end
+
+local function move_to_tag_by_name(s, tagname)
+ return function(c)
+ local t = awful.tag.find_by_name(s, tagname)
+ if not t then
+ error("No tag by the name of " .. tagname)
+ return
+ end
+ c:move_to_tag(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
+ },
+ },
+ { rule = { type = "dialog" },
+ properties = { floating = true,
+ ontop = true,
+ skip_taskbar = true,
+ urgent = true,
+ --new_tag = true,
+ --switchtotag = true,
+ placement = awful.placement.centered
+ }
+ },
+ { rule = { class = "URxvt" },
+ properties = { size_hints_honor = false, }
+ },
+ { rule = { instance = "irc" },
+ callback = move_to_tag_by_name(nil, "irc"),
+ },
+ { rule = { class = "Revolt" },
+ callback = move_to_tag_by_name(nil, "[m]"),
+ },
+ { rule = { class = "Firefox" },
+ callback = move_to_tag_by_name(nil, "ffx"),
+ },
+ { rule = { class = "Chromium" },
+ callback = move_to_tag_by_name(nil, "chr"),
+ },
+ { rule = { class = "Thunderbird" },
+ callback = move_to_tag_by_name(nil, "cal"),
+ },
+ { rule = { instance = "mutt" },
+ properties = {
+ new_tag = {
+ name = "mutt",
+ layout = awful.layout.suit.fair.horizontal,
+ volatile = true
+ },
+ switchtotag = true,
+ },
+ },
+ { rule_any = { class = {
+ "Wicd-client.py",
+ "Gxmessage",
+ "Pinentry"
+ }},
+ properties = { floating = true,
+ maximized = false,
+ focus = true,
+ placement = awful.placement.centered,
+ },
+ },
+ { rule_any = { instance = {
+ "tridactyl-edit",
+ "libreoffice",
+ "pdfshuffler"
+ }},
+ properties = { floating = true,
+ maximized = false,
+ focus = true,
+ placement = awful.placement.centered,
+ },
+ },
+-- { rule_any = { class = {
+-- "Gscan2pdf",
+-- "Gimp",
+-- },
+-- instance = {
+-- "libreoffice",
+-- }
+-- },
+-- properties = { new_tag = {
+-- layout = layouts.maximised,
+-- volatile = true,
+-- },
+-- switchtotag = true,
+-- focus = true,
+-- },
+-- },
+--XX-- { rule = { class = "Gscan2pdf" },
+--XX-- properties = {
+--XX-- switchtotag = true
+--XX-- },
+--XX-- callback = move_to_tag(1, 5)
+--XX-- },
+--XX-- { rule = { name = "gscan2pdf .*" },
+--XX-- properties = {
+--XX-- floating = false,
+--XX-- },
+--XX-- },
+--XX-- { rule = { class = "Thunar", type = "normal" },
+--XX-- properties = {
+--XX-- floating = false,
+--XX-- },
+--XX-- },
+--XX-- { rule = { class = "Pinentry", instance = "pinentry" },
+--XX-- properties = {
+--XX-- floating = true,
+--XX-- },
+--XX-- },
+--XX-- { rule = { class = "Gxmessage" },
+--XX-- properties = {
+--XX-- floating = true,
+--XX-- },
+--XX-- },
+--XX--}
+}
+-- }}}
+
+-- {{{ 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 not awesome.startup then
+ local t = awful.screen.focused().selected_tag
+ if t.name == "xmutt" then
+ awful.client.setslave(c)
+ end
+ 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 = gears.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, so that focus follows mouse.
+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" then
+ if (c.class == "Firefox-esr" or c.class == "Firefox") then
+ return false
+ end
+ end
+end)
+
+client.connect_signal("request::activate", function(c, context, hints)
+ if gears.table.hasitem({
+ "client.focus.byidx",
+ "client.jumpto",
+ "autofocus.check_focus",
+ "rules",
+ "ewmh",
+ }, context) then
+ gears.timer.delayed_call(function()
+ -- we need a delayed call so that we execute *after layout changes
+ if hints.raise and c == client.focus and client.focus:isvisible() then
+ move_mouse_to_area(client.focus)
+ end
+ end)
+ end
+end)
+
+-- vim:ft=lua:sw=4:sts=4:ts=4:et
--- /dev/null
+local awful = require('awful')
+
+module = {}
+
+function module.delete_tag()
+ local t = awful.screen.focused().selected_tag
+ if not t then return end
+ t:delete()
+end
+
+local tmpcounter = 0
+function module.add_tag(name, properties, switchto, force)
+ local p = (type(properties) == "table" and properties) or {}
+ if not name then
+ name = name or string.format("t%d", tmpcounter)
+ tmpcounter = tmpcounter + 1
+ end
+ if not awful.tag.find_by_name(p.screen, name) or force then
+ p.screen = (p.screen
+ or (client.focus and client.focus.screen)
+ or awful.screen.focused())
+ local t = awful.tag.add(name, p)
+ if switchto then
+ t:view_only()
+ end
+ return t
+ else
+ local text = "A tag with name \"" .. name .. "\" already exists"
+ if p.screen then
+ text = text .. " (on screen " .. p.screen.name .. ")"
+ end
+ naughty.notify({
+ preset = naughty.config.presets.low,
+ title = "Add a new tag",
+ text = text
+ })
+ end
+end
+
+function module.rename_tag()
+ awful.prompt.run {
+ prompt = "New tag name: ",
+ textbox = awful.screen.focused().mypromptbox.widget,
+ exe_callback = function(new_name)
+ if not new_name or #new_name == 0 then return end
+
+ local t = awful.screen.focused().selected_tag
+ if t then
+ t.name = new_name
+ end
+ end
+ }
+end
+
+function module.move_to_new_tag(clnt, properties, volatile, switchto, force)
+ local c = clnt or client.focus
+ if not c then return end
+
+ local p = (type(properties) == "table" and properties) or {}
+ local s = p.screen or c.screen
+ local t = module.add_tag(c.class, {
+ screen = s,
+ volatile = volatile,
+ }, switchto, force)
+ c:move_to_tag(t)
+end
+
+function module.copy_tag()
+ local t = awful.screen.focused().selected_tag
+ if not t then return end
+
+ local clients = t:clients()
+ local t2 = awful.tag.add(t.name, awful.tag.getdata(t))
+ t2:clients(clients)
+ t2:view_only()
+end
+
+function module.collect_orphan_clients_to_tag(name)
+ local orphans = {}
+ for _,c in ipairs(client.get()) do
+ if #c:tags() == 0 then
+ orphans[#orphans+1] = c
+ end
+ end
+ if #orphans == 0 then
+ naughty.notify({text="No orphan clients found."})
+ return
+ end
+ local t = awful.tag.find_by_name(nil, name)
+ if not t then
+ t = module.add_tag("orphans", {
+ volatile = true,
+ screen = awful.screen.focused(),
+ }, true)
+ end
+ for _,c in ipairs(orphans) do
+ c:move_to_tag(t)
+ end
+end
+
+return module
--- /dev/null
+local module = {}
+
+function module.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
+
+return module
--- /dev/null
+Background images:
+ Mikael Eriksson <mikael_eriksson@miffe.org>
+ Licensed under CC-BY-SA-3.0
--- /dev/null
+---------------------------
+-- Default awesome theme --
+---------------------------
+
+local theme_assets = require("beautiful.theme_assets")
+local xresources = require("beautiful.xresources")
+local dpi = xresources.apply_dpi
+
+local gfs = require("gears.filesystem")
+local themes_path = gfs.get_themes_dir()
+
+local theme = {}
+
+theme.font = "sans 10"
+
+theme.bg_normal = "#222222"
+theme.bg_focus = "#535d6c"
+theme.bg_urgent = "#ff0000"
+theme.bg_minimize = "#444444"
+theme.bg_systray = theme.bg_minimize
+
+theme.fg_normal = "#aaaaaa"
+theme.fg_focus = "#ffffff"
+theme.fg_urgent = "#ffffff"
+theme.fg_minimize = "#ffffff"
+
+theme.useless_gap = dpi(0)
+theme.border_width = dpi(2)
+theme.border_normal = "#cccccc"
+theme.border_focus = "#00aa00"
+theme.border_marked = "#cc0000"
+
+-- There are other variable sets
+-- overriding the default one when
+-- defined, the sets are:
+-- taglist_[bg|fg]_[focus|urgent|occupied|empty|volatile]
+-- tasklist_[bg|fg]_[focus|urgent]
+-- titlebar_[bg|fg]_[normal|focus]
+-- tooltip_[font|opacity|fg_color|bg_color|border_width|border_color]
+-- mouse_finder_[color|timeout|animate_timeout|radius|factor]
+-- prompt_[fg|bg|fg_cursor|bg_cursor|font]
+-- hotkeys_[bg|fg|border_width|border_color|shape|opacity|modifiers_fg|label_bg|label_fg|group_margin|font|description_font]
+-- Example:
+--theme.taglist_bg_focus = "#ff0000"
+
+-- Generate taglist squares:
+local taglist_square_size = dpi(4)
+theme.taglist_squares_sel = theme_assets.taglist_squares_sel(
+ taglist_square_size, theme.fg_normal
+)
+theme.taglist_squares_unsel = theme_assets.taglist_squares_unsel(
+ taglist_square_size, theme.fg_normal
+)
+
+-- Variables set for theming notifications:
+-- notification_font
+-- notification_[bg|fg]
+-- notification_[width|height|margin]
+-- notification_[border_color|border_width|shape|opacity]
+
+-- Variables set for theming the menu:
+-- menu_[bg|fg]_[normal|focus]
+-- menu_[border_color|border_width]
+theme.menu_submenu_icon = themes_path.."default/submenu.png"
+theme.menu_height = dpi(15)
+theme.menu_width = dpi(100)
+
+-- You can add as many variables as
+-- you wish and access them by using
+-- beautiful.variable in your rc.lua
+--theme.bg_widget = "#cc0000"
+
+-- Define the image to load
+theme.titlebar_close_button_normal = themes_path.."default/titlebar/close_normal.png"
+theme.titlebar_close_button_focus = themes_path.."default/titlebar/close_focus.png"
+
+theme.titlebar_minimize_button_normal = themes_path.."default/titlebar/minimize_normal.png"
+theme.titlebar_minimize_button_focus = themes_path.."default/titlebar/minimize_focus.png"
+
+theme.titlebar_ontop_button_normal_inactive = themes_path.."default/titlebar/ontop_normal_inactive.png"
+theme.titlebar_ontop_button_focus_inactive = themes_path.."default/titlebar/ontop_focus_inactive.png"
+theme.titlebar_ontop_button_normal_active = themes_path.."default/titlebar/ontop_normal_active.png"
+theme.titlebar_ontop_button_focus_active = themes_path.."default/titlebar/ontop_focus_active.png"
+
+theme.titlebar_sticky_button_normal_inactive = themes_path.."default/titlebar/sticky_normal_inactive.png"
+theme.titlebar_sticky_button_focus_inactive = themes_path.."default/titlebar/sticky_focus_inactive.png"
+theme.titlebar_sticky_button_normal_active = themes_path.."default/titlebar/sticky_normal_active.png"
+theme.titlebar_sticky_button_focus_active = themes_path.."default/titlebar/sticky_focus_active.png"
+
+theme.titlebar_floating_button_normal_inactive = themes_path.."default/titlebar/floating_normal_inactive.png"
+theme.titlebar_floating_button_focus_inactive = themes_path.."default/titlebar/floating_focus_inactive.png"
+theme.titlebar_floating_button_normal_active = themes_path.."default/titlebar/floating_normal_active.png"
+theme.titlebar_floating_button_focus_active = themes_path.."default/titlebar/floating_focus_active.png"
+
+theme.titlebar_maximized_button_normal_inactive = themes_path.."default/titlebar/maximized_normal_inactive.png"
+theme.titlebar_maximized_button_focus_inactive = themes_path.."default/titlebar/maximized_focus_inactive.png"
+theme.titlebar_maximized_button_normal_active = themes_path.."default/titlebar/maximized_normal_active.png"
+theme.titlebar_maximized_button_focus_active = themes_path.."default/titlebar/maximized_focus_active.png"
+
+theme.wallpaper = themes_path.."default/background.png"
+
+-- You can use your own layout icons like this:
+theme.layout_fairh = themes_path.."default/layouts/fairhw.png"
+theme.layout_fairv = themes_path.."default/layouts/fairvw.png"
+theme.layout_floating = themes_path.."default/layouts/floatingw.png"
+theme.layout_magnifier = themes_path.."default/layouts/magnifierw.png"
+theme.layout_max = themes_path.."default/layouts/maxw.png"
+theme.layout_fullscreen = themes_path.."default/layouts/fullscreenw.png"
+theme.layout_tilebottom = themes_path.."default/layouts/tilebottomw.png"
+theme.layout_tileleft = themes_path.."default/layouts/tileleftw.png"
+theme.layout_tile = themes_path.."default/layouts/tilew.png"
+theme.layout_tiletop = themes_path.."default/layouts/tiletopw.png"
+theme.layout_spiral = themes_path.."default/layouts/spiralw.png"
+theme.layout_dwindle = themes_path.."default/layouts/dwindlew.png"
+theme.layout_cornernw = themes_path.."default/layouts/cornernww.png"
+theme.layout_cornerne = themes_path.."default/layouts/cornernew.png"
+theme.layout_cornersw = themes_path.."default/layouts/cornersww.png"
+theme.layout_cornerse = themes_path.."default/layouts/cornersew.png"
+
+-- Generate Awesome icon:
+theme.awesome_icon = theme_assets.awesome_icon(
+ theme.menu_height, theme.bg_focus, theme.fg_focus
+)
+
+-- Define the icon theme for application icons. If not set then the icons
+-- from /usr/share/icons and /usr/share/icons/hicolor will be used.
+theme.icon_theme = nil
+
+return theme
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
--- /dev/null
+*
+!/.config/awesome/clocksarray.lua
+!/.config/awesome/cryptocoin_widgets.lua
+!/.config/awesome/debugfunc.lua
+!/.config/awesome/forex_widgets.lua
+!/.config/awesome/lain/.gitmodules
+!/.config/awesome/lain/helpers.lua
+!/.config/awesome/lain/icons/cal/white/10.png
+!/.config/awesome/lain/icons/cal/white/11.png
+!/.config/awesome/lain/icons/cal/white/12.png
+!/.config/awesome/lain/icons/cal/white/13.png
+!/.config/awesome/lain/icons/cal/white/14.png
+!/.config/awesome/lain/icons/cal/white/15.png
+!/.config/awesome/lain/icons/cal/white/16.png
+!/.config/awesome/lain/icons/cal/white/17.png
+!/.config/awesome/lain/icons/cal/white/18.png
+!/.config/awesome/lain/icons/cal/white/19.png
+!/.config/awesome/lain/icons/cal/white/1.png
+!/.config/awesome/lain/icons/cal/white/20.png
+!/.config/awesome/lain/icons/cal/white/21.png
+!/.config/awesome/lain/icons/cal/white/22.png
+!/.config/awesome/lain/icons/cal/white/23.png
+!/.config/awesome/lain/icons/cal/white/24.png
+!/.config/awesome/lain/icons/cal/white/25.png
+!/.config/awesome/lain/icons/cal/white/26.png
+!/.config/awesome/lain/icons/cal/white/27.png
+!/.config/awesome/lain/icons/cal/white/28.png
+!/.config/awesome/lain/icons/cal/white/29.png
+!/.config/awesome/lain/icons/cal/white/2.png
+!/.config/awesome/lain/icons/cal/white/30.png
+!/.config/awesome/lain/icons/cal/white/31.png
+!/.config/awesome/lain/icons/cal/white/3.png
+!/.config/awesome/lain/icons/cal/white/4.png
+!/.config/awesome/lain/icons/cal/white/5.png
+!/.config/awesome/lain/icons/cal/white/6.png
+!/.config/awesome/lain/icons/cal/white/7.png
+!/.config/awesome/lain/icons/cal/white/8.png
+!/.config/awesome/lain/icons/cal/white/9.png
+!/.config/awesome/lain/icons/layout/default/cascade.png
+!/.config/awesome/lain/icons/layout/default/cascadetile.png
+!/.config/awesome/lain/icons/layout/default/cascadetilew.png
+!/.config/awesome/lain/icons/layout/default/cascadew.png
+!/.config/awesome/lain/icons/layout/default/centerfair.png
+!/.config/awesome/lain/icons/layout/default/centerfairw.png
+!/.config/awesome/lain/icons/layout/default/centerworkh.png
+!/.config/awesome/lain/icons/layout/default/centerworkhw.png
+!/.config/awesome/lain/icons/layout/default/centerwork.png
+!/.config/awesome/lain/icons/layout/default/centerworkw.png
+!/.config/awesome/lain/icons/layout/default/termfair.png
+!/.config/awesome/lain/icons/layout/default/termfairw.png
+!/.config/awesome/lain/icons/layout/zenburn/cascade.png
+!/.config/awesome/lain/icons/layout/zenburn/cascadetile.png
+!/.config/awesome/lain/icons/layout/zenburn/centerfair.png
+!/.config/awesome/lain/icons/layout/zenburn/centerworkh.png
+!/.config/awesome/lain/icons/layout/zenburn/centerwork.png
+!/.config/awesome/lain/icons/layout/zenburn/termfair.png
+!/.config/awesome/lain/icons/mail.png
+!/.config/awesome/lain/icons/no_net.png
+!/.config/awesome/lain/icons/openweathermap/01d.png
+!/.config/awesome/lain/icons/openweathermap/01n.png
+!/.config/awesome/lain/icons/openweathermap/02d.png
+!/.config/awesome/lain/icons/openweathermap/02n.png
+!/.config/awesome/lain/icons/openweathermap/03d.png
+!/.config/awesome/lain/icons/openweathermap/03n.png
+!/.config/awesome/lain/icons/openweathermap/04d.png
+!/.config/awesome/lain/icons/openweathermap/04n.png
+!/.config/awesome/lain/icons/openweathermap/09d.png
+!/.config/awesome/lain/icons/openweathermap/09n.png
+!/.config/awesome/lain/icons/openweathermap/10d.png
+!/.config/awesome/lain/icons/openweathermap/10n.png
+!/.config/awesome/lain/icons/openweathermap/11d.png
+!/.config/awesome/lain/icons/openweathermap/11n.png
+!/.config/awesome/lain/icons/openweathermap/13d.png
+!/.config/awesome/lain/icons/openweathermap/13n.png
+!/.config/awesome/lain/icons/openweathermap/50d.png
+!/.config/awesome/lain/icons/openweathermap/50n.png
+!/.config/awesome/lain/icons/openweathermap/na.png
+!/.config/awesome/lain/icons/openweathermap/README.md
+!/.config/awesome/lain/icons/taskwarrior.png
+!/.config/awesome/lain/init.lua
+!/.config/awesome/lain/ISSUE_TEMPLATE.md
+!/.config/awesome/lain/lain-git.rockspec
+!/.config/awesome/lain/layout/cascade.lua
+!/.config/awesome/lain/layout/centerwork.lua
+!/.config/awesome/lain/layout/init.lua
+!/.config/awesome/lain/layout/termfair.lua
+!/.config/awesome/lain/LICENSE
+!/.config/awesome/lain/README.rst
+!/.config/awesome/lain/scripts/dfs
+!/.config/awesome/lain/util/dkjson.lua
+!/.config/awesome/lain/util/init.lua
+!/.config/awesome/lain/util/markup.lua
+!/.config/awesome/lain/util/quake.lua
+!/.config/awesome/lain/util/separators.lua
+!/.config/awesome/lain/widget/alsabar.lua
+!/.config/awesome/lain/widget/alsa.lua
+!/.config/awesome/lain/widget/bat.lua
+!/.config/awesome/lain/widget/calendar.lua
+!/.config/awesome/lain/widget/contrib/gpmdp.lua
+!/.config/awesome/lain/widget/contrib/init.lua
+!/.config/awesome/lain/widget/contrib/kbdlayout.lua
+!/.config/awesome/lain/widget/contrib/moc.lua
+!/.config/awesome/lain/widget/contrib/redshift.lua
+!/.config/awesome/lain/widget/contrib/task.lua
+!/.config/awesome/lain/widget/contrib/tpbat/init.lua
+!/.config/awesome/lain/widget/contrib/tpbat/smapi.lua
+!/.config/awesome/lain/widget/cpu.lua
+!/.config/awesome/lain/widget/fs.lua
+!/.config/awesome/lain/widget/imap.lua
+!/.config/awesome/lain/widget/init.lua
+!/.config/awesome/lain/widget/mem.lua
+!/.config/awesome/lain/widget/mpd.lua
+!/.config/awesome/lain/widget/net.lua
+!/.config/awesome/lain/widget/pulseaudio.lua
+!/.config/awesome/lain/widget/pulsebar.lua
+!/.config/awesome/lain/widget/sysload.lua
+!/.config/awesome/lain/widget/temp.lua
+!/.config/awesome/lain/widget/watch.lua
+!/.config/awesome/lain/widget/weather.lua
+!/.config/awesome/lain/wiki
+!/.config/awesome/luatz
+!/.config/awesome/modules/luatz/.busted
+!/.config/awesome/modules/luatz/COPYING
+!/.config/awesome/modules/luatz/doc/gettime.md
+!/.config/awesome/modules/luatz/doc/index.md
+!/.config/awesome/modules/luatz/doc/links.md
+!/.config/awesome/modules/luatz/doc/Makefile
+!/.config/awesome/modules/luatz/doc/metadata.yaml
+!/.config/awesome/modules/luatz/doc/parse.md
+!/.config/awesome/modules/luatz/doc/README.md
+!/.config/awesome/modules/luatz/doc/site.css
+!/.config/awesome/modules/luatz/doc/template.html
+!/.config/awesome/modules/luatz/doc/timetable.md
+!/.config/awesome/modules/luatz/doc/tzinfo.md
+!/.config/awesome/modules/luatz/examples/date_arithmetic.lua
+!/.config/awesome/modules/luatz/examples/os_date.lua
+!/.config/awesome/modules/luatz/.gitignore
+!/.config/awesome/modules/luatz/.luacheckrc
+!/.config/awesome/modules/luatz/.luacov
+!/.config/awesome/modules/luatz/luatz/gettime.lua
+!/.config/awesome/modules/luatz/luatz/init.lua
+!/.config/awesome/modules/luatz/luatz/parse.lua
+!/.config/awesome/modules/luatz/luatz-scm-0.rockspec
+!/.config/awesome/modules/luatz/luatz/strftime.lua
+!/.config/awesome/modules/luatz/luatz/timetable.lua
+!/.config/awesome/modules/luatz/luatz/tzcache.lua
+!/.config/awesome/modules/luatz/luatz/tzfile.lua
+!/.config/awesome/modules/luatz/luatz/tzinfo.lua
+!/.config/awesome/modules/luatz/NEWS
+!/.config/awesome/modules/luatz/README.md
+!/.config/awesome/modules/luatz/spec/Godthab.tz
+!/.config/awesome/modules/luatz/spec/parse_spec.lua
+!/.config/awesome/modules/luatz/spec/strftime_spec.lua
+!/.config/awesome/modules/luatz/spec/timetable_spec.lua
+!/.config/awesome/modules/luatz/spec/tzcache_spec.lua
+!/.config/awesome/modules/luatz/spec/tzfile_spec.lua
+!/.config/awesome/modules/luatz/.travis.yml
+!/.config/awesome/rc.lua
+!/.config/awesome/taghelpers.lua
+!/.config/awesome/tblutils.lua
+!/.config/awesome/theme/background.png
+!/.config/awesome/theme/background_white.png
+!/.config/awesome/theme/layouts/cornerne.png
+!/.config/awesome/theme/layouts/cornernew.png
+!/.config/awesome/theme/layouts/cornernw.png
+!/.config/awesome/theme/layouts/cornernww.png
+!/.config/awesome/theme/layouts/cornerse.png
+!/.config/awesome/theme/layouts/cornersew.png
+!/.config/awesome/theme/layouts/cornersw.png
+!/.config/awesome/theme/layouts/cornersww.png
+!/.config/awesome/theme/layouts/dwindle.png
+!/.config/awesome/theme/layouts/dwindlew.png
+!/.config/awesome/theme/layouts/fairh.png
+!/.config/awesome/theme/layouts/fairhw.png
+!/.config/awesome/theme/layouts/fairv.png
+!/.config/awesome/theme/layouts/fairvw.png
+!/.config/awesome/theme/layouts/floating.png
+!/.config/awesome/theme/layouts/floatingw.png
+!/.config/awesome/theme/layouts/fullscreen.png
+!/.config/awesome/theme/layouts/fullscreenw.png
+!/.config/awesome/theme/layouts/magnifier.png
+!/.config/awesome/theme/layouts/magnifierw.png
+!/.config/awesome/theme/layouts/max.png
+!/.config/awesome/theme/layouts/maxw.png
+!/.config/awesome/theme/layouts/spiral.png
+!/.config/awesome/theme/layouts/spiralw.png
+!/.config/awesome/theme/layouts/tilebottom.png
+!/.config/awesome/theme/layouts/tilebottomw.png
+!/.config/awesome/theme/layouts/tileleft.png
+!/.config/awesome/theme/layouts/tileleftw.png
+!/.config/awesome/theme/layouts/tile.png
+!/.config/awesome/theme/layouts/tiletop.png
+!/.config/awesome/theme/layouts/tiletopw.png
+!/.config/awesome/theme/layouts/tilew.png
+!/.config/awesome/theme/README
+!/.config/awesome/theme/submenu.png
+!/.config/awesome/theme/taglist/squarefw.png
+!/.config/awesome/theme/taglist/squarew.png
+!/.config/awesome/theme/theme.lua
+!/.config/awesome/theme/titlebar/close_focus.png
+!/.config/awesome/theme/titlebar/close_normal.png
+!/.config/awesome/theme/titlebar/floating_focus_active.png
+!/.config/awesome/theme/titlebar/floating_focus_inactive.png
+!/.config/awesome/theme/titlebar/floating_normal_active.png
+!/.config/awesome/theme/titlebar/floating_normal_inactive.png
+!/.config/awesome/theme/titlebar/maximized_focus_active.png
+!/.config/awesome/theme/titlebar/maximized_focus_inactive.png
+!/.config/awesome/theme/titlebar/maximized_normal_active.png
+!/.config/awesome/theme/titlebar/maximized_normal_inactive.png
+!/.config/awesome/theme/titlebar/minimize_focus.png
+!/.config/awesome/theme/titlebar/minimize_normal.png
+!/.config/awesome/theme/titlebar/ontop_focus_active.png
+!/.config/awesome/theme/titlebar/ontop_focus_inactive.png
+!/.config/awesome/theme/titlebar/ontop_normal_active.png
+!/.config/awesome/theme/titlebar/ontop_normal_inactive.png
+!/.config/awesome/theme/titlebar/sticky_focus_active.png
+!/.config/awesome/theme/titlebar/sticky_focus_inactive.png
+!/.config/awesome/theme/titlebar/sticky_normal_active.png
+!/.config/awesome/theme/titlebar/sticky_normal_inactive.png
+!/.gitignore.d/awesome
+!/.xsession.d/50-awesomewm
--- /dev/null
+sleep 10 &
+sleeppid=$!
+
+if [ -f ~/code/awesome/awesome ] && [ -x ~/code/awesome/awesome ]; then
+ ~/code/awesome/awesome
+elif [[ -x =awesome ]]; then
+ if awesome --check; then
+ awesome --no-argb
+ else
+ awesome --config ${XDG_CONFIG_HOME:-~/.config}/awesome/stock-rc.lua.
+ fi
+else
+ x-window-manager
+fi
+
+wait $sleeppid