├── .envrc
├── .gitattributes
├── .gitignore
├── Makefile
├── api
└── lawful.fnl
├── async-testing.fnl
├── awesome-macros.fnl
├── commands
├── tag.fnl
└── window.fnl
├── daemons
├── battery.fnl
├── cpu.fnl
├── pulseaudio.fnl
├── ram.fnl
└── workspace-env.fnl
├── features
├── alt-tab.fnl
├── broom.fnl
├── familiar.fnl
├── hydra.fnl
├── motion.fnl
├── nrepl.fnl
├── persistence.fnl
├── titlebar.fnl
├── wallpaper.fnl
└── workspaces.fnl
├── fennel.lua
├── icons
├── _macros.fnl
├── box.fnl
├── layouts.fnl
├── loader.fnl
├── shtwzrd.fnl
└── tabler.fnl
├── init.fnl
├── keybindings.fnl
├── manifest.scm
├── patches
└── fix-snid.lua
├── rc.lua
├── rules.fnl
├── test
├── hotfuzz.fnl
├── icons.fnl
├── init.fnl
└── xml.fnl
├── themes
└── dracula
│ ├── theme.fnl
│ └── titlebar
│ ├── close_focus.svg
│ ├── close_focus_hover.svg
│ ├── close_normal.svg
│ ├── close_normal_hover.svg
│ ├── floating_focus_active.svg
│ ├── floating_focus_active_hover.svg
│ ├── floating_focus_inactive.svg
│ ├── floating_focus_inactive_hover.svg
│ ├── floating_normal_active.svg
│ ├── floating_normal_active_hover.svg
│ ├── floating_normal_inactive.svg
│ ├── floating_normal_inactive_hover.svg
│ ├── maximized_focus_active.svg
│ ├── maximized_focus_active_hover.svg
│ ├── maximized_focus_inactive.svg
│ ├── maximized_focus_inactive_hover.svg
│ ├── maximized_normal_active.svg
│ ├── maximized_normal_active_hover.svg
│ ├── maximized_normal_inactive.svg
│ ├── maximized_normal_inactive_hover.svg
│ ├── minimize_focus.svg
│ ├── minimize_focus_hover.svg
│ ├── minimize_normal.svg
│ ├── minimize_normal_hover.svg
│ ├── ontop_focus_active.svg
│ ├── ontop_focus_active_hover.svg
│ ├── ontop_focus_inactive.svg
│ ├── ontop_focus_inactive_hover.svg
│ ├── ontop_normal_active.svg
│ ├── ontop_normal_active_hover.svg
│ ├── ontop_normal_inactive.svg
│ ├── ontop_normal_inactive_hover.svg
│ ├── sticky_focus_active.svg
│ ├── sticky_focus_active_hover.svg
│ ├── sticky_focus_inactive.svg
│ ├── sticky_focus_inactive_hover.svg
│ ├── sticky_normal_active.svg
│ ├── sticky_normal_active_hover.svg
│ ├── sticky_normal_inactive.svg
│ └── sticky_normal_inactive_hover.svg
├── todo.md
├── user.fnl
├── utils
├── async.fnl
├── hotfuzz.fnl
├── identicon.fnl
├── input.fnl
├── oleander.fnl
├── pulseaudio.fnl
├── screens.fnl
├── tags.fnl
├── widgets.fnl
└── xml.fnl
├── vendor
├── bencode.lua
├── jeejah.lua
├── jeejah
│ └── fenneleval.lua
├── json.lua
├── lume.lua
├── lunatest.lua
└── serpent.lua
└── widgets
├── layout-switcher.fnl
└── workspace-switcher.fnl
/.envrc:
--------------------------------------------------------------------------------
1 | use_guix --load ./manifest.scm
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | vendor/* linguist-vendored
2 | fennel.lua linguist-vendored
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .nrepl-port
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash
2 |
3 | awesome_lib_path := $(shell echo "${GUIX_ENVIRONMENT}/share/awesome/lib/?.lua;${GUIX_ENVIRONMENT}/share/awesome/lib/?/init.lua")
4 | lua_share_path := $(shell echo "${GUIX_ENVIRONMENT}/share/lua/5.1/?.lua;${GUIX_ENVIRONMENT}/share/lua/5.1/init.lua")
5 | lua_lib_path := $(shell echo "${GUIX_ENVIRONMENT}/lib/lua/5.1/?.lua;${GUIX_ENVIRONMENT}/lib/lua/5.1/init.lua")
6 |
7 | awesome_local_path := $(shell echo "${HOME}/.config/awesome/?.lua;${HOME}/.config/awesome/?/?.lua")
8 | lua_path := "$(shell lua -e 'print(package.path)');$(awesome_lib_path);$(awesome_local_path);$(lua_share_path);$(lua_lib_path)"
9 |
10 | lua_cpath := "$(shell lua -e 'print(package.path)');${GUIX_ENVIRONMENT}/lib/lua/5.1/?.so"
11 |
12 | run:
13 | Xephyr :1 -name xephyr_awesome -screen "1366x768" >/dev/null 2>&1 &
14 | sleep 1
15 | DISPLAY=:1.0 awesome -c rc.lua &
16 |
17 | stop:
18 | kill `pgrep Xephyr` >/dev/null 2>&1
19 |
20 | test:
21 | LUA_PATH=${lua_path} LUA_CPATH=${lua_cpath} fennel --globals awesome,client --correlate --metadata --lua luajit test/init.fnl
22 |
23 | .PHONY: run stop test
24 |
--------------------------------------------------------------------------------
/api/lawful.fnl:
--------------------------------------------------------------------------------
1 | (require "patches.fix-snid")
2 | (local lgi (require :lgi))
3 | (local cairo lgi.cairo)
4 | (local rsvg lgi.Rsvg)
5 | (local awful (require :awful))
6 | (local naughty (require :naughty))
7 | (local gears (require :gears))
8 | (local dump gears.debug.dump_return)
9 | (local join gears.table.join)
10 | (local unpack (or table.unpack _G.unpack))
11 | (import-macros {: async : await} :utils.async)
12 |
13 | (var lawful {:ext {}
14 | :geo awful.placement ;; passthrough
15 | :util {:join join}
16 | :fs {}
17 | :img {}
18 | :notify {}})
19 |
20 | (fn lawful.fs.home-dir []
21 | (os.getenv "HOME"))
22 |
23 | (lambda lawful.fs.config-dir []
24 | (.. (lawful.fs.home-dir) "/.config/awesome/"))
25 |
26 | (lambda lawful.fs.cache-dir []
27 | (.. (lawful.fs.home-dir) "/.cache/awesome/"))
28 |
29 | (lambda lawful.fs.icon-dir []
30 | (.. (lawful.fs.home-dir) "/.cache/awesome/icons/"))
31 |
32 | (lambda lawful.img.load-svg [svg-file-name width height]
33 | (let [surf (cairo.ImageSurface cairo.Format.ARGB32 width height)
34 | cr (cairo.Context surf)
35 | handle (assert (rsvg.Handle.new_from_file svg-file-name))
36 | dim (handle:get_dimensions)
37 | aspect (math.min (/ width dim.width) (/ height dim.height))]
38 | (cr:scale aspect aspect)
39 | (handle:render_cairo cr)
40 | surf))
41 |
42 | (lambda lawful.notify.msg [?message ?args]
43 | (let [args (or ?args {})
44 | message (or ?message nil)
45 | tv (type message)]
46 | (if message
47 | (match tv
48 | :string (naughty.notify (join args {:text message}))
49 | :number (naughty.notify (join args {:text (.. "" message)}))
50 | _ (naughty.notify (join args {:text (dump message)})))
51 | (naughty.notify (join args {:text "nil"})))))
52 |
53 | (lambda lawful.notify.info [?message ?timeout]
54 | (lawful.notify.msg ?message {:timeout (or ?timeout 10)}))
55 |
56 | (fn lawful.notify.error [?message]
57 | (let [err-preset {:timeout 0 :bg "#000000" :fg "#ff0000" :max_height 1080}]
58 | (lawful.notify.msg ?message err-preset)))
59 |
60 | (lambda lawful.ext.shellout [cmd]
61 | (async
62 | (let [[out err reason code] (await awful.spawn.easy_async_with_shell cmd)]
63 | {:stdout out
64 | :stderr err
65 | :reason reason
66 | :code code})))
67 |
68 | (lambda lawful.ext.shellout! [cmd]
69 | (let [{: stdout : stderr : code} (lawful.ext.shellout cmd)]
70 | (if (= code 0)
71 | stdout
72 | (lawful.notify.error stderr))))
73 |
74 | (set lawful.ext.spawnbuf {})
75 |
76 | (lambda lawful.ext.spawn [cmd ?props ?cb]
77 | "Spawn application with CMD and set PROPS on resulting client.
78 | Execute callback ?CB if provided, passing the client as the only argument."
79 | ;; launch everything through an interactive bash so aliases can resolve
80 | (let [(pid snid) (awesome.spawn (.. "bash -ic " cmd) true)]
81 | (when snid
82 | (tset lawful.ext.spawnbuf snid [(or ?props {}) ?cb]))
83 | (values pid snid)))
84 |
85 | (client.connect_signal
86 | :manage
87 | (fn [c]
88 | (when c.startup_id
89 | (let [snid c.startup_id
90 | snid-data (. lawful.ext.spawnbuf snid)]
91 | (when snid-data
92 | (let [props (. snid-data 1)
93 | ?cb (. snid-data 2)]
94 | (each [k v (pairs props)]
95 | (tset c k v))
96 | (when (= (. props :titlebars_enabled) false)
97 | (awful.titlebar.hide c))
98 | (when (. props :placement) ; apply placement
99 | ((. props :placement) c))
100 | (when ?cb
101 | (?cb c))
102 | (tset lawful.ext.spawnbuf snid nil)))))))
103 |
104 | lawful
105 |
--------------------------------------------------------------------------------
/async-testing.fnl:
--------------------------------------------------------------------------------
1 | (fn test-simple []
2 | (async
3 | (output.notify "1")
4 | (let [[stdout stderr reason code] (await awful.spawn.easy_async_with_shell "sleep 2 && echo 2")]
5 | (output.notify (or stdout "failed")))))
6 |
7 | (fn test-nested []
8 | (async
9 | (var thing
10 | (do
11 | (output.notify "1")
12 | (output.notify "2")
13 | (async
14 | (let [[stdout stderr reason code] (await awful.spawn.easy_async_with_shell "sleep 2 && echo 3")]
15 | stdout))))
16 |
17 | (output.notify (or thing "failed"))))
18 |
19 | (fn test-nested-serial []
20 | (async
21 | (var thing
22 | (do
23 | (output.notify "1")
24 | (output.notify "2")
25 | (async
26 | (let [[stdout stderr reason code] (await awful.spawn.easy_async_with_shell "sleep 2 && echo 3")]
27 | stdout))
28 | (async
29 | (let [[stdout stderr reason code] (await awful.spawn.easy_async_with_shell "sleep 2 && echo 4")]
30 | stdout))))
31 |
32 | (output.notify (or thing "failed"))))
33 |
34 | (fn test-nested-serial-return []
35 | (async
36 | (var thing
37 | (do
38 | (output.notify "1")
39 | (output.notify "2")
40 | (async
41 | (let [[stdout1 stderr1 reason1 code1] (await awful.spawn.easy_async_with_shell "sleep 2 && echo 4")
42 | [nothin] (output.notify stdout1)
43 | [stdout2 stderr2 reason2 code2] (await awful.spawn.easy_async_with_shell (.. "sleep 2 && echo 4 and " stdout1))]
44 | stdout2))))
45 |
46 | (output.notify (or thing "failed"))))
47 |
--------------------------------------------------------------------------------
/awesome-macros.fnl:
--------------------------------------------------------------------------------
1 | (fn /< [...]
2 | "Create a 'mixed' table like in standard Lua.
3 | The table can contain both sequential and non-sequential keys.
4 | Like:
5 | (/< (awful.titlebar.widget.iconwidget c)
6 | :buttons buttons
7 | :layout wibox.layout.fixed.horizontal)
8 |
9 | Useful for wibox declarations."
10 | (let [tbl {}]
11 | (var skip 0)
12 | (each [i v (ipairs [...])]
13 | (when (~= i skip)
14 | (let [tv (type v)]
15 | (match tv
16 | "string" (do
17 | (set skip (+ i 1))
18 | (tset tbl v (. [...] skip)))
19 | "table" (table.insert tbl v)
20 | _ (error (.. tv " key literal in mixed table"))))))
21 | tbl))
22 |
23 | (fn with-current-screen [name body ...]
24 | "Do something with the currently selected screen."
25 | `(let [,name (awful.screen.focused)]
26 | (when ,name
27 | ,body
28 | ,...)))
29 |
30 | (fn with-current-tag [name body ...]
31 | "Do something with the currently 'active' tag (tag where focus is)."
32 | `(let [,name (. (awful.screen.focused) :selected_tag)]
33 | (when ,name
34 | ,body
35 | ,...)))
36 |
37 | {:/< /<
38 | :with-current-tag with-current-tag
39 | :with-current-screen with-current-screen}
40 |
--------------------------------------------------------------------------------
/commands/tag.fnl:
--------------------------------------------------------------------------------
1 | ;; Functions meant to be called interactively on tags
2 | (local awful (require "awful"))
3 | (local beautiful (require "beautiful"))
4 | (local xresources (require "beautiful.xresources"))
5 | (local dpi xresources.apply_dpi)
6 | (local lume (require "vendor.lume"))
7 | (local tags (require "utils.tags"))
8 |
9 | (local gap-step 4)
10 |
11 | (local tagcmd {})
12 |
13 | (lambda tagcmd.layout-fn [layout]
14 | "Return a 0-arity function which changes the layout of current tag to LAYOUT"
15 | (fn []
16 | (let [s (awful.screen.focused)
17 | ct s.selected_tag]
18 | (tset ct :layout layout))))
19 |
20 | (fn tagcmd.destroy-current []
21 | "Delete the currently focused tag"
22 | (tags.destroy (. (awful.screen.focused) :selected_tag)))
23 |
24 | (fn tagcmd.go-right []
25 | "Unmap current tag and map next tag, creating a new tag if none exists"
26 | (tags.view-next (awful.screen.focused)))
27 |
28 | (fn tagcmd.go-left []
29 | "Unmap current tag and map previous tag"
30 | (tags.view-prev (awful.screen.focused)))
31 |
32 | (fn tagcmd.inc-gap []
33 | "Grow the gap between clients"
34 | (let [s (awful.screen.focused)
35 | ct s.selected_tag]
36 | (tset ct :gap (+ gap-step ct.gap))))
37 |
38 | (fn tagcmd.dec-gap []
39 | "Shrink the gap between clients"
40 | (let [s (awful.screen.focused)
41 | ct s.selected_tag
42 | cgap (- ct.gap gap-step)]
43 | (tset ct :gap (if (> cgap 0) cgap 0))))
44 |
45 | (fn tagcmd.inc-masters []
46 | "Increase the number of windows that share the master area"
47 | (awful.tag.incnmaster 1))
48 |
49 | (fn tagcmd.dec-masters []
50 | "Decrease the number of windows that share the master area"
51 | (awful.tag.incnmaster -1))
52 |
53 | (fn tagcmd.inc-master-width []
54 | "Increase the master width factor, if the layout permits"
55 | (awful.tag.incmwfact 0.05))
56 |
57 | (fn tagcmd.dec-master-width []
58 | "Decrease the master width factor, if the layout permits"
59 | (awful.tag.incmwfact -0.05))
60 |
61 | (fn tagcmd.inc-cols []
62 | "Increase the number of columns, if the layout permits"
63 | (let [s (awful.screen.focused)
64 | ct s.selected_tag]
65 | (tset ct :column_count (+ 1 ct.column_count))))
66 |
67 | (fn tagcmd.dec-cols []
68 | "Decrease the number of columns, if the layout permits"
69 | (let [s (awful.screen.focused)
70 | ct s.selected_tag]
71 | (tset ct :column_count (- ct.column_count 1))))
72 |
73 | (fn tagcmd.toggle-fill-policy []
74 | "Toggle size fill policy for master client(s)"
75 | (let [s (awful.screen.focused)
76 | ct s.selected_tag]
77 | (awful.tag.togglemfpol ct)))
78 |
79 | tagcmd
80 |
--------------------------------------------------------------------------------
/daemons/battery.fnl:
--------------------------------------------------------------------------------
1 | ;; Provides signals --
2 | ;; battery::capacity
3 | ;; battery::status
4 | ;; Requires --
5 | ;; upower
6 |
7 | (local awful (require "awful"))
8 | (local lume (require "vendor.lume"))
9 | (local {: notify } (require :api.lawful))
10 |
11 | (local listen-script "bash -c 'upower --monitor'")
12 | (local capacity-script "bash -c 'cat /sys/class/power_supply/BAT*/capacity'")
13 | (local status-script "upower -i '/org/freedesktop/UPower/devices/battery_BAT0'")
14 |
15 | (lambda average-capacity [line-output]
16 | "Take a string of new-line delimited numbers and average them"
17 | (var t {})
18 | (each [l (: line-output :gmatch "([^\n]*)\n?")]
19 | (table.insert t (tonumber l)))
20 | (let [sum (lume.reduce t (fn [a b] (+ a b)))]
21 | (/ sum (# t))))
22 |
23 | (lambda get-field [str key]
24 | (lume.trim (or (: str :match (.. key "(.-)\n")) "")))
25 |
26 | (lambda get-capacity [callback]
27 | "Asynchronously get the averaged capacity of all connected batteries"
28 | (awful.spawn.easy_async capacity-script
29 | (fn [out]
30 | (let [capacity (average-capacity out)]
31 | (callback capacity)))))
32 |
33 | (lambda get-status [callback]
34 | "Asynchronously get the status of the first connected battery.
35 | Returns map with keys :time-to-empty, :state, :time-to-full"
36 | (awful.spawn.easy_async status-script
37 | (fn [out]
38 | (let [state (get-field out "state:")
39 | toe (get-field out "time to empty:")
40 | tof (get-field out "time to full:")]
41 | (callback {:state state
42 | :time-to-empty toe
43 | :time-to-full tof})))))
44 |
45 | (fn emit []
46 | (get-capacity (fn [cap] (awesome.emit_signal "battery::percentage" cap)
47 | (get-status (fn [stats] (awesome.emit_signal "battery::status" stats))))))
48 |
49 | (lambda listen [callback]
50 | (awful.spawn.with_line_callback listen-script {
51 | :stdout callback
52 | :stderr (fn [err] (notify.error err))
53 | }))
54 |
55 | (listen emit)
56 |
--------------------------------------------------------------------------------
/daemons/cpu.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local gears (require "gears"))
3 | (local lume (require "vendor.lume"))
4 |
5 | (local interval 3)
6 | (local cpu-script
7 | "bash -c 'vmstat 1 2 | tail -1 | tr -s [:blank:] | cut -d \" \" -f16'")
8 |
9 | (lambda get-cpu [callback]
10 | (awful.spawn.easy_async cpu-script
11 | (fn [res]
12 | (let [sanitized (lume.trim res)
13 | cpu-idle (tonumber sanitized)
14 | cpu-used (- 100 (or cpu-idle 0))]
15 | (callback cpu-used)))))
16 |
17 | (fn emit []
18 | (get-cpu (fn [usage] (awesome.emit_signal "cpu::usage" usage))))
19 |
20 | (lambda listen [callback]
21 | (gears.timer {:autostart true
22 | :call_now true
23 | :timeout interval
24 | :callback callback}))
25 |
26 | (listen emit)
27 |
--------------------------------------------------------------------------------
/daemons/pulseaudio.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local pa (require "utils.pulseaudio"))
3 | (local {: notify } (require :api.lawful))
4 |
5 | (fn get-volume [?callback]
6 | (let [cb (or ?callback (fn [] nil))]
7 | (awful.spawn.easy_async_with_shell pa.get-volume-script cb)))
8 |
9 | (fn get-muted [?callback]
10 | (let [cb (or ?callback (fn [] nil))]
11 | (awful.spawn.easy_async_with_shell pa.get-muted-script cb)))
12 |
13 | (fn get-mic-muted [?callback]
14 | (let [cb (or ?callback (fn [] nil))]
15 | (awful.spawn.easy_async_with_shell pa.get-mic-muted-script cb)))
16 |
17 | (fn emit-output-data []
18 | (get-volume (fn [vol]
19 | (awesome.emit_signal
20 | "pulseaudio::output::volume"
21 | (tonumber vol))))
22 | (get-muted (fn [muted?]
23 | (awesome.emit_signal
24 | "pulseaudio::output::muted"
25 | (if (string.find muted? "no") false true)))))
26 |
27 | (fn emit-input-data []
28 | (get-mic-muted (fn [muted?]
29 | (awesome.emit_signal
30 | "pulseaudio::input::muted"
31 | (if (string.find muted? "no") false true)))))
32 |
33 | (lambda listen-volume [callback]
34 | (awful.spawn.with_line_callback pa.volume-listener-script
35 | {
36 | :stdout callback
37 | :stderr (fn [err] (notify.error err))
38 | }))
39 |
40 | (lambda listen-mic [callback]
41 | (awful.spawn.with_line_callback pa.mic-listener-script
42 | {
43 | :stdout callback
44 | :stderr (fn [err] (notify.error err))
45 | }))
46 |
47 | ;; run once on start-up to push out an initial value
48 | (emit-output-data)
49 | (emit-input-data)
50 |
51 | ;; find any orphaned listeners and kill them before spawning a new one
52 | (awful.spawn.easy_async_with_shell
53 | pa.kill-listeners-script
54 | (fn []
55 | (listen-volume emit-output-data)
56 | (listen-mic emit-input-data)))
57 |
--------------------------------------------------------------------------------
/daemons/ram.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local gears (require "gears"))
3 | (local lume (require "vendor.lume"))
4 |
5 | (local interval 15)
6 | (local ram-script
7 | "bash -c \"free | grep Mem | awk '{print $3/$2 * 100.0}'\"")
8 |
9 | (lambda get-ram-usage [callback]
10 | (awful.spawn.easy_async ram-script
11 | (fn [res]
12 | (let [ram-used (tonumber res)]
13 | (callback ram-used)))))
14 |
15 | (fn emit []
16 | (get-ram-usage (fn [usage] (awesome.emit_signal "ram::usage" usage))))
17 |
18 | (lambda listen [callback]
19 | (gears.timer {:autostart true
20 | :call_now true
21 | :timeout interval
22 | :callback callback}))
23 |
24 | (listen emit)
25 |
--------------------------------------------------------------------------------
/daemons/workspace-env.fnl:
--------------------------------------------------------------------------------
1 | (local workspaces (require :features.workspaces))
2 | (local { : notify : ext } (require :api.lawful))
3 | (import-macros {: async : await} :utils.async)
4 |
5 | (fn inject-bash-workspace-env []
6 | (with-open [bashrc (io.open (.. (os.getenv "HOME") "/.bashrc") :a+)]
7 | (let [source-line "source ~/.cache/awesome/wsenv"
8 | content (bashrc:read "*all")]
9 | (when (not (string.match content source-line))
10 | (bashrc:write source-line)))))
11 |
12 | (fn prepare-firefox-profile [workspace-name]
13 | (let [config-path (.. (os.getenv "HOME") "/.config/firefox")
14 | profile-path (.. (os.getenv "HOME") "/.mozilla/firefox/" workspace-name)]
15 | (ext.shellout (.. "firefox -CreateProfile \"" workspace-name " " profile-path "\""))))
16 | ; (ext.shellout (.. "ln -f -s " config-path "/* " profile-path))))
17 |
18 | (fn spawn-workspace-daemons [workspace-name]
19 | (prepare-firefox-profile workspace-name)
20 | (when (not (= workspace-name :default))
21 | (do
22 | (ext.shellout (.. "emacs --daemon=" workspace-name)))))
23 |
24 | (fn generate-bash-aliases [workspace-name]
25 | (let [filepath (.. (os.getenv "HOME") "/.cache/awesome" "/wsenv")
26 | lines (if (= workspace-name :default)
27 | [(.. "alias emacsclient='emacsclient -c'")
28 | (.. "alias e='emacsclient'")
29 | (.. "alias firefox='firefox -new-instance -P " workspace-name "'")]
30 | [(.. "alias emacsclient='emacsclient -c -s " workspace-name "'")
31 | (.. "alias e='emacsclient'")
32 | (.. "alias firefox='firefox -new-instance -P " workspace-name "'")]
33 | )]
34 | (with-open [outfile (io.open filepath :w)]
35 | (outfile:write
36 | (table.concat lines "\n")))))
37 |
38 | (awesome.connect_signal
39 | "startup"
40 | (fn []
41 | (async
42 | (inject-bash-workspace-env))))
43 |
44 | (awesome.connect_signal
45 | "workspaces::applied"
46 | (fn [workspace-name]
47 | (async
48 | (doto workspace-name
49 | (spawn-workspace-daemons)
50 | (generate-bash-aliases)))))
51 |
--------------------------------------------------------------------------------
/features/alt-tab.fnl:
--------------------------------------------------------------------------------
1 | ;; Not functional yet
2 | ;; Waiting for gears.history API
3 | ;; https://www.reddit.com/r/awesomewm/comments/ams2gt/keygrabber_example_in_documentation/
4 |
5 | (local awful (require "awful"))
6 | (local input (require "utils.input"))
7 |
8 | (local bindings
9 | [
10 | ; [[:Mod1 ] :Tab awful.client.focus.history.select_previous ]
11 | ; [[:Mod1 :Shift] :Tab awful.client.focus.history.select_next ]
12 | ])
13 |
14 | (local alt-tab-config
15 | {
16 | :keybindings bindings
17 | :stop_key :Mod1
18 | :stop_event :release
19 | :start_callback awful.client.focus.history.disable_tracking
20 | :stop_callback awful.client.focus.history.enable_tracking
21 | :export_keybindings true
22 | })
23 |
24 | (local grabber-fn (awful.keygrabber alt-tab-config))
25 |
26 | (input.keybind "client" [:alt :shift] :Tab grabber-fn "cycle focus backward")
27 |
28 | (local
29 | alt-tab
30 | (input.keybind "client" [:alt] :Tab grabber-fn "cycle focus forward"))
31 |
32 |
33 | alt-tab
34 |
--------------------------------------------------------------------------------
/features/familiar.fnl:
--------------------------------------------------------------------------------
1 | ; ██
2 | ; ░░████░░
3 | ; ▒▒ ▒▒████ ▒▒
4 | ; ██ ██████▒▒ ██
5 | ; ██████░░▒▒████████▓▓░░ ▓▓▒▒
6 | ; ██ ▒▒████████████████████ ▒▒▓▓░░████▒▒
7 | ; ░░████ ██████████████████ ██████████████
8 | ; ████████████████████████ ██████████████
9 | ; ▒▒░░ ████████████████████ ██████████████▒▒
10 | ; ████████████████████████░░ ▒▒██████████████
11 | ; ██████████████████ ██████████████
12 | ; ░░██████████████████████████
13 | ; ░░████████████████████████
14 | ; ██████████████████████████
15 | ; ▒▒████████████████████████
16 | ; ██████████████████████░░
17 | ; ████████████ ██▓▓▒▒
18 | ; ▒▒██████████ ████
19 | ; ██████████ ░░▒▒
20 | ; ▒▒████████ ▒▒
21 | ; ██████████ ▓▓░░██░░▒▒
22 | ; ████████ ▒▒▒▒
23 | ; ▒▒████▒▒
24 | ; ░░██▓▓
25 | ; ██
26 |
27 | ;; “You see, a witch has to have a familiar, some little animal like a cat or
28 | ;; toad. He helps her somehow. When the witch dies the familiar is supposed to
29 | ;; die too, but sometimes it doesn't.”
30 | ;; -- Henry Kuttner, “Before I Wake”
31 |
32 | (local awful (require "awful"))
33 | (local lume (require "vendor.lume"))
34 | (local input (require "utils.input"))
35 | (local persistence (require "features.persistence"))
36 | (local {: ext : geo} (require "api.lawful"))
37 | (local {: concat } (require "utils.oleander"))
38 |
39 | (local fam {})
40 |
41 | (set fam.default-config {
42 | :placement :left
43 | :screen-ratio 0.33
44 | })
45 |
46 | (set fam.collection {})
47 |
48 | (lambda tag-familiar [conf]
49 | "Return a function for capturing a familiar's window ID after it has mapped"
50 | (lambda [client]
51 | (tset fam.collection
52 | (.. "" client.window)
53 | (lume.merge conf {:wid client.window}))))
54 |
55 | (lambda axis-from-dir [dir]
56 | (match dir
57 | :left :vertical
58 | :right :vertical
59 | :top :horizontal
60 | :bottom :horizontal
61 | _ (error (.. "Unknown direction '"
62 | dir
63 | "': expected top, left, right or bottom"))))
64 |
65 | (lambda gen-placement [conf]
66 | "Generate a placement function based on a familiar's config"
67 | (fn [c]
68 | (let [axis (axis-from-dir conf.placement)
69 | p (+ geo.scale
70 | (. geo conf.placement)
71 | (. geo (.. "maximize_" axis "ly")))]
72 | (p c {:to_percent conf.screen-ratio :honor_workarea false}))))
73 |
74 | (lambda summon-familiar [conf]
75 | "Summon familiar with configuration CONF"
76 | (let [placement (gen-placement conf)
77 | (pid snid) (ext.spawn conf.command
78 | {
79 | :is_familiar true
80 | :familiar_name conf.name
81 | :floating true
82 | :ontop true
83 | :sticky true
84 | :placement placement
85 | :border_width 0
86 | :skip_taskbar true
87 | :hidden false
88 | :size_hints_honor false
89 | :requests_no_titlebar true
90 | :titlebars_enabled false}
91 | (tag-familiar conf)
92 | )]
93 | nil))
94 |
95 | (lambda find-familiar [name]
96 | "Enumerate current clients and return the familiar named NAME"
97 | ((awful.client.iterate (fn [c] (= c.familiar_name name)))))
98 |
99 | (lambda find-or-summon-familiar [conf]
100 | "Find familiar named CONF.NAME, summoning it if it does not exist"
101 | (let [f (find-familiar conf.name)]
102 | (or f (summon-familiar conf))))
103 |
104 | (lambda toggle-familiar [conf]
105 | "Call or dismiss familiar named CONF.NAME"
106 | (let [f (find-or-summon-familiar conf)]
107 | (when f
108 | (tset f :hidden (not f.hidden))
109 | (when (not f.hidden)
110 | (: f :emit_signal "request::activate")))))
111 |
112 | (fn fam.save []
113 | "Return current state for persistence"
114 | {:collection fam.collection})
115 |
116 | (lambda fam.load [state]
117 | "Restore contents of STATE and apply it"
118 | (set fam.collection (or state.collection fam.collection))
119 | (each [_ c (ipairs (client.get))]
120 | (let [wid (.. "" c.window)]
121 | (when (. fam.collection wid)
122 | (let [conf (. fam.collection wid)
123 | placement (gen-placement conf)]
124 | (tset c :familiar_name conf.name)
125 | (tset c :is_familiar true)
126 | (tset c :border_width 0)
127 | (tset c :ontop true)
128 | (tset c :sticky true)
129 | (tset c :hidden true)
130 | (awful.titlebar.hide c)
131 | (placement c))))))
132 |
133 | (persistence.register "familiars" fam.save fam.load)
134 |
135 | (lambda fam.def [conf]
136 | "Define a familiar with properties map CONF.
137 |
138 | CONF has properties --
139 | :name -- every familiar deserves a unique and fitting name
140 | :key -- key for summoning familiar, in form [[MODIFIERS] KEY-CODE]
141 | :command -- command to execute, along with any arguments
142 | :screen-ratio -- 0 < decimal < 1 representing how much of the screen it covers
143 | :placement -- portion of screen (left, right, top, bottom) it calls home"
144 | (let [category "familiars"
145 | description (.. "⏼ " conf.name " visible")
146 | merge-conf (lume.merge fam.default-config conf)
147 | toggle-fn (fn [] (toggle-familiar merge-conf))
148 | [mods key] conf.key
149 | binding (input.keybind category mods key toggle-fn description)]
150 | (root.keys (concat (root.keys) binding))))
151 |
152 | fam.def
153 |
--------------------------------------------------------------------------------
/features/motion.fnl:
--------------------------------------------------------------------------------
1 | (local lume (require "lume"))
2 | ;[:g my.gap.function "gaps" "misc section"]
3 |
4 | (fn parse
5 | [_ _ _ sequence]
6 | (let [tokens (lume.split (: sequence :gsub "%D" " %1 "))]
7 |
--------------------------------------------------------------------------------
/features/nrepl.fnl:
--------------------------------------------------------------------------------
1 | (local lawful (require :api.lawful))
2 | (local gears (require :gears))
3 | (local jeejah (require :vendor.jeejah))
4 |
5 | (local nrepl-port-filepath (.. (lawful.fs.config-dir) ".nrepl-port"))
6 |
7 | (var _coro nil)
8 |
9 | (lambda spawn-nrepl [?port]
10 | (let [port (or ?port 7888)]
11 | (set _coro (jeejah.start port {:debug true :fennel true}))
12 | (with-open [nrepl-port-file (io.open nrepl-port-filepath :w)]
13 | (nrepl-port-file:write port))
14 | (lawful.notify.info (.. "nREPL server listening on " port "."))
15 | (gears.timer {:autostart true
16 | :call_now true
17 | :timeout 0.100
18 | :callback (fn [] (coroutine.resume _coro))})))
19 |
20 | (lambda kill-nrepl []
21 | (jeejah.stop _coro)
22 | (os.remove nrepl-port-filepath)
23 | (lawful.notify.info "nREPL server disconnected."))
24 |
25 | {:spawn-nrepl spawn-nrepl
26 | :kill-nrepl kill-nrepl}
27 |
--------------------------------------------------------------------------------
/features/persistence.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local json (require "vendor.json"))
3 | (local lume (require "vendor.lume"))
4 | (local screen-utils (require "utils.screens"))
5 | (local { : notify} (require :api.lawful))
6 |
7 | (local persistence {})
8 |
9 | (local signame "persistence::")
10 | (local cachedir (awful.util.get_cache_dir))
11 | (local statefile "state.json")
12 | (local permafile "persisted.json")
13 |
14 | (local subscribers [])
15 |
16 | ;; initialize as disabled
17 | (set persistence.enabled false)
18 |
19 | (lambda write-file [path filename content]
20 | (let [f (io.open (.. path "/" filename) "w")]
21 | (: f :write content)
22 | (: f :close)))
23 |
24 | (lambda read-file [path filename]
25 | (match (io.open (.. path "/" filename) "r")
26 | [nil err] (do (write-file path filename "{}")
27 | (match (io.open (.. path "/" filename) "r")
28 | [nil err] (notify.err err)
29 | f (: f :read "*all")))
30 | f (: f :read "*all")))
31 |
32 | (lambda delete-file [path filename]
33 | (os.remove (.. path filename)))
34 |
35 | (lambda persistence.register [name save on-load ?persist-across-reboot?]
36 | "Register hooks for saving and loading state"
37 | (table.insert subscribers {:name name
38 | :save save
39 | :on-load on-load
40 | :perma ?persist-across-reboot?}))
41 |
42 | (fn persistence.save-all []
43 | (var new-state {})
44 | (var new-perma {})
45 | (awesome.emit_signal (.. signame "saving"))
46 | (each [_ sub (ipairs subscribers)]
47 | (let [s (if sub.perma new-perma new-state)]
48 | (awesome.emit_signal (.. signame sub.name "::" "saving"))
49 | (tset s sub.name (sub.save))
50 | (awesome.emit_signal (.. signame sub.name "::" "saved"))))
51 | (when persistence.enabled
52 | (write-file cachedir statefile (json.encode new-state))
53 | (write-file cachedir permafile (json.encode new-perma)))
54 | (awesome.emit_signal (.. signame "saved")))
55 |
56 | (fn persistence.load-all []
57 | (awesome.emit_signal (.. signame "loading"))
58 | (let [state-file (read-file cachedir statefile)
59 | perma-file (read-file cachedir permafile)
60 | s (if persistence.enabled
61 | (if state-file (json.decode state-file) {})
62 | {})
63 | p (if persistence.enabled
64 | (if perma-file (json.decode perma-file) {})
65 | {})]
66 | (each [_ sub (ipairs subscribers)]
67 | (let [data (if sub.perma p s)]
68 | (when data
69 | (awesome.emit_signal
70 | (.. signame sub.name "::" "loading"))
71 | (sub.on-load (or (. data sub.name) {}))
72 | (awesome.emit_signal
73 | (.. signame sub.name "::" "loaded")))))
74 | (awesome.emit_signal (.. signame "loaded"))))
75 |
76 | (fn layout-name [layout]
77 | (let [tv (type layout)]
78 | (match tv
79 | "function" (. (layout) :name)
80 | "table" layout.name
81 | _ (error (.. "Layout should be a function or table, got " tv)))))
82 |
83 | (fn save-tags []
84 | (let [current-tags (root.tags)]
85 | (lume.map
86 | current-tags
87 | (fn [t] {
88 | :name t.name
89 | :selected t.selected
90 | :activated t.activated
91 | :index t.index
92 | :screen-index t.screen.index
93 | :master-width-factor t.master_width_factor
94 | :layout (layout-name t.layout)
95 | :volatile t.volatile
96 | :gap t.gap
97 | :gap-single-client t.gap_single_client
98 | :master-fill-policy t.master_fill_policy
99 | :master-count t.master_count
100 | :icon t.icon
101 | :column-count t.column_count
102 | :window-ids (lume.map (: t :clients) (fn [c] c.window))
103 | }))))
104 |
105 | (fn load-tags [map]
106 | (let [screens (screen-utils.get-screens)
107 | clients (client.get)]
108 | (each [_ t (pairs map)]
109 | (let [scr (or (. screens t.screen-index) screen.primary)
110 | lay (-> awful.layout.layouts
111 | (lume.filter (fn [l] (= l.name t.layout)))
112 | (lume.first))]
113 | (local tag (awful.tag.add
114 | t.name
115 | {
116 | :selected t.selected
117 | :activated t.activated
118 | :hide t.hide
119 | :index t.index
120 | :screen scr
121 | :master_width_factor t.master-width-factor
122 | :layout lay
123 | :volatile t.volatile
124 | :gap_single_client t.gap-single-client
125 | :master_fill_policy t.master-fill-policy
126 | :master_count t.master-count
127 | :icon t.icon
128 | :column_count t.column-count
129 | }))
130 | (each [_ c (ipairs clients)]
131 | (when (lume.match t.window-ids (fn [wid] (= wid c.window)))
132 | (: c :move_to_tag tag)))))))
133 |
134 | (persistence.register "tags" save-tags load-tags)
135 |
136 | (fn persistence.enable []
137 | (set persistence.enabled true))
138 |
139 | ;; other features can depend on persistence's signals,
140 | ;; even if persistence is not enabled
141 | (awesome.connect_signal
142 | "exit"
143 | (fn [restart?]
144 | (if restart?
145 | (persistence.save-all)
146 | (persistence.delete-file cachedir statefile))))
147 |
148 | (awesome.connect_signal "startup" (fn [] (persistence.load-all)))
149 |
150 | persistence
151 |
--------------------------------------------------------------------------------
/features/titlebar.fnl:
--------------------------------------------------------------------------------
1 | (local wibox (require :wibox))
2 | (local awful (require :awful))
3 | (local lawful (require :api.lawful))
4 | (local {: map} (require :lume))
5 | (import-macros {: /< : with-current-tag } :awesome-macros)
6 |
7 | (var titlebars? false)
8 | (var enabled? false)
9 |
10 | (fn add-titlebar [c]
11 | "Decorate client C with a titlebar."
12 | (let [buttons (lawful.util.join
13 | (awful.button [] 1 (fn []
14 | (: c :emit_signal :request::activate :titlebar {:raise true})
15 | (awful.mouse.client.move c)))
16 | (awful.button [] 3 (fn []
17 | (: c :emit_signal :request::activate :titlebar {:raise true})
18 | (awful.mouse.client.resize c))))
19 | titlebar (awful.titlebar c)]
20 | (: titlebar :setup (/<
21 | (/< ;; Left
22 | (awful.titlebar.widget.iconwidget c)
23 | :buttons buttons
24 | :layout wibox.layout.fixed.horizontal)
25 | (/< ;; Middle
26 | ;; (/< ;; Title
27 | ;; :align "center"
28 | ;; :widget (awful.titlebar.widget.titlewidget c))
29 | :buttons buttons
30 | :layout wibox.layout.flex.horizontal)
31 | (/< ;; Right
32 | (awful.titlebar.widget.floatingbutton c)
33 | (awful.titlebar.widget.maximizedbutton c)
34 | (awful.titlebar.widget.stickybutton c)
35 | (awful.titlebar.widget.ontopbutton c)
36 | (awful.titlebar.widget.closebutton c)
37 | :layout (wibox.layout.fixed.horizontal))
38 | :layout wibox.layout.align.horizontal))
39 | (when (not titlebars?)
40 | (awful.titlebar.hide c))))
41 |
42 | (fn enable []
43 | "Enable titlebars for all windows in current tag."
44 | (set titlebars? true)
45 | (with-current-tag t
46 | (map (t:clients) (fn [c]
47 | (awful.titlebar.show c)))))
48 |
49 | (fn disable []
50 | "Disable titlebars for all windows in current tag."
51 | (set titlebars? false)
52 | (with-current-tag t
53 | (map (t:clients) (fn [c]
54 | (awful.titlebar.hide c)))))
55 |
56 | (fn toggle []
57 | "Toggle titlebars for all windows in current tag."
58 | (if titlebars?
59 | (disable)
60 | (enable)))
61 |
62 | (client.connect_signal :request::titlebars add-titlebar)
63 |
64 | {:toggle toggle
65 | :disable disable
66 | :enable enable}
67 |
--------------------------------------------------------------------------------
/features/wallpaper.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local gears (require "gears"))
3 | (local beautiful (require "beautiful"))
4 |
5 | (local wallpaper {})
6 |
7 | (lambda wallpaper.set [s]
8 | (when beautiful.wallpaper
9 | (var wallpaper beautiful.wallpaper)
10 | ;; If wallpaper is a function, call it with the screen
11 | (when (= (type wallpaper) "function")
12 | (set wallpaper (wallpaper s)))
13 | ;; workaround memleak when called frequently eg. by a timer
14 | ;; https://github.com/awesomeWM/awesome/issues/2858
15 | (collectgarbage "step" 4000)
16 | (gears.wallpaper.maximized wallpaper s true)))
17 |
18 | (fn wallpaper.enable []
19 | (awful.screen.connect_for_each_screen wallpaper.set)
20 | ;; Reset wallpaper when a screen's geometry changes (eg. resolution change)
21 | (screen.connect_signal "property::geometry" wallpaper.set))
22 |
23 | wallpaper
24 |
--------------------------------------------------------------------------------
/features/workspaces.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local lume (require "vendor.lume"))
3 | (local tag-utils (require "utils.tags"))
4 | (local screen-utils (require "utils.screens"))
5 | (local identicon (require "utils.identicon"))
6 | (local user (require "user"))
7 | (local persistence (require "features.persistence"))
8 |
9 | (local workspaces {})
10 |
11 | (set workspaces.map {:default {:tags [] :selected []}})
12 | (set workspaces.current :default)
13 |
14 | (set workspaces.name-cache [])
15 |
16 | (fn workspaces.get [?name]
17 | "Get the current workspace, or the workspace with name ?NAME"
18 | (let [name (or ?name workspaces.current)]
19 | (. workspaces.map name)))
20 |
21 | (fn workspaces.get-icon [name]
22 | "Get the icon for workspace with NAME"
23 | (if (= name :default)
24 | user.avatar
25 | (identicon.create name 7 32)))
26 |
27 | (fn workspaces.list []
28 | "List active workspaces"
29 | (lume.keys workspaces.map))
30 |
31 | (lambda workspaces.create [name]
32 | "Create a new workspace with name NAME, if it does not already exist"
33 | (when (not (workspaces.get name))
34 | (tset workspaces.map name {:tags [] :selected []})))
35 |
36 | (fn workspaces.reverse-tag-lookup []
37 | "Get a table where keys are tag names and values are workspace names"
38 | (let [concat (fn [a b] (lume.merge a b))]
39 | (-> (lume.keys workspaces.map)
40 | (lume.map
41 | (fn [k]
42 | (let [v (. workspaces.map k)
43 | kv (lume.map v.tags (fn [t] {t k}))]
44 | (lume.reduce kv concat {}))))
45 | (lume.reduce concat))))
46 |
47 | (lambda workspaces.attach-tag [tag ?client ?workspace]
48 | "Associate TAG with ?WORKSPACE or current workspace, if it has no connections"
49 | (let [lookup (workspaces.reverse-tag-lookup)]
50 | (let [wsname (or (. lookup tag.name) ?workspace workspaces.current)
51 | ws (workspaces.get wsname)]
52 | (when (not (lume.find (or ws.tags []) tag.name))
53 | (table.insert (. ws :tags) tag.name))
54 | (tset tag :workspace wsname))))
55 |
56 | (fn workspaces.save-selected-tags []
57 | "Find all selected tags across all screens and persist them"
58 | (var selection [])
59 | (each [scr (screen-utils.screen-iter)]
60 | (set selection
61 | (lume.concat selection
62 | (lume.map (. scr :selected_tags) (fn [t] t.name)))))
63 | (let [selected-tags (. (. workspaces.map workspaces.current) :tags)
64 | filtered (lume.filter selection (fn [s] (lume.find selected-tags s)))]
65 | (tset (. workspaces.map workspaces.current) :selected filtered)))
66 |
67 | (fn workspaces.restore-selected-tags []
68 | "Re-select the last known selected tags for the current workspace"
69 | (lume.each (. (. workspaces.map workspaces.current) :selected)
70 | (fn [name] (let [t (awful.tag.find_by_name nil name)]
71 | (tset t :selected true)))))
72 |
73 | (lambda workspaces.clear-tag-ref [tagname]
74 | "Walk over the workspaces map, eliminating references to TAGNAME"
75 | (each [wsname (pairs workspaces.map)]
76 | (let [ws (. workspaces.map wsname)]
77 | (lume.remove (. ws :tags) tagname)
78 | (lume.remove (. ws :selected) tagname))))
79 |
80 | (lambda workspaces.apply [?name]
81 | "Activate ?NAME or current workspace, deactivating tags not attached to it"
82 | (let [ws (or ?name workspaces.current)
83 | tags (root.tags)
84 | predicate (fn [t] (= t.workspace ws))
85 | active (lume.filter tags predicate)
86 | inactive (lume.reject tags predicate)]
87 |
88 | (awesome.emit_signal "workspaces::applying" ws)
89 | (workspaces.save-selected-tags)
90 | (set workspaces.current ws)
91 | (each [_ tag (ipairs active)]
92 | (tag-utils.activate tag))
93 | (each [_ tag (ipairs inactive)]
94 | (tag-utils.deactivate tag))
95 | (workspaces.restore-selected-tags)
96 | ;; if there's no active tags on a screen, create one
97 | (each [scr (screen-utils.screen-iter)]
98 | (when (= (# (tag-utils.list-visible scr)) 0)
99 | (tag-utils.create scr nil {:workspace ws})))
100 | (awesome.emit_signal "workspaces::applied" ws)))
101 |
102 | (fn workspaces.save []
103 | "Return current state for persistence"
104 | {:current workspaces.current
105 | :map workspaces.map})
106 |
107 | (lambda workspaces.load [state]
108 | "Restore contents of STATE and apply it"
109 | (set workspaces.current (or state.current workspaces.current))
110 | (set workspaces.map (or state.map workspaces.map))
111 | (let [lookup (workspaces.reverse-tag-lookup)]
112 | (each [_ t (ipairs (root.tags))]
113 | ;; if a tag has no workspace (orphaned), attach it to the current workspace
114 | (workspaces.attach-tag t nil (or (. lookup tag.name) workspaces.current)))
115 | (awful.tag.attached_connect_signal nil "tagged" workspaces.attach-tag)
116 | (workspaces.apply)))
117 |
118 | (fn workspaces.save-names []
119 | workspaces.name-cache)
120 |
121 | (lambda workspaces.load-names [l]
122 | (set workspaces.name-cache l))
123 |
124 | (fn workspaces.enable []
125 | (awesome.emit_signal "workspaces::init")
126 | (awesome.connect_signal "tag::deleted" workspaces.clear-tag-ref)
127 | (persistence.register "workspaces" workspaces.save workspaces.load)
128 | (persistence.register "workspace-names"
129 | workspaces.save-names
130 | workspaces.load-names
131 | true))
132 |
133 | workspaces
134 |
--------------------------------------------------------------------------------
/icons/_macros.fnl:
--------------------------------------------------------------------------------
1 | (local icon-macros {})
2 |
3 | (fn icon-macros.deficon [name tbl-def]
4 | `(fn ,name [options#]
5 | (local xml# (require "utils.xml"))
6 | (let [width# (or (. options# :width) 24)
7 | height# (or (. options# :height) 24)
8 | viewBox# (or (. options# :viewBox) "0 0 24 24")
9 | fill# (or (. options# :fill) "none")
10 | shape-rendering# (or (. options# :shape-rendering) :auto)
11 | stroke-color# (or (. options# :stroke-color) "#fff")
12 | stroke-width# (or (. options# :stroke-width) 2)
13 | stroke-linecap# (or (. options# :stroke-linecap) :round)
14 | stroke-linejoin# (or (. options# :stroke-linejoin) :round)]
15 | ((. xml# :create-element) :svg
16 | {:xmlns "http://www.w3.org/2000/svg"
17 | :shape-rendering shape-rendering#
18 | :height height#
19 | :width width#
20 | :viewBox (or viewBox# (.. "0 0 " height# " " width#))
21 | :stroke-width stroke-width#
22 | :stroke stroke-color#
23 | :stroke-linecap stroke-linecap#
24 | :stroke-linejoin stroke-linejoin#
25 | :fill fill#}
26 | ,tbl-def))))
27 |
28 | icon-macros
29 |
--------------------------------------------------------------------------------
/icons/box.fnl:
--------------------------------------------------------------------------------
1 | (require-macros :icons._macros)
2 |
3 | (deficon box
4 | [[:path {:stroke :none :d "M0 0h24v24H0z"}]
5 | [:polyline {:points "12 3 20 7.5 20 16.5 12 21 4 16.5 4 7.5 12 3"}]
6 | [:line {:x1 12 :y1 12 :x2 20 :y2 7.5}]
7 | [:line {:x1 12 :y1 12 :x2 12 :y2 21}]
8 | [:line {:x1 12 :y1 12 :x2 4 :y2 7.5}]])
9 |
--------------------------------------------------------------------------------
/icons/layouts.fnl:
--------------------------------------------------------------------------------
1 | (require-macros :icons._macros)
2 |
3 | (local layouts {})
4 |
5 | (deficon layouts.tile
6 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
7 | [:rect {:x 4 :y 4 :width 4 :height 16 :stroke-width 1}]
8 | [:rect {:x 10 :y 4 :width 4 :height 4 :stroke-width 1}]
9 | [:rect {:x 10 :y 10 :width 4 :height 4 :stroke-width 1}]
10 | [:rect {:x 10 :y 16 :width 4 :height 4 :stroke-width 1}]
11 | [:rect {:x 16 :y 4 :width 4 :height 4 :stroke-width 1}]
12 | [:rect {:x 16 :y 10 :width 4 :height 4 :stroke-width 1}]
13 | [:rect {:x 16 :y 16 :width 4 :height 4 :stroke-width 1}]])
14 |
15 | (deficon layouts.tilebottom
16 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
17 | [:rect {:x 4 :y 4 :width 16 :height 4 :stroke-width 1}]
18 | [:rect {:x 4 :y 10 :width 4 :height 4 :stroke-width 1}]
19 | [:rect {:x 4 :y 16 :width 4 :height 4 :stroke-width 1}]
20 | [:rect {:x 10 :y 10 :width 4 :height 4 :stroke-width 1}]
21 | [:rect {:x 10 :y 16 :width 4 :height 4 :stroke-width 1}]
22 | [:rect {:x 16 :y 10 :width 4 :height 4 :stroke-width 1}]
23 | [:rect {:x 16 :y 16 :width 4 :height 4 :stroke-width 1}]])
24 |
25 | (deficon layouts.tiletop
26 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
27 | [:rect {:x 4 :y 16 :width 16 :height 4 :stroke-width 1}]
28 | [:rect {:x 4 :y 10 :width 4 :height 4 :stroke-width 1}]
29 | [:rect {:x 4 :y 4 :width 4 :height 4 :stroke-width 1}]
30 | [:rect {:x 10 :y 10 :width 4 :height 4 :stroke-width 1}]
31 | [:rect {:x 10 :y 4 :width 4 :height 4 :stroke-width 1}]
32 | [:rect {:x 16 :y 10 :width 4 :height 4 :stroke-width 1}]
33 | [:rect {:x 16 :y 4 :width 4 :height 4 :stroke-width 1}]])
34 |
35 | (deficon layouts.tileleft
36 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
37 | [:rect {:x 4 :y 4 :width 4 :height 4 :stroke-width 1}]
38 | [:rect {:x 4 :y 10 :width 4 :height 4 :stroke-width 1}]
39 | [:rect {:x 4 :y 16 :width 4 :height 4 :stroke-width 1}]
40 | [:rect {:x 16 :y 4 :width 4 :height 16 :stroke-width 1}]
41 | [:rect {:x 10 :y 4 :width 4 :height 4 :stroke-width 1}]
42 | [:rect {:x 10 :y 10 :width 4 :height 4 :stroke-width 1}]
43 | [:rect {:x 10 :y 16 :width 4 :height 4 :stroke-width 1}]])
44 |
45 | (deficon layouts.spiral
46 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
47 | [:rect {:x 4 :y 4 :width 8 :height 16 :stroke-width 1}]
48 | [:rect {:x 14 :y 4 :width 8 :height 8 :stroke-width 1}]
49 | [:rect {:x 18 :y 14 :width 4 :height 6 :stroke-width 1}]
50 | [:rect {:x 14 :y 18 :width 2 :height 2 :stroke-width 1}]
51 | [:rect {:x 14 :y 14 :width 2 :height 2 :stroke-width 1}]])
52 |
53 | (deficon layouts.dwindle
54 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
55 | [:rect {:x 4 :y 4 :width 8 :height 16 :stroke-width 1}]
56 | [:rect {:x 14 :y 4 :width 8 :height 8 :stroke-width 1}]
57 | [:rect {:x 14 :y 14 :width 4 :height 6 :stroke-width 1}]
58 | [:rect {:x 20 :y 18 :width 2 :height 2 :stroke-width 1}]
59 | [:rect {:x 20 :y 14 :width 2 :height 2 :stroke-width 1}]])
60 |
61 | (deficon layouts.max
62 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
63 | [:rect {:x 8 :y 8 :width 8 :height 8 :stroke-width 1}]
64 | [:polygon {:points "12,4 10,6 14,6"}]
65 | [:polygon {:points "12,20 10,18 14,18"}]
66 | [:polygon {:points "4,12 6,10 6,14"}]
67 | [:polygon {:points "20,12 18,10 18,14"}]])
68 |
69 | (deficon layouts.fullscreen
70 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
71 | [:polygon {:points "12,4 10,6 14,6"}]
72 | [:polygon {:points "12,20 10,18 14,18"}]
73 | [:polygon {:points "4,12 6,10 6,14"}]
74 | [:polygon {:points "20,12 18,10 18,14"}]])
75 |
76 | (deficon layouts.magnifier
77 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
78 | [:rect {:x 6 :y 6 :width 12 :height 12}]
79 | [:line {:x1 12 :y1 4 :x2 12 :y2 6}]
80 | [:line {:x1 12 :y1 18 :x2 12 :y2 20}]
81 | [:line {:x1 4 :y1 12 :x2 6 :y2 12}]
82 | [:line {:x1 18 :y1 12 :x2 20 :y2 12}]])
83 |
84 | (deficon layouts.fairv
85 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
86 | [:rect {:x 4 :y 4 :width 8 :height 4 :stroke-width 1}]
87 | [:rect {:x 4 :y 10 :width 8 :height 4 :stroke-width 1}]
88 | [:rect {:x 4 :y 16 :width 8 :height 4 :stroke-width 1}]
89 | [:rect {:x 14 :y 4 :width 8 :height 4 :stroke-width 1}]
90 | [:rect {:x 14 :y 10 :width 8 :height 4 :stroke-width 1}]
91 | [:rect {:x 14 :y 16 :width 8 :height 4 :stroke-width 1}]])
92 |
93 | (deficon layouts.fairh
94 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
95 | [:rect {:x 4 :y 4 :width 4 :height 8 :stroke-width 1}]
96 | [:rect {:x 10 :y 4 :width 4 :height 8 :stroke-width 1}]
97 | [:rect {:x 16 :y 4 :width 4 :height 8 :stroke-width 1}]
98 | [:rect {:x 4 :y 14 :width 4 :height 8 :stroke-width 1}]
99 | [:rect {:x 10 :y 14 :width 4 :height 8 :stroke-width 1}]
100 | [:rect {:x 16 :y 14 :width 4 :height 8 :stroke-width 1}]])
101 |
102 | (deficon layouts.floating
103 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
104 | [:rect {:x 2 :y 2 :width 16 :height 12 :stroke-width 1}]
105 | [:line {:x1 6 :y1 14 :x2 6 :y2 20 :stroke-width 1}]
106 | [:line {:x1 6 :y1 20 :x2 22 :y2 20 :stroke-width 1}]
107 | [:line {:x1 22 :y1 20 :x2 22 :y2 8 :stroke-width 1}]
108 | [:line {:x1 22 :y1 8 :x2 18 :y2 8 :stroke-width 1}]])
109 |
110 | (deficon layouts.cornernw
111 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
112 | [:rect {:x 2 :y 2 :width 14 :height 14 :stroke-width 1}]
113 | [:rect {:x 18 :y 2 :width 4 :height 5 :stroke-width 1}]
114 | [:rect {:x 18 :y 10 :width 4 :height 5 :stroke-width 1}]
115 | [:rect {:x 18 :y 18 :width 4 :height 4 :stroke-width 1}]
116 | [:rect {:x 10 :y 18 :width 6 :height 4 :stroke-width 1}]
117 | [:rect {:x 2 :y 18 :width 6 :height 4 :stroke-width 1}]])
118 |
119 | (deficon layouts.cornersw
120 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
121 | [:rect {:x 2 :y 8 :width 14 :height 14 :stroke-width 1}]
122 | [:rect {:x 18 :y 10 :width 4 :height 5 :stroke-width 1}]
123 | [:rect {:x 18 :y 17 :width 4 :height 5 :stroke-width 1}]
124 | [:rect {:x 18 :y 2 :width 4 :height 5 :stroke-width 1}]
125 | [:rect {:x 10 :y 2 :width 6 :height 4 :stroke-width 1}]
126 | [:rect {:x 2 :y 2 :width 6 :height 4 :stroke-width 1}]])
127 |
128 | (deficon layouts.cornerse
129 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
130 | [:rect {:x 8 :y 8 :width 14 :height 14 :stroke-width 1}]
131 | [:rect {:x 2 :y 10 :width 4 :height 5 :stroke-width 1}]
132 | [:rect {:x 2 :y 17 :width 4 :height 5 :stroke-width 1}]
133 | [:rect {:x 2 :y 2 :width 4 :height 5 :stroke-width 1}]
134 | [:rect {:x 8 :y 2 :width 6 :height 4 :stroke-width 1}]
135 | [:rect {:x 16 :y 2 :width 6 :height 4 :stroke-width 1}]])
136 |
137 | (deficon layouts.cornerne
138 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
139 | [:rect {:x 8 :y 2 :width 14 :height 14 :stroke-width 1}]
140 | [:rect {:x 2 :y 2 :width 4 :height 5 :stroke-width 1}]
141 | [:rect {:x 2 :y 10 :width 4 :height 5 :stroke-width 1}]
142 | [:rect {:x 18 :y 18 :width 4 :height 4 :stroke-width 1}]
143 | [:rect {:x 10 :y 18 :width 6 :height 4 :stroke-width 1}]
144 | [:rect {:x 2 :y 18 :width 6 :height 4 :stroke-width 1}]])
145 |
146 | layouts
147 |
--------------------------------------------------------------------------------
/icons/loader.fnl:
--------------------------------------------------------------------------------
1 | (local json (require "vendor.json"))
2 | (local oleander (require "utils.oleander"))
3 | (local {: notify : ext : fs } (require :api.lawful))
4 |
5 | (local icon-loader {})
6 |
7 | (set icon-loader.cachedir (fs.icon-dir))
8 | (tset icon-loader :initialized false)
9 |
10 | (lambda loader [module name ?options]
11 | (let [iconpkg (require (.. "icons." module))
12 | icon-fn (. iconpkg name)
13 | opts (or ?options {})
14 | opt-hash (oleander.bsd-checksum (json.encode opts))
15 | filename (.. module "-" name "-" opt-hash ".svg")
16 | filepath (.. icon-loader.cachedir filename)
17 | exists (io.open filepath :r)]
18 | (if (= exists nil)
19 | (with-open [outfile (io.open filepath :w)]
20 | (outfile:write (icon-fn opts)))
21 | (exists:close))
22 | filepath))
23 |
24 | (lambda icon-loader.load [module name ?options]
25 | (when (not icon-loader.initialized)
26 | (ext.shellout (.. "mkdir " icon-loader.cachedir " || true"))
27 | (tset icon-loader :initialized true))
28 | (loader module name ?options))
29 |
30 | (fn icon-loader.clear-cache []
31 | (ext.shellout (.. "rm " icon-loader.cachedir "*.svg"))
32 | (tset icon-loader :initialized false))
33 |
34 | icon-loader
35 |
--------------------------------------------------------------------------------
/icons/shtwzrd.fnl:
--------------------------------------------------------------------------------
1 | (require-macros :icons._macros)
2 |
3 | (local shtwzrd {})
4 |
5 | (deficon shtwzrd.avatar8bit
6 | [[:path {:stroke "#413331" :d "M1 0h5M0 1h3M6 1h1M0 2h1M0 3h2M0 4h1M6 4h1M0 5h1M3 5h2M6 5h1M0 6h1M2 6h5M0 7h2M5 7h2"}]
7 | [:path {:stroke "#e1c0ac" :d "M3 1h3M4 3h1M1 4h5M1 5h2M5 5h1M1 6h1M2 7h3"}]
8 | [:path {:stroke "#91bdcd" :d "M1 2h1M6 2h1M6 3h1"}]
9 | [:path {:stroke "#b4d1dc" :d "M2 2h4M2 3h2M5 3h1"}]])
10 |
11 | shtwzrd
12 |
--------------------------------------------------------------------------------
/icons/tabler.fnl:
--------------------------------------------------------------------------------
1 | (require-macros :icons._macros)
2 |
3 | (local tabler {})
4 |
5 | (deficon tabler.box
6 | [[:path {:stroke :none :d "M0 0h24v24H0z"}]
7 | [:polyline {:points "12 3 20 7.5 20 16.5 12 21 4 16.5 4 7.5 12 3"}]
8 | [:line {:x1 12 :y1 12 :x2 20 :y2 7.5}]
9 | [:line {:x1 12 :y1 12 :x2 12 :y2 21}]
10 | [:line {:x1 12 :y1 12 :x2 4 :y2 7.5}]])
11 |
12 | (deficon tabler.grid
13 | [[:path {:stroke :none :d "M0 0h24v24H0z"}]
14 | [:circle {:cx 5 :cy 5 :r 1}]
15 | [:circle {:cx 12 :cy 5 :r 1}]
16 | [:circle {:cx 19 :cy 5 :r 1}]
17 | [:circle {:cx 5 :cy 12 :r 1}]
18 | [:circle {:cx 12 :cy 12 :r 1}]
19 | [:circle {:cx 19 :cy 12 :r 1}]
20 | [:circle {:cx 5 :cy 19 :r 1}]
21 | [:circle {:cx 12 :cy 19 :r 1}]
22 | [:circle {:cx 19 :cy 19 :r 1}]])
23 |
24 | (deficon tabler.terminal
25 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
26 | [:path {:d "M8 9l3 3l-3 3"}]
27 | [:line {:x1 13 :y1 15 :x2 16 :y2 15}]
28 | [:rect {:x 3 :y 4 :width 18 :height 16 :rx 2}]])
29 |
30 | (local battery-path
31 | (.. "M6 7h11a2 2 0 0 1 2 2v.5a0.5 .5 0 0 0 .5 .5a0.5 .5 0 0 1 .5"
32 | " .5v3a0.5 .5 0 0 1 -.5 .5a0.5 .5 0 0 0 -.5 .5v.5a2 2 0 0 1 -2"
33 | " 2h-11a2 2 0 0 1 -2 -2v-6a2 2 0 0 1 2 -2"))
34 |
35 | (deficon tabler.battery-empty
36 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
37 | [:path {:d battery-path}]])
38 |
39 | (deficon tabler.battery-low
40 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
41 | [:path {:d battery-path}]
42 | [:line {:x1 7 :y1 10 :x2 7 :y2 14}]])
43 |
44 | (deficon tabler.battery-mid
45 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
46 | [:path {:d battery-path}]
47 | [:line {:x1 7 :y1 10 :x2 7 :y2 14}]
48 | [:line {:x1 10 :y1 10 :x2 10 :y2 14}]])
49 |
50 | (deficon tabler.battery-high
51 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
52 | [:path {:d battery-path}]
53 | [:line {:x1 7 :y1 10 :x2 7 :y2 14}]
54 | [:line {:x1 10 :y1 10 :x2 10 :y2 14}]
55 | [:line {:x1 13 :y1 10 :x2 13 :y2 14}]])
56 |
57 | (deficon tabler.battery-full
58 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
59 | [:path {:d battery-path}]
60 | [:line {:x1 7 :y1 10 :x2 7 :y2 14}]
61 | [:line {:x1 10 :y1 10 :x2 10 :y2 14}]
62 | [:line {:x1 13 :y1 10 :x2 13 :y2 14}]
63 | [:line {:x1 16 :y1 10 :x2 16 :y2 14}]])
64 |
65 | (deficon tabler.battery-charging
66 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
67 | [:path {:d (..
68 | "M16 7h1a2 2 0 0 1 2 2v.5a0.5 .5 0 0 0 .5 .5a0.5 .5 0 0"
69 | " 1 .5 .5v3a0.5 .5 0 0 1 -.5 .5a0.5 .5 0 0 0 -.5 .5v.5a2"
70 | " 2 0 0 1 -2 2h-2")}]
71 | [:path {:d "M8 7h-2a2 2 0 0 0 -2 2v6a2 2 0 0 0 2 2h1"}]
72 | [:path {:d "M12 8l-2 4h3l-2 4"}]])
73 |
74 | (deficon tabler.battery-disabled
75 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
76 | [:path {:d (..
77 | "M11 7h6a2 2 0 0 1 2 2v.5a0.5 .5 0 0 0 .5 .5a0.5 .5"
78 | " 0 0 1 .5 .5v3a0.5 .5 0 0 1 -.5 .5a0.5 .5 0 0 0"
79 | " -.5 .5v.5m-2 2h-11a2 2 0 0 1 -2 -2v-6a2 2 0 0 1 2 -2h1"
80 | )}]
81 | [:line {:x1 3 :y1 3 :x2 21 :y2 21}]])
82 |
83 | (local speaker-path
84 | (.. "M6 15h-2a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h2l3.5 -4.5a0.8"
85 | " .8 0 0 1 1.5 .5v14a0.8 .8 0 0 1 -1.5 .5l-3.5 -4.5"))
86 |
87 | (deficon tabler.volume-low
88 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
89 | [:path {:d speaker-path }]
90 | [:path {:d "M15 8a5 5 0 0 1 0 8"}]])
91 |
92 | (deficon tabler.volume-high
93 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
94 | [:path {:d speaker-path }]
95 | [:path {:d "M15 8a5 5 0 0 1 0 8"}]
96 | [:path {:d "M17.7 5a9 9 0 0 1 0 14"}]])
97 |
98 | (deficon tabler.volume-muted
99 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
100 | [:path {:d speaker-path }]
101 | [:path {:d "M16 10l4 4m0 -4l-4 4"}]])
102 |
103 | (deficon tabler.wifi-low
104 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
105 | [:path {:d "M9.172 15.172a4 4 0 0 1 5.656 0" }]
106 | [:line {:x1 12 :y1 18 :x2 12.01 :y2 18}]])
107 |
108 | (deficon tabler.wifi-mid
109 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
110 | [:path {:d "M9.172 15.172a4 4 0 0 1 5.656 0" }]
111 | [:path {:d "M6.343 12.343a8 8 0 0 1 11.314 0" }]
112 | [:line {:x1 12 :y1 18 :x2 12.01 :y2 18}]])
113 |
114 | (deficon tabler.wifi-high
115 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
116 | [:path {:d "M9.172 15.172a4 4 0 0 1 5.656 0" }]
117 | [:path {:d "M6.343 12.343a8 8 0 0 1 11.314 0" }]
118 | [:path {:d "M3.515 9.515c4.686 -4.687 12.284 -4.687 17 0" }]
119 | [:line {:x1 12 :y1 18 :x2 12.01 :y2 18}]])
120 |
121 | (deficon tabler.wifi-disabled
122 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
123 | [:path {:d "M9.172 15.172a4 4 0 0 1 5.656 0" }]
124 | [:path {:d "M6.343 12.343a7.963 7.963 0 0 1 3.864 -2.14m4.163 .155a7.965 7.965 0 0 1 3.287 2" }]
125 | [:path {:d "M3.515 9.515a12 12 0 0 1 3.544 -2.455m3.101 -.92a12 12 0 0 1 10.325 3.374" }]
126 | [:line {:x1 12 :y1 18 :x2 12.01 :y2 18}]
127 | [:line {:x1 3 :y1 3 :x2 21 :y2 21}]])
128 |
129 | (deficon tabler.desktop
130 | [[:path {:stroke :none :fill :none :d "M0 0h24v24H0z"}]
131 | [:rect {:x 3 :y 4 :width 18 :height 12 :rx 1}]
132 | [:line {:x1 7 :y1 20 :x2 17 :y2 20}]
133 | [:line {:x1 9 :y1 16 :x2 9 :y2 20}]
134 | [:line {:x1 15 :y1 16 :x2 15 :y2 20}]])
135 |
136 | tabler
137 |
--------------------------------------------------------------------------------
/keybindings.fnl:
--------------------------------------------------------------------------------
1 | ;; Keyboard & Mouse bindings declarations -- all in one place
2 | (local awful (require "awful"))
3 | (local gears (require "gears"))
4 | (local hotkeys_popup (require "awful.hotkeys_popup"))
5 | (local lume (require "vendor.lume"))
6 | (local input (require "utils.input"))
7 | (local pa (require "utils.pulseaudio"))
8 | (local wincmd (require "commands.window"))
9 | (local tagcmd (require "commands.tag"))
10 | (local defhydra (require "features.hydra"))
11 | (local {: spawn-nrepl : kill-nrepl } (require :features.nrepl))
12 | (local {: notify} (require :api.lawful))
13 | (local titlebar (require :features.titlebar))
14 |
15 | (local layouts awful.layout.suit)
16 |
17 | (local window-hydra
18 | (defhydra {:name "Windows 🐲" :take-counts true :key [[:mod] :w] }
19 | ["Movement"
20 | [:h wincmd.move-left "move left"]
21 | [:j wincmd.move-down "move down"]
22 | [:k wincmd.move-up "move up"]
23 | [:l wincmd.move-right "move right"]
24 | [:c wincmd.cycle-clockwise "cycle clockwise"]
25 | [:C wincmd.cycle-counter-clockwise "cycle counter-clockwise"]
26 | [:Left wincmd.transfer-to-prev-tag "to previous tag" {:exit true}]
27 | [:Right wincmd.transfer-to-next-tag "to next tag" {:exit true}]]
28 | ["Snap"
29 | [:H wincmd.snap-left "snap to left edge"]
30 | [:J wincmd.snap-bottom "snap to bottom edge"]
31 | [:K wincmd.snap-top "snap to top edge"]
32 | [:L wincmd.snap-right "snap to right edge"]
33 | [:Y wincmd.snap-top-left-corner "snap to top left"]
34 | [:U wincmd.snap-top-right-corner "snap to top right"]
35 | [:N wincmd.snap-bottom-left-corner "snap to bottom left"]
36 | [:B wincmd.snap-bottom-right-corner "snap to bottom right"]]
37 | ["State"
38 | [:m wincmd.minimize "minimize" {:exit true}]
39 | [:q wincmd.close "close" {:exit true}]
40 | [:space wincmd.toggle-floating "⏼ floating"]
41 | [:s wincmd.toggle-sticky "⏼ sticky"]
42 | [:f wincmd.toggle-fullscreen "⏼ fullscreen" {:exit true}]]))
43 |
44 | (local tag-hydra
45 | (defhydra {:name "Tags 🐲" :take-counts true :key [[:mod] :t] }
46 | ["Layout"
47 | [:space (tagcmd.layout-fn layouts.floating) "floating"]
48 | [:= (tagcmd.layout-fn layouts.fair) "fair"]
49 | [:/ (tagcmd.layout-fn layouts.fair.horizontal) "fairv"]
50 | [:t (tagcmd.layout-fn layouts.tile) "tile"]
51 | [:s (tagcmd.layout-fn layouts.spiral) "spiral"]
52 | [:S (tagcmd.layout-fn layouts.spiral.dwindle) "dwindle"]
53 | [:f (tagcmd.layout-fn layouts.max) "max"]
54 | [:F (tagcmd.layout-fn layouts.max.fullscreen) "fullscreen"]
55 | [:+ (tagcmd.layout-fn layouts.magnifier) "magnifier"]]
56 | ["Spacing"
57 | [:c tagcmd.inc-cols "add column"]
58 | [:C tagcmd.dec-cols "remove column"]
59 | [:m tagcmd.inc-masters "increase masters"]
60 | [:M tagcmd.dec-masters "decrease masters"]
61 | [:j tagcmd.dec-master-width "shrink master area"]
62 | [:k tagcmd.inc-master-width "grow master area"]
63 | [:p tagcmd.toggle-fill-policy "⏼ fill policy"]
64 | [:g tagcmd.inc-gap "increase gaps"]
65 | [:G tagcmd.dec-gap "decrease gaps"]]
66 | ["Transfer"
67 | [:q tagcmd.destroy-current "destroy" {:exit true}]
68 | [:h (fn [] (notify.msg "TODO")) "move to screen left"]
69 | [:l (fn [] (notify.msg "TODO")) "move to screen right"]]))
70 |
71 | (local
72 | bindings
73 | {
74 | :global-keys
75 | (gears.table.join
76 |
77 | (input.key-group
78 | "awesome"
79 | [[:mod] :s hotkeys_popup.show_help "show help"]
80 | [[:mod :shift] :r awesome.restart "reload config"]
81 | [[:mod :shift] :t titlebar.toggle "⏼ titlebars"]
82 | )
83 |
84 | (input.key-group
85 | "exec"
86 | [[:mod] :space (fn [] (awful.util.spawn "rofi -show run")) "app launcher"]
87 | [[:mod :shift] :n (fn [] (spawn-nrepl)) "spawn nrepl"]
88 | [[:mod :shift] :z (fn [] (kill-nrepl)) "kill nrepl"]
89 | [[:mod] :e (fn [] (awful.util.spawn "emacsclient -c -n -e '(switch-to-buffer nil)'")) "emacs"]
90 | [[:mod] :v (fn []
91 | (let [option "gopass ls --flat"
92 | prompt "rofi -dmenu -p pass"
93 | action "xargs --no-run-if-empty gopass show -c"
94 | cmd (.. option " | " prompt " | " action)]
95 | (awful.spawn.easy_async_with_shell cmd (fn []))))
96 | "secret vault"]
97 | [[:mod] :Return (fn [] (awful.spawn "alacritty")) "open terminal"]
98 | [[] :XF86AudioMute pa.toggle-muted "⏼ audio muted"]
99 | [[] :XF86AudioMicMute pa.toggle-mic-mute "⏼ mic muted"]
100 | [[] :XF86AudioRaiseVolume (fn [] (pa.adjust-volume 5)) "raise volume"]
101 | [[] :XF86AudioLowerVolume (fn [] (pa.adjust-volume -5)) "lower volume"]
102 | )
103 |
104 | (input.key-group
105 | "tags"
106 | [[:mod :shift] :l tagcmd.go-right "switch to next tag"]
107 | [[:mod :shift] :h tagcmd.go-left "switch to previous tag"]
108 | )
109 | tag-hydra
110 |
111 | (input.key-group
112 | "client"
113 | [[:mod] :h (fn [] (awful.client.focus.bydirection :left)) "focus left"]
114 | [[:mod] :j (fn [] (awful.client.focus.bydirection :down)) "focus down"]
115 | [[:mod] :k (fn [] (awful.client.focus.bydirection :up)) "focus up"]
116 | [[:mod] :l (fn [] (awful.client.focus.bydirection :right)) "focus right"]
117 | [[:mod] :n (fn [] (awful.client.focus.byidx 1)) "focus next"]
118 | [[:mod] :p (fn [] (awful.client.focus.byidx -1)) "focus previous"]
119 | [[:mod :shift] :j (fn [] (awful.client.swap.byidx 1)) "swap next with previous"]
120 | [[:mod :shift] :k (fn [] (awful.client.swap.byidx -1)) "swap next with previous"]
121 | )
122 | window-hydra
123 | )
124 |
125 |
126 | :client-keys
127 | (gears.table.join
128 | (input.key-group
129 | "client"
130 | [[:mod] :m wincmd.minimize "minimize"]
131 | [[:mod] :t wincmd.toggle-ontop "⏼ keep on top"]
132 | [[:mod :shift] :M wincmd.toggle-maximized "⏼ maximize"]
133 | [[:mod] :f wincmd.toggle-fullscreen "⏼ fullscreen"]
134 | [[:mod :ctrl] :space awful.client.floating.toggle "⏼ floating"]
135 | [[:mod :ctrl] :Return wincmd.move-to-master "move to master"]
136 | [[:mod :ctrl] :m wincmd.toggle-maximize-vertical "⏼ max vertically"]
137 | [[:mod :shift] :m wincmd.toggle-maximize-horizontal "⏼ max horizontally"]
138 | [[:mod :shift] :c wincmd.close "close"]
139 | ))
140 |
141 | :client-buttons
142 | (gears.table.join
143 | (input.mousebind [] input.left-click client.mouse-raise)
144 | (input.mousebind [:mod] input.left-click client.mouse-drag-move)
145 | (input.mousebind [:mod] input.right-click client.mouse-drag-resize)
146 | )
147 | })
148 |
149 | bindings
150 |
--------------------------------------------------------------------------------
/manifest.scm:
--------------------------------------------------------------------------------
1 | (use-modules (guix transformations))
2 |
3 | (packages->manifest
4 | (list
5 | (specification->package "coreutils")
6 | (specification->package "make")
7 | (specification->package "lua@5.1")
8 | (specification->package "lua5.1-socket")
9 | (specification->package "lua5.1-lgi")
10 |
11 | ((options->transformation
12 | '((with-input . "lua=lua@5.1")
13 | (with-commit . "fennel=03c1c95f")))
14 | (specification->package "fennel"))
15 |
16 | (specification->package "luajit")
17 | (specification->package "awesome-next-luajit")
18 | (specification->package "gobject-introspection")
19 | (specification->package "xorg-server")
20 | (specification->package "xterm")))
21 |
22 |
--------------------------------------------------------------------------------
/patches/fix-snid.lua:
--------------------------------------------------------------------------------
1 | local awful = require("awful")
2 | local blacklisted_snid = setmetatable({}, {__mode = "v" })
3 |
4 | --- Make startup notification work for some clients like XTerm. This is ugly
5 | -- but works often enough to be useful.
6 | local function fix_startup_id(c)
7 | -- Prevent "broken" sub processes created by c
to inherit its SNID
8 | if c.startup_id then
9 | blacklisted_snid[c.startup_id] = blacklisted_snid[c.startup_id] or c
10 | return
11 | end
12 |
13 | if not c.pid then return end
14 |
15 | -- Read the process environment variables
16 | local f = io.open("/proc/"..c.pid.."/environ", "rb")
17 |
18 | -- It will only work on Linux, that's already 99% of the userbase.
19 | if not f then return end
20 |
21 | local value = "%d+\0"
22 |
23 | local snid = f:read("*all"):match("STARTUP_ID=" .. value)
24 | f:close()
25 |
26 | -- If there is already a client using this SNID, it means it's either a
27 | -- subprocess or another window for the same process. While it makes sense
28 | -- in some case to apply the same rules, it is not always the case, so
29 | -- better doing nothing rather than something stupid.
30 | if blacklisted_snid[snid] then return end
31 |
32 | if snid then
33 | c.startup_id = snid
34 | blacklisted_snid[snid] = c
35 | end
36 | end
37 |
38 | awful.rules.add_rule_source(
39 | "snid", fix_startup_id, {}, {"awful.spawn", "awful.rules"}
40 | )
41 |
--------------------------------------------------------------------------------
/rc.lua:
--------------------------------------------------------------------------------
1 | -- add guix lua packages to package.path
2 | guixEnv = os.getenv("GUIX_ENVIRONMENT")
3 | if guixEnv then
4 | package.path = package.path .. ";" .. guixEnv .. "/share/lua/5.1/?.lua" ..
5 | ";" .. guixEnv .. "/share/lua/5.1/?/init.lua" ..
6 | ";" .. guixEnv .. "/lib/lua/5.1/?.lua" ..
7 | ";" .. guixEnv .. "/lib/lua/5.1/?/init.lua"
8 |
9 | package.cpath = package.cpath .. ";" .. guixEnv .. "/lib/lua/5.1/?.so"
10 | end
11 |
12 | cfgDir = os.getenv("HOME") .. "/.config/awesome/"
13 |
14 | -- add the vendor dir
15 | package.path = package.path .. ";" .. cfgDir .. "/vendor/?.lua"
16 | package.path = package.path .. ";" .. cfgDir .. "/vendor/?/?.lua"
17 |
18 | fennel = require("fennel")
19 |
20 | fennel.path = fennel.path .. ";" .. cfgDir .. "?.fnl;" .. cfgDir .. "?/init.fnl"
21 |
22 | searcher = fennel.makeSearcher({
23 | correlate = true,
24 | useMetadata = true,
25 | -- disable strict checking.
26 | -- TODO: assemble a full list of globals so we can enable this
27 | allowedGlobals = false
28 | })
29 |
30 | table.insert(package.loaders or package.searchers, searcher)
31 | debug.traceback = fennel.traceback
32 | require("init") -- load ~/.config/awesome/init.fnl
33 |
--------------------------------------------------------------------------------
/rules.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local beautiful (require "beautiful"))
3 | (local keybindings (require "keybindings"))
4 |
5 | (local
6 | rules
7 | [
8 | ;; All clients will match this rule.
9 | {
10 | :rule { }
11 | :properties {
12 | :border_width beautiful.border_width
13 | :border_color beautiful.border_normal
14 | :focus awful.client.focus.filter
15 | :raise true
16 | :size_hints_honor false
17 | :keys keybindings.client-keys
18 | :buttons keybindings.client-buttons
19 | :screen awful.screen.preferred
20 | }
21 | }
22 |
23 | ;; Floating clients.
24 | {
25 | :rule_any {
26 | :instance [
27 | "DTA" ;; Firefox addon DownThemAll.
28 | "copyq" ;; Includes session name in class.
29 | "pinentry"
30 | ]
31 | :class [
32 | "Arandr"
33 | "Blueman-manager"
34 | "Gpick"
35 | "Kruler"
36 | "MessageWin" ;; kalarm.
37 | "Sxiv"
38 | "Tor Browser" ;; Needs a fixed window size to avoid fingerprinting by screen size.
39 | "Wpa_gui"
40 | "veromix"
41 | "xtightvncviewer"
42 | ]
43 | ;; Note that the name property shown in xprop might be set slightly after creation of the client
44 | ;; and the name shown there might not match defined rules here.
45 | :name [
46 | "Event Tester" ;; xev
47 | ]
48 | :role [
49 | "AlarmWindow" ;; Thunderbird's calendar.
50 | "ConfigManager" ;; Thunderbird's about:config.
51 | "pop-up" ;; e.g. Google Chrome's (detached) Developer Tools.
52 | ]
53 | }
54 | :properties {:floating true }}
55 |
56 | ;; Add titlebars to normal clients and dialogs
57 | {
58 | :rule_any {:type [ "normal" "dialog" ] }
59 | :properties {:titlebars_enabled true }
60 | }
61 |
62 | {
63 | :rule_any {:role [ "PictureInPicture" ] }
64 | :properties {
65 | :placement awful.placement.bottom_right
66 | }
67 | }
68 | {
69 | :rule_any {:name ["*Minibuf-0*"]}
70 | :properties {
71 | :placement awful.placement.bottom_left
72 | :height 20
73 | :width 1920
74 | :floating true
75 | :honor_padding true
76 | :honor_workarea true
77 | :requests_no_titlebar true
78 | :titlebars_enabled false
79 | }
80 | }
81 |
82 | ;; Set Firefox to always map on the tag named "2" on screen 1.
83 | ;; { :rule { :class "Firefox" }
84 | ;; :properties { :screen 1 :tag "2" } }
85 | ])
86 |
87 | rules
88 |
--------------------------------------------------------------------------------
/test/hotfuzz.fnl:
--------------------------------------------------------------------------------
1 | (local t (require "vendor.lunatest"))
2 | (local pp (require "fennel.view"))
3 | (local hotfuzz (require "utils.hotfuzz"))
4 | (local lume (require "vendor.lume"))
5 |
6 | (local m {})
7 |
8 | (fn m.test-seller-matrix []
9 | (let [result-matrix (hotfuzz.init-seller-matrix 3 3)
10 | expected-matrix [[0 0 0] [1 0 0] [2 0 0]]]
11 | (t.assert_equal (pp expected-matrix) (pp result-matrix))))
12 |
13 | (fn m.test-core-levenshtein []
14 | (let [expect [[[0 0 0 0 0 0 0 0] [1 1 1 1 1 1 1 1] [2 2 1 2 2 1 2 2] [3 3 2 1 2 2 2 3] [4 4 3 2 1 2 3 3] [5 5 4 3 2 2 3 4] [6 6 5 4 3 3 2 3]]]
15 | inputs [["kitten" "sitting"]]]
16 | (each [i p (ipairs inputs)]
17 | (let [[term cand] p]
18 | (var mat (hotfuzz.init-seller-matrix (+ 1 (length term)) (+ 1 (length cand))))
19 | (for [n 2 (+ 1 (length term))]
20 | (for [m 2 (+ 1 (length cand))]
21 | (hotfuzz.levenshtein-dist term cand mat n m)))
22 | (t.assert_equal (pp (. expect i)) (pp mat))))))
23 |
24 | (fn m.test-init-search-trie []
25 | (let [trie (hotfuzz.init-search-trie ["ha" "hat" "cat"])
26 | expected {:candidates {} :children {:c {:candidates {} :children {:a {:candidates {} :children {:t {:candidates [{:index 3 :value "cat"}] :children {} :depth 0}} :depth 1}} :depth 2} :h {:candidates {} :children {:a {:candidates [{:index 1 :value "ha"}] :children {:t {:candidates [{:index 2 :value "hat"}] :children {} :depth 0}} :depth 1}} :depth 2}} :depth 3}]
27 | (t.assert_equal (pp expected) (pp trie))))
28 |
29 | (fn m.test-compare-results []
30 | (let [perfect {:score 1 :hit {:start 0} :length-diff 0 :index 0}
31 | good-score {:score .9 :hit {:start 3} :length-diff 3 :index 1}
32 | good-hit {:score .6 :hit {:start 0} :length-diff 3 :index 2}
33 | good-len {:score .6 :hit {:start 3} :length-diff 0 :index 3}
34 | awful {:score .6 :hit {:start 3} :length-diff 3 :index 4}
35 | even-worse {:score .6 :hit {:start 3} :length-diff 3 :index 5}]
36 | (t.assert_lt 0 (hotfuzz.compare-results perfect good-score) "perfect match")
37 | (t.assert_gt 0 (hotfuzz.compare-results good-score perfect) "can't beat perfect")
38 | (t.assert_lt 0 (hotfuzz.compare-results good-score good-hit) "score > hit")
39 | (t.assert_lt 0 (hotfuzz.compare-results good-hit good-len) "hit > length")
40 | (t.assert_lt 0 (hotfuzz.compare-results good-len awful) "length > nothing")
41 | (t.assert_lt 0 (hotfuzz.compare-results awful even-worse) "index final deciding factor")))
42 |
43 | (fn m.test-add-result []
44 | (let [candidate {:value "cat" :index 1}
45 | hit {:start 0}
46 | score 1
47 | len 2
48 | expected {:hit hit :length-diff len :score score :value candidate.value :index candidate.index}]
49 | (var results [])
50 | (var result-map {})
51 | (hotfuzz.add-result results result-map candidate score hit len)
52 | (t.assert_equal 1 (length results))
53 | (t.assert_equal 1 (length result-map))
54 | (t.assert_equal (pp [expected]) (pp results))))
55 |
56 | (fn m.test-search-order []
57 | "search should return values in expected order (perfect match first)"
58 | (let [search-candidates ["rat" "matt" "cat" "catepillar" "bobcat"]
59 | search-results (. (hotfuzz.search search-candidates "cat") :results)
60 | expected-order ["cat" "catepillar" "bobcat" "rat" "matt"]
61 | resulting-order (lume.map search-results (fn [x] x.value))]
62 | (t.assert_equal (pp expected-order) (pp resulting-order))))
63 |
64 | (fn m.test-search-threshold []
65 | "candidate `space` should be excluded from results for scoring too low"
66 | (let [search-candidates ["moon" "moonman" "moonpie" "space"]
67 | search-results (hotfuzz.search search-candidates "moo")]
68 | (t.assert_equal 3 (length search-results.results))))
69 |
70 | (fn m.test-search-hit []
71 | "result should return a hit that gives offset and length of the match"
72 | (let [search-candidates ["witch's broom" "moon-witch"]
73 | results (. (hotfuzz.search search-candidates "witch") :results)
74 | broom-hit (-> results (lume.filter (fn [x] (= x.value "witch's broom"))) (lume.first))
75 | moon-hit (-> results (lume.filter (fn [x] (= x.value "moon-witch"))) (lume.first))]
76 | (t.assert_equal "witch" (string.sub "moon-witch" moon-hit.hit.start moon-hit.hit.end))
77 | (t.assert_equal "witch" (string.sub "witch's broom" broom-hit.hit.start broom-hit.hit.end))))
78 |
79 | (fn m.test-search-hit-single-char []
80 | "result should return a hit that gives offset and length of the match, no off-by-one errors"
81 | (let [search-candidates ["awesome" "something" "default" "set" "ime" "ton"]
82 | results (. (hotfuzz.search search-candidates "some" {:threshold 0.0}) :results)
83 | awesome-hit (-> results (lume.filter (fn [x] (= x.value "awesome"))) (lume.first))
84 | set-hit (-> results (lume.filter (fn [x] (= x.value "set"))) (lume.first))
85 | ime-hit (-> results (lume.filter (fn [x] (= x.value "ime"))) (lume.first))
86 | ton-hit (-> results (lume.filter (fn [x] (= x.value "ton"))) (lume.first))
87 | something-hit (-> results (lume.filter (fn [x] (= x.value "something"))) (lume.first))
88 | default-hit (-> results (lume.filter (fn [x] (= x.value "default"))) (lume.first))]
89 | (t.assert_equal "some" (string.sub "something" something-hit.hit.start something-hit.hit.end))
90 | (t.assert_equal "some" (string.sub "awesome" awesome-hit.hit.start awesome-hit.hit.end))
91 | (t.assert_equal "me" (string.sub "ime" ime-hit.hit.start ime-hit.hit.end))
92 | (t.assert_equal "e" (string.sub "set" set-hit.hit.start set-hit.hit.end))
93 | (t.assert_equal "o" (string.sub "ton" ton-hit.hit.start ton-hit.hit.end))
94 | (t.assert_equal "e" (string.sub "default" default-hit.hit.start default-hit.hit.end))))
95 |
96 | m
97 |
--------------------------------------------------------------------------------
/test/icons.fnl:
--------------------------------------------------------------------------------
1 | (local t (require "vendor.lunatest"))
2 | (local icon-loader (require "icons.loader"))
3 | (local tabler-icons (require "icons.tabler"))
4 |
5 | (local m {})
6 |
7 | (fn m.test-can-load-icon []
8 | (let [tabler-grid (icon-loader.load :tabler :grid {:viewBox "0 0 24 24"})]
9 | ; can we load an icon without getting exceptions?
10 | (t.assert_equal 1 1)))
11 |
12 | m
13 |
--------------------------------------------------------------------------------
/test/init.fnl:
--------------------------------------------------------------------------------
1 | (local t (require "vendor.lunatest"))
2 |
3 | ;(t.suite :test.icons)
4 | (t.suite :test.hotfuzz)
5 | (t.suite :test.xml)
6 |
7 | (t.run)
8 |
--------------------------------------------------------------------------------
/test/xml.fnl:
--------------------------------------------------------------------------------
1 | (local t (require "vendor.lunatest"))
2 | (local pp (require "fennel.view"))
3 | (local xml (require "utils.xml"))
4 | (local lume (require "vendor.lume"))
5 |
6 | (local m {})
7 |
8 | (fn m.test-create-empty-element []
9 | (let [result (xml.create-element :span)]
10 | (t.assert_equal "" result)))
11 |
12 | (fn m.test-create-simple-element []
13 | (let [result (xml.create-element :span {} "hello world")]
14 | (t.assert_equal "hello world" result)))
15 |
16 | (fn m.test-create-multiple-child-element []
17 | (let [result (xml.create-element :span {} "rain" "bow")]
18 | (t.assert_equal "rainbow" result)))
19 |
20 | (fn m.test-create-element-with-attributes []
21 | (let [result (xml.create-element :span {:foreground :red} "hello world")]
22 | (t.assert_equal "hello world" result)))
23 |
24 | (fn m.test-create-nested-element []
25 | (let [result (xml.create-element
26 | :span {:text :big}
27 | [[:div {} "l. 1"]
28 | [:span {} [:div {} "l. 2"]]
29 | [:span {} [[:div {} "l. 3a"]
30 | [:span {} "l. 3b"]]]])]
31 | (t.assert_equal
32 | "l. 1
l. 2
l. 3a
l. 3b"
33 | result)))
34 |
35 | m
36 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/close_focus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/close_focus_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/close_normal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/close_normal_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/floating_focus_active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/floating_focus_active_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/floating_focus_inactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/floating_focus_inactive_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/floating_normal_active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/floating_normal_active_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/floating_normal_inactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/floating_normal_inactive_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/maximized_focus_active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/maximized_focus_active_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/maximized_focus_inactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/maximized_focus_inactive_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/maximized_normal_active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/maximized_normal_active_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/maximized_normal_inactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/maximized_normal_inactive_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/minimize_focus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/minimize_focus_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/minimize_normal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/minimize_normal_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
64 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/ontop_focus_active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/ontop_focus_active_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/ontop_focus_inactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/ontop_focus_inactive_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/ontop_normal_active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/ontop_normal_active_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/ontop_normal_inactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/ontop_normal_inactive_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/sticky_focus_active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/sticky_focus_active_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/sticky_focus_inactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/sticky_focus_inactive_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/sticky_normal_active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/sticky_normal_active_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/sticky_normal_inactive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/themes/dracula/titlebar/sticky_normal_inactive_hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | * ~~wmii style tag management~~
2 | - ~~dynamic~~
3 | - ~~volatile~~
4 | - ~~automatically create new tag when going -> past existing tags~~
5 | * ~~tag hydra~~
6 | - ~~manage properties like layout, gaps, columns, master width factor~~
7 | * workspaces
8 | - ~~hide tags not part of current workspace~~
9 | - spawn programs like Firefox and chrome differently when in a workspace (profiles)
10 | - improve workspace switcher
11 | - ~~persistence~~
12 | - ~~identicons for each workspace~~
13 | - ~~persist previously used workspace names in the perma.json file~~
14 | * ~~sticky clients/familiars~~
15 | - ~~fix layout issues (want to dictate via :direction and :fill-percentage)~~
16 | - ~~prevent titlebars~~
17 | - ~~persist info about familiars in state.json~~
18 | - ~~slide-in terminal for one-off commands~~
19 | - ~~apps like spotify, teams, a browser~~
20 | - ~~persistent, workspace-agnostic tag for these?~~ sticky works across workspaces
21 | - ~~autofocus on open~~
22 | - ~~always on top~~
23 | * notification center
24 | - inspiration
25 | - https://www.reddit.com/r/unixporn/comments/dwiyca/awesome_notification_center/
26 | * daemons
27 | - ~~battery~~
28 | - ~~cpu~~
29 | - ~~memory~~
30 | - network status
31 | - ~~pulseaudio status (vol, sinks?)~~
32 | * a e s t h e t i c s (especially the bar omg ew)
33 | - ~~svg handling~~
34 | - ~~loader~~
35 | - ~~identicons~~
36 | - ~~deficon macro~~
37 | - ~~caching ?~~
38 | - more svgs
39 | - ~~popups need to constrain to screen edges~~
40 | - ~~maybe use the default placement functions or steal their logic?~~
41 | - inspiration
42 | - https://www.reddit.com/r/unixporn/comments/atkn5b/awesome_skyfall/
43 | - https://www.reddit.com/r/unixporn/comments/caiad9/awesomewm_nighty_nordic/
44 | - https://www.youtube.com/watch?v=_1M1Wv64JGA
45 | - https://www.reddit.com/r/unixporn/comments/bgfeoh/bspwm_dracula_all_the_things/
46 | - https://www.reddit.com/r/unixporn/comments/dnsiiq/bspwm_dracula_revamped_for_a_newer_laptop/
47 | * ~~password integration (gopass + rofi, need some keyring integration i guess?)~~
48 | * screenshot shortcut
49 | - just select an area and copy to clipboard
50 | - optionally save to a dir
51 | * ssh bastion-style prompt (via rofi ?)
52 | * ~~rofi emoji picker 🍆 (used rofimoji)~~
53 | * screen setup scripts/ui ?
54 | - move focus between screens implicitly (requires getting physical scr order)
55 | - allow moving clients between tags in a similar way
56 | * script for bringing a random window into current tag via rofi
57 | - see https://github.com/elenapan/dotfiles/blob/997650801a7f6a60b1c1753f40e5f610669a1b2c/config/awesome/helpers.lua#L104
58 | - yoink and yeet
59 | * pulseaudio ui/config ?
60 | - single output device across all streams pls
61 | - make WF1000MX3 stop connecting as a damm headset
62 | - priority setting? Bluetooth > HDMI > Analog > everything else ?
63 | * lockscreen (xsecurelock?)
64 | * emacs org and calendar integration?
65 | - emacs org capture keybind?
66 | * toggle-able - keypress echo widget
67 | * more tag movement commands (between screens, merge tags, swap tags) ?
68 | * figure out how I want floating/tiling clients inside floating/tiling layouts to work
69 |
70 | * consider using machi, or making something similar (particularly draft mode)
71 | - https://github.com/xinhaoyuan/layout-machi
72 |
73 | * virt stuff?
74 | - https://github.com/virt-lightning/virt-lightning
75 |
76 | $dracula-superdark:e#191A21
77 | $dracula-background: #282a36;
78 | $dracula-current-line: #44475a;
79 | $dracula-selection: #44475a;
80 | $dracula-foreground: #f8f8f2;
81 | $dracula-comment: #6272a4;
82 | $dracula-cyan: #8be9fd;
83 | $dracula-green: #50fa7b;
84 | $dracula-orange: #ffb86c;
85 | $dracula-pink: #ff79c6;
86 | $dracula-purple: #bd93f9;
87 | $dracula-red: #ff5555;
88 | $dracula-yellow: #f1fa8c;
89 |
--------------------------------------------------------------------------------
/user.fnl:
--------------------------------------------------------------------------------
1 | (local icon-loader (require "icons.loader"))
2 |
3 | (local user {})
4 |
5 | (set user.avatar
6 | (icon-loader.load :shtwzrd :avatar8bit {
7 | :viewBox "0 -0.5 8 8"
8 | :height 8
9 | :width 8
10 | :stroke-width 1
11 | :stroke-linecap :none
12 | :stroke-linejoin :none
13 | :shape-rendering :crispEdges}))
14 |
15 | user
16 |
--------------------------------------------------------------------------------
/utils/async.fnl:
--------------------------------------------------------------------------------
1 | (fn async [...]
2 | "Transform inclosed expressions into a self-executing coroutine."
3 | `(if (coroutine.running) ; only create the outermost coro, no nesting
4 | (do ,... )
5 | (do ((coroutine.wrap (fn [] ,... ))))))
6 |
7 | (fn make-async [func]
8 | "Transform function FUNC with last arg as a callback into a function
9 | which utilizes coroutines.
10 | It will yield the current coroutine, and resume it when FUNC returns.
11 | Must be called within a coroutine."
12 | `(fn [...]
13 | (when (not= (type ,func) "function")
14 | (error (.. "await: Expected function, got " (type ,func))))
15 | (var co# nil)
16 | (let [len# (+ (select "#" ...) 1) ;; callback is left nil, so length++
17 | arg# [...]
18 | tv# (type (. arg# len#))]
19 | (when (not= tv# (type nil))
20 | (error (.. "await: Last arg (callback) should be nil, but got " tv#)))
21 | (tset arg# len# (fn [...]
22 | (if (= co# nil)
23 | (set co# [...])
24 | (coroutine.resume co# [...])
25 | [...])))
26 | (,func (unpack arg# 1 len#))
27 | (if (= co# nil)
28 | (do
29 | (set co# (coroutine.running))
30 | (coroutine.yield))
31 | (unpack co#)))))
32 |
33 | (fn await [func ...]
34 | "Transform function FUNC with last arg as a callback into a coroutine.
35 | Yield the current coroutine, which will get resumed when FUNC returns.
36 | Must be called within a coroutine, or from within an `async` block."
37 | (let [af# (make-async func)]
38 | `(,af# ,...)))
39 |
40 | {:async async
41 | :make-async make-async
42 | :await await}
43 |
--------------------------------------------------------------------------------
/utils/hotfuzz.fnl:
--------------------------------------------------------------------------------
1 | (local pp (require "fennel.view"))
2 | (local lume (require "vendor.lume"))
3 | (local {: concat : range : fill } (require "utils.oleander"))
4 | (local m {})
5 |
6 | (fn m.init-seller-matrix [n m]
7 | "Return an initialized N-by-M matrix for doing Seller's algorithm."
8 | (var matrix [])
9 | (for [i 1 n]
10 | (tset matrix i (fill 1 m 0))
11 | (tset (. matrix i) 1 (- i 1)))
12 | matrix)
13 |
14 | (fn m.matrix-pp [matrix]
15 | "Print MATRIX out in rows and cols typical for displaying edit distance calcs"
16 | (for [i 1 (length matrix)]
17 | (for [j 1 (length (. matrix 1))]
18 | (io.write (. (. matrix i) j) " "))
19 | (io.write "\n")))
20 |
21 | (fn m.levenshtein-dist [str-a str-b matrix i j]
22 | "Core levenshtein distance function.
23 | Write the cost at position (I,J) in the given MATRIX for STR-A and STR-B"
24 | (let [sub-cost (if (= (str-a:byte (- i 1)) (str-b:byte (- j 1))) 0 1)
25 | del-dist (+ (. (. matrix (- i 1)) j) 1); deletion
26 | ins-dist (+ (. (. matrix i) (- j 1)) 1); insertion
27 | sub-dist (+ (. (. matrix (- i 1)) (- j 1)) sub-cost)
28 | dist (math.min del-dist ins-dist sub-dist)]; substitution
29 | (tset (. matrix i) j dist))
30 | matrix)
31 |
32 | (fn m.levenshtein-col [str-a str-b matrix j]
33 | "Calculate the levenshtein distance between STR-A and STR-B on column J"
34 | (let [len-a (string.len str-a)]
35 | (for [i 2 (+ 1 len-a) 1]
36 | (m.levenshtein-dist str-a str-b matrix i j))
37 | matrix))
38 |
39 | (lambda m.trie-insert [trie text item]
40 | (var walker trie)
41 | (for [i 1 (length text)]
42 | (let [c (text:sub i i)]
43 | (when (not (. walker.children c))
44 | (tset walker.children c {:children {} :candidates [] :depth 0}))
45 | (set walker.depth (math.max walker.depth (- (length text) (- i 1))))
46 | (set walker (. walker.children c))))
47 | (table.insert walker.candidates item))
48 |
49 | (lambda m.init-search-trie [items ?trie]
50 | (var t (or ?trie {:candidates [] :children {} :depth 0}))
51 | (each [i c (ipairs items)]
52 | (m.trie-insert t c {:index i :value c}))
53 | t)
54 |
55 | (fn m.compare-results [a b]
56 | "Compare A to B and return a numeric score.
57 | If negative, A is better. If positive, B is better."
58 | (let [non-zero (fn [a] (~= a 0))]
59 | (if (non-zero (- b.score a.score)) ; score-diff
60 | (- b.score a.score)
61 | (non-zero (- a.hit.start b.hit.start)) ; earlier-match
62 | (- a.hit.start b.hit.start)
63 | (non-zero (- a.length-diff b.length-diff)) ; closer-length
64 | (- a.length-diff b.length-diff)
65 | (- a.index b.index)))) ; if nothing else, earlier insertion order wins
66 |
67 | (fn m.result-comparator [a b]
68 | "Compare A to B and return a boolean value.
69 | If true, A is better. If false, B is better."
70 | (> 0 (m.compare-results a b)))
71 |
72 | (fn m.add-result [results result-map candidate score hit len]
73 | "Add CANDIDATE with SCORE, HIT and LEN to RESULTS and RESULT-MAP in a
74 | normalized way."
75 | (local item {:value candidate.value
76 | :score score
77 | :hit hit
78 | :length-diff len
79 | :index candidate.index})
80 | (if (= nil (. result-map candidate.index))
81 | (do
82 | (tset result-map candidate.index (length results))
83 | (table.insert results item))
84 | (< (m.compare-results item (. results (. result-map candidate.index))) 0)
85 | (tset results (. result-map candidate.index) item)))
86 |
87 | (fn m.should-continue [node term opts score-value end-value]
88 | (let [best-potential (math.min score-value (- end-value (+ 1 node.depth)))]
89 | (>= (- 1 (/ best-potential (length term))) opts.threshold)))
90 |
91 | (fn m.back-track [matrix score-idx]
92 | "Walk back up MATRIX, returning the hit start and end from SCORE-IDX"
93 | (if (= 0 score-idx)
94 | {:start 0 :end 0}
95 | (do
96 | (var start score-idx)
97 | (for [i (- (length matrix) 2) 1 -1]
98 | (let [row (. matrix i)]
99 | (when (> start 1)
100 | (when (>= (. row start) (. row (- start 1)))
101 | (set start (- start 1))))))
102 | {:start (if (= start 1) 2 (- start 1))
103 | :end (- score-idx 1)})))
104 |
105 | (fn m.search-recursively [trie term matrix results result-map opts]
106 | (var stack [])
107 | (each [c child (pairs trie.children)]
108 | (table.insert stack [child 2 c 0 (length term)]))
109 |
110 | (var acc [])
111 | (while (> (length stack) 0)
112 | (do
113 | (var [node len char start-idx start-value] (table.remove stack))
114 | (tset acc (- len 1) char)
115 | (while (> (length acc) (- len 1))
116 | (table.remove acc))
117 |
118 | (m.levenshtein-col term (table.concat acc) matrix len)
119 |
120 | ;; update best score and position
121 | (local end-idx len)
122 | (local end-value (-> matrix (. (length matrix) (. end-idx))))
123 | (var [score-idx score-value] [start-idx start-value])
124 | (when (< end-value start-value)
125 | (do
126 | (set score-idx end-idx)
127 | (set score-value end-value)))
128 |
129 | ;; populate result
130 | (when (> (length node.candidates) 0)
131 | (do
132 | (let [l (length term)
133 | score (- 1.0 (/ score-value l))]
134 | (when (>= score opts.threshold)
135 | (do
136 | (let [hit (m.back-track matrix score-idx)
137 | ldiff (math.abs (- len (length term)))]
138 | (each [_ cand (pairs node.candidates)]
139 | (m.add-result results result-map cand score hit ldiff))))))))
140 |
141 | ;; push children onto stack to keep iterating
142 | (each [c child (pairs node.children)]
143 | (when (m.should-continue child term opts score-value end-value)
144 | (let [frame [child (+ 1 len) c score-idx score-value]]
145 | (table.insert stack frame)))))))
146 |
147 | (lambda m.search [items term ?opts ?trie]
148 | "Fuzzy-search ITEMS for TERM with given options ?OPTS.
149 | Returns a table with keys:
150 | :results -- sequential table of items with keys:
151 | :hit -- {:start :end} indices for matching portion of string
152 | :value -- provided text from ITEMS string table
153 | :index -- results's index in ITEMS table
154 | :score -- value from 0..1 scoring how close the result matches TERM
155 | :trie -- the prefix-tree generated for performing the search
156 |
157 | Possible ?OPTS:
158 | :threshold -- score (0..1) below which we discard results
159 |
160 | If calling multiple times with same ITEMS, cache :trie and pass it as ?TRIE to
161 | avoid recalculating the entire prefix-tree."
162 | (let [trie (or ?trie (m.init-search-trie items))
163 | defaults {:threshold .6}
164 | rows (+ (length term) 1)
165 | cols (+ trie.depth 1)
166 | matrix (m.init-seller-matrix rows cols)
167 | opts (concat defaults (or ?opts {}))]
168 | (var results [])
169 | (var result-map {})
170 | (when (or (>= opts.threshold 0)
171 | (= (length term) 0))
172 | (each [i cand (pairs trie.candidates)]
173 | (m.add-result
174 | results
175 | result-map
176 | cand
177 | 0
178 | {:start 1 :end (length cand)}
179 | (length term))))
180 |
181 | (m.search-recursively trie term matrix results result-map opts)
182 |
183 | {:trie trie
184 | :results (lume.sort results m.result-comparator)}))
185 |
186 | m
187 |
--------------------------------------------------------------------------------
/utils/identicon.fnl:
--------------------------------------------------------------------------------
1 | (local lawful (require "api.lawful"))
2 | (local xresources (require "beautiful.xresources"))
3 | (local dpi xresources.apply_dpi)
4 | (local lume (require "vendor.lume"))
5 | (local xml (require "utils.xml"))
6 | (local oleander (require "utils.oleander"))
7 | (local range oleander.range)
8 | (local bsd-checksum oleander.bsd-checksum)
9 |
10 | (local identicon {})
11 |
12 | (fn identicon.color-from-hash [hash]
13 | (math.randomseed hash)
14 | (let [r (math.random 0 255)
15 | g (math.random 0 255)
16 | b (math.random 0 255)]
17 | (.. "rgb(" r ", " g ", " b ")")))
18 |
19 | (lambda generate-squares [hash squares square-length color]
20 | "Generate squares in a SQUARESxSQUARES grid"
21 | (var seq [])
22 | (math.randomseed hash)
23 | (let [mirror (+ (/ squares 2) 1)
24 | l square-length]
25 | (for [i 0 (- mirror 1)]
26 | (for [j 0 (- squares 1)]
27 | (when (<= 0.5 (math.random))
28 | (let [x (* i l)
29 | y (* j l)
30 | x2 (* (- squares i 1) l)] ; x position reflected over center
31 | (table.insert
32 | seq
33 | [:rect {:x x :y y :width l :height l :fill color}])
34 | (when (~= i (- mirror 1)) ; no need to reflect the center column
35 | (table.insert
36 | seq
37 | [:rect {:x x2 :y y :width l :height l :fill color}])))))))
38 | seq)
39 |
40 | (lambda identicon.generate-svg
41 | [str squares dimension ?color-list?]
42 | "Get SVG string for an identicon using seed STR.
43 |
44 | Use SQUARES to determine the resolution of the identicon (must be odd).
45 | Use DIMENSION as a size hint.
46 | If ?COLOR-LIST? is provided, restrict output to that palette"
47 | (let [hash (bsd-checksum str)
48 | size (/ dimension squares)
49 | canvas (* squares size)
50 | mirror (+ (/ squares 2) 1)
51 | color (if ?color-list?
52 | (. ?color-list? (+ (% hash (length ?color-list?)) 1))
53 | (identicon.color-from-hash hash))]
54 | (xml.create-element
55 | :svg {:xmlns "http://www.w3.org/2000/svg"
56 | :shape-rendering :crispEdges ; turns off anti-aliasing
57 | :viewBox (.. "0 0 " canvas " " canvas)}
58 | (lume.concat [[:rect {:x 0 :y 0
59 | :width canvas :height canvas
60 | :stroke :none :fill :none}]]
61 | (generate-squares hash squares size color)))))
62 |
63 | (lambda identicon.create
64 | [str squares dimension ?color-list?]
65 | "Generate an identicon and return the filepath to the resulting SVG file.
66 |
67 | Use SQUARES to determine the resolution of the identicon (must be odd).
68 | Use DIMENSION as a size hint.
69 | If ?COLOR-LIST? is provided, restrict output to that palette"
70 | (let [filename (.. str ".svg")
71 | filepath (.. (lawful.fs.cache-dir) filename)
72 | exists (io.open filepath :r)]
73 | (if (= exists nil)
74 | (with-open [outfile (io.open filepath :w)]
75 | (let [svg (identicon.generate-svg str squares dimension ?color-list?)]
76 | (outfile:write svg)))
77 | (exists:close))
78 | (lawful.img.load-svg filepath dimension dimension)))
79 |
80 | identicon
81 |
--------------------------------------------------------------------------------
/utils/input.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local lume (require "vendor.lume"))
3 | (local unpack (or table.unpack _G.unpack))
4 |
5 | (var input {})
6 |
7 | ;; X11 Mouse Buttons
8 | (set input.left-click 1)
9 | (set input.middle-click 2)
10 | (set input.right-click 3)
11 | (set input.scroll-up 4)
12 | (set input.scroll-down 5)
13 | (set input.scroll-left 6)
14 | (set input.scroll-right 7)
15 | (set input.thumb-back-click 8)
16 | (set input.thumb-next-click 9)
17 |
18 | (set input.modkey "Mod4")
19 |
20 | (set input.modifiers
21 | {:mod input.modkey
22 | :shift "Shift"
23 | :ctrl "Control"
24 | :alt "Alt"})
25 |
26 | (fn input.map-mods
27 | ;; convert short modifier names to ones Awesome/X11 understands
28 | [mods]
29 | (-> mods
30 | (lume.map (partial . input.modifiers))))
31 |
32 | (fn input.keybind
33 | ;; describe a keybinding and assign it to a group
34 | [group mods key-code fun desc]
35 | (awful.key (input.map-mods mods) key-code fun {:description desc :group group}))
36 |
37 | (fn input.mousebind
38 | ;; describe a mouse button binding, short-modifier-aware
39 | [mods btn-code fun]
40 | (awful.button (input.map-mods mods) btn-code fun))
41 |
42 | (fn input.key-group
43 | ;; Given a group name, and any additional number of arguments shaped as follows:
44 | ;; [[MODIFIERS] KEY-CODE FUNCTION DESCRIPTION]
45 | ;; return an `awful.key` for each additional argument (multi-valued return)
46 | [group ...]
47 | (let [map-key-group (partial input.keybind group)]
48 | (-> [...]
49 | (lume.map (fn [k] (map-key-group (unpack k))))
50 | (lume.reduce (fn [a b] (lume.concat a b)) [])
51 | (values))))
52 |
53 | input
54 |
--------------------------------------------------------------------------------
/utils/oleander.fnl:
--------------------------------------------------------------------------------
1 | ;; Oleander is a dumping bed for various algorithms, functions and other little
2 | ;; flowers too precious to leave strewn about this garden.
3 |
4 | (local m {})
5 |
6 | (fn m.require? [...]
7 | "Optionally require a module, returning nil if it could not be resolved."
8 | (let [(ok mod) (pcall require ...)]
9 | (if ok mod nil)))
10 |
11 | (lambda m.range
12 | [start end ?step]
13 | "Create a sequential table from START to END, with a stride of ?STEP or 1"
14 | (local s (if (= ?step nil) 1 ?step))
15 | (var seq [])
16 | (for [i start end s]
17 | (table.insert seq i))
18 | seq)
19 |
20 | (lambda m.fill
21 | [start end value]
22 | "Create a table with keys from START to END where each value is VALUE"
23 | (var seq [])
24 | (for [i start end]
25 | (table.insert seq i value))
26 | seq)
27 |
28 | ;; try to 'polyfill' the best available bitwise operators
29 | (local bit (m.require? "bitop"))
30 | (local blshift (when bit bit.lshift))
31 | (local brshift (when bit bit.rshift))
32 | (local llshift (when (>= _G._VERSION "Lua 5.3") _G.lshift))
33 | (local lrshift (when (>= _G._VERSION "Lua 5.3") _G.rshift))
34 |
35 | (fn shim-lshift [n bits]
36 | (* (math.floor n) (^ 2 bits)))
37 |
38 | (fn shim-rshift [n bits]
39 | (math.floor (/ (math.floor n) (^ 2 bits))))
40 |
41 | (set m.lshift (or llshift blshift shim-lshift))
42 | (set m.rshift (or lrshift brshift shim-rshift))
43 |
44 | (fn m.bsd-checksum
45 | [str]
46 | "Simple checksum. Returns a 32-bit numeric hash of input STR."
47 | (var sum 0)
48 | (let [bytes [(string.byte str 1 -1)]]
49 | (each [_ value (ipairs bytes)]
50 | (set sum (+ (m.rshift sum 1) (m.lshift sum 31)))
51 | (set sum (+ sum value))
52 | (set sum (% sum 2147483647))) ; clamp to expected range
53 | sum))
54 |
55 | (fn m.concat [...]
56 | "Concatenate all tables into a new table."
57 | (let [ret []]
58 | (for [i 1 (select "#" ...) 1]
59 | (local t (select i ...))
60 | (let [tv (type t)]
61 | (if (= tv :table)
62 | (do
63 | (each [k v (pairs t)]
64 | (if (= (type k) "number")
65 | (table.insert ret v)
66 | (tset ret k v))))
67 | (error (.. "Expected table, got " tv)))))
68 | ret))
69 |
70 | m
71 |
--------------------------------------------------------------------------------
/utils/pulseaudio.fnl:
--------------------------------------------------------------------------------
1 | ;; a set of helper functions for interacting with PulseAudio
2 | ;; requires: pactl
3 | (local awful (require "awful"))
4 |
5 | (local pa {})
6 |
7 | (set
8 | pa.get-volume-script
9 | "pactl list sinks | grep -m1 'Volume:' | awk '{print $5}' | cut -d '%' -f1")
10 |
11 | (set
12 | pa.get-muted-script
13 | "pactl list sinks | grep -m 1 'Mute:' | awk '{printf \"%s\", $2}'")
14 |
15 | (set
16 | pa.get-mic-muted-script
17 | "pactl list sources | grep -m 1 'Mute:' | awk '{printf \"%s\", $2}'")
18 |
19 | (set
20 | pa.volume-listener-script
21 | "bash -c 'pactl subscribe 2> /dev/null | grep --line-buffered \"sink\"'")
22 |
23 | (set
24 | pa.mic-listener-script
25 | "bash -c 'pactl subscribe 2> /dev/null | grep --line-buffered \"source\"'")
26 |
27 | (set
28 | pa.kill-listeners-script
29 | "ps x | grep \"pactl subscribe\" | grep -v grep | awk '{print $1}' | xargs kill")
30 |
31 | (lambda pa.toggle-muted-script-tmpl [?sink]
32 | (let [sink (or ?sink "@DEFAULT_SINK@")]
33 | (.. "pactl set-sink-mute " sink " toggle")))
34 |
35 | (lambda pa.toggle-mute-mic-script-tmpl [?source]
36 | (let [source (or ?source "@DEFAULT_SOURCE@")]
37 | (.. "pactl set-source-mute " source " toggle")))
38 |
39 | (lambda pa.adjust-volume-script-tmpl [amount ?sink]
40 | (let [sink (or ?sink "@DEFAULT_SINK@")
41 | sign (if (> amount 0) "+" "")]
42 | (.. "pactl set-sink-volume " sink " " sign amount "%")))
43 |
44 | (lambda pa.adjust-volume [step ?callback]
45 | (let [cmd (pa.adjust-volume-script-tmpl step)
46 | cb (or ?callback (fn [] nil))]
47 | (awful.spawn.easy_async_with_shell cmd cb)))
48 |
49 | (lambda pa.toggle-muted [?sink ?callback]
50 | (let [cmd (pa.toggle-muted-script-tmpl ?sink)
51 | cb (or ?callback (fn [] nil))]
52 | (awful.spawn.easy_async_with_shell cmd cb)))
53 |
54 | (lambda pa.toggle-mic-mute [?source ?callback]
55 | (let [cmd (pa.toggle-mute-mic-script-tmpl ?source)
56 | cb (or ?callback (fn [] nil))]
57 | (awful.spawn.easy_async_with_shell cmd cb)))
58 |
59 | (lambda pa.get-volume [?callback]
60 | (let [cb (or ?callback (fn [] nil))]
61 | (awful.spawn.easy_async_with_shell pa.get-volume-script cb)))
62 |
63 | pa
64 |
--------------------------------------------------------------------------------
/utils/screens.fnl:
--------------------------------------------------------------------------------
1 | (local screen-utils {})
2 |
3 | (fn screen-utils.screen-iter []
4 | "Return an iterator for enumerating the currently attached screens"
5 | (var i 0)
6 | (var n (: screen :count))
7 | (fn []
8 | (set i (+ i 1))
9 | (when (<= i n)
10 | (. screen i))))
11 |
12 | (fn screen-utils.get-screens []
13 | "Return an array containing all current screens"
14 | (var ss [])
15 | (each [s (screen-utils.screen-iter)]
16 | (table.insert ss s))
17 | ss)
18 |
19 | screen-utils
20 |
--------------------------------------------------------------------------------
/utils/tags.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local beautiful (require "beautiful"))
3 | (local lume (require "vendor.lume"))
4 | (local identicon (require "utils.identicon"))
5 |
6 | (local tags {})
7 |
8 | (lambda tags.list-visible [?scr]
9 | "Return all active (visible) tags in-order"
10 | (let [s (or ?scr (awful.screen.focused))]
11 | (-> s.tags
12 | (lume.reject (fn [t] t.hide))
13 | (lume.sort (fn [a b] (< a.name b.name))))))
14 |
15 | (lambda tags.create [?scr ?layout ?props]
16 | "Create a new tag on ?SCR or focused screen with ?LAYOUT.
17 | Passthrough ?PROPS will merge, overwriting the defaults"
18 | (let [s (or ?scr (awful.screen.focused))
19 | id (.. (os.time) "-" (os.clock))] ; sequential name for filter/sort
20 | (awful.tag.add
21 | id
22 | (lume.merge
23 | {:screen s
24 | :layout (or ?layout awful.layout.suit.tile)
25 | :icon (identicon.create id 5 128)
26 | :selected true
27 | :gap beautiful.useless_gap}
28 | (or ?props {})))))
29 |
30 | (lambda tags.destroy [tag]
31 | "Delete TAG"
32 | (let [name tag.name]
33 | (: tag :delete)
34 | (awesome.emit_signal "tag::deleted" name)))
35 |
36 | (lambda tags.get-next [?scr]
37 | "Return the tag after current tag, creating a new tag if there is no next"
38 | (let [s (or ?scr (awful.screen.focused))
39 | visible (tags.list-visible s)
40 | ct (or
41 | (. (awful.screen.focused) :selected_tag)
42 | (lume.last visible))
43 | ct-index (lume.find visible ct)
44 | nt (. visible (+ (or ct-index (# visible)) 1))]
45 | (or nt (tags.create s ct.layout))))
46 |
47 | (lambda tags.view-next [?scr]
48 | "Unmap current tag and map next tag, creating a new tag if none exists"
49 | (let [s (or ?scr (awful.screen.focused))
50 | visible (tags.list-visible s)
51 | ct (or
52 | (. (awful.screen.focused) :selected_tag)
53 | (lume.last visible))
54 | nt (tags.get-next s)]
55 | (tset nt :selected true)
56 | (when ct
57 | (tset ct :selected false))))
58 |
59 | (lambda tags.get-prev [?scr]
60 | "Return the tag before current tag, or first visible tag"
61 | (let [s (or ?scr (awful.screen.focused))
62 | visible (tags.list-visible s)
63 | ct (or
64 | (. (awful.screen.focused) :selected_tag)
65 | (lume.last visible))
66 | ct-index (lume.find visible ct)
67 | pt-index (- (or ct-index (# visible)) 1)]
68 | (or (. visible pt-index) (. visible 1))))
69 |
70 | (lambda tags.view-prev [?scr]
71 | "Unmap current tag and map previous tag"
72 | (let [s (or ?scr (awful.screen.focused))
73 | visible (tags.list-visible s)
74 | ct (or
75 | (. (awful.screen.focused) :selected_tag)
76 | (lume.last visible))
77 | pt (tags.get-prev s)]
78 | (when pt
79 | (do
80 | (tset ct :selected false)
81 | (tset pt :selected true)
82 | (when (= (# (: ct :clients)) 0)
83 | (when (~= ct pt)
84 | (tags.destroy ct)))))))
85 |
86 | (lambda tags.activate [tag]
87 | "Idempotently activate a tag"
88 | (tset tag :hide false))
89 |
90 | (lambda tags.deactivate [tag]
91 | "Idempotently deactivate a tag"
92 | (tset tag :hide true)
93 | (tset tag :selected false))
94 |
95 | (fn tags.get-or-create
96 | [scr tag-prop]
97 | (let [{:pos pos :workspace workspace} tag-prop
98 | tag-by-props (lume.filter scr.tags (fn [t]
99 | (and
100 | (= t.pos pos)
101 | (= t.workspace workspace))))]
102 | (if (= (# tag-by-props) 0)
103 | (awful.tag.add pos {:layout (. awful.layout.layouts 1)
104 | :screen scr
105 | :pos pos
106 | :workspace workspace })
107 | (. tag-by-props 1))))
108 |
109 | (fn tags.get-by-prop
110 | [key value ?invert]
111 | (lume.filter (root.tags) (fn [t] (if ?invert
112 | (~= (. t key) value))
113 | (= (. t key) value))))
114 |
115 | tags
116 |
--------------------------------------------------------------------------------
/utils/widgets.fnl:
--------------------------------------------------------------------------------
1 | (local awful (require "awful"))
2 | (local beautiful (require "beautiful"))
3 |
4 | (local widget-utils {})
5 |
6 | (lambda widget-utils.buttonize [target on-click? ?props]
7 | "Attach ON-CLICK? callback and stylize TARGET as a button.
8 |
9 | PROPS are possible overrides to styles defined in `beautiful.button_*`
10 |
11 | PROPS.on-hover-in and PROPS.on-hover-out are optional callbacks"
12 | (let [props (or ?props {})
13 | bg-hover (or props.bg-hover beautiful.button_bg_hover "#222")
14 | fg-hover (or props.fg-hover beautiful.button_fg_hover "#eee")
15 | bg-click (or props.bg-click beautiful.button_bg_click "#222")
16 | fg-click (or props.fg-click beautiful.button_fg_click "#eee")
17 | color-in-fn (fn [t key color]
18 | (fn []
19 | (when (~= (. t key) color)
20 | (tset t (.. key :_backup) (. t key))
21 | (tset t (.. :has_ key :_backup) true))
22 | (tset t key color)))
23 | color-out-fn (fn [t key]
24 | (fn []
25 | (when (. t (.. :has_ key :_backup))
26 | (tset t key (. t (.. key :_backup))))))]
27 | (when props.on-hover-out
28 | (: target :connect_signal :mouse::leave props.on-hover-out))
29 | (when props.on-hover-in
30 | (: target :connect_signal :mouse::enter props.on-hover-in))
31 | (: target :connect_signal :mouse::enter (color-in-fn target :bg bg-hover))
32 | (: target :connect_signal :mouse::enter (color-in-fn target :fg fg-hover))
33 | (: target :connect_signal :mouse::leave (color-out-fn target :bg))
34 | (: target :connect_signal :mouse::leave (color-out-fn target :fg))
35 | (: target :connect_signal :button::press (color-in-fn target :bg bg-click))
36 | (: target :connect_signal :button::press (color-in-fn target :fg fg-click))
37 | (: target :connect_signal :button::release (color-out-fn target :bg))
38 | (: target :connect_signal :button::release (color-out-fn target :fg))
39 | (when on-click?
40 | (: target :connect_signal :button::release on-click?))
41 | target))
42 |
43 | (lambda widget-utils.popoverize [target popover-widget ?props]
44 | "Transform TARGET into a popover button which displays given POPOVER-WIDGET.
45 |
46 | PROPS contains:
47 | :preferred-positions -- priority array like [:bottom :right :left :top]
48 | :preferred-anchors -- priority array like [:middle :back :front]
49 | overrides to styles defined in `beautiful.button_*`
50 | :on-hover-in, :on-hover-out -- optional callbacks to mouse events on TARGET"
51 | (let [props (or ?props {})
52 | ppos (or props.preferred-positions [:bottom :right :left :top])
53 | panc (or props.preferred-anchors [:middle :back :front])]
54 | (widget-utils.buttonize
55 | target
56 | (fn []
57 | (awful.placement.next_to
58 | popover-widget
59 | {:mode :geometry
60 | :preferred_positions ppos
61 | :preferred_anchors panc
62 | :geometry mouse.current_widget_geometry})
63 | (tset popover-widget :visible (not popover-widget.visible)))
64 | props)))
65 |
66 | widget-utils
67 |
--------------------------------------------------------------------------------
/utils/xml.fnl:
--------------------------------------------------------------------------------
1 | (local {: map : reduce} (require :vendor.lume))
2 | (local unpack (or table.unpack _G.unpack))
3 |
4 | ;; hiccup-inspired library for creating XML from Fennel tables
5 | (local xml {})
6 |
7 | (fn xml.escape [str]
8 | "Substitute XML special characters with their escape sequences."
9 | (-> (or str "")
10 | (string.gsub "&" "&")
11 | (string.gsub "<" "<")
12 | (string.gsub ">" ">")
13 | (string.gsub "\"" """)))
14 |
15 | (fn xml.format-attr [key value]
16 | "Transform key-value pair into pair of attribute-value strings."
17 | (match value
18 | true [key "true"]
19 | false [key "false"]
20 | nil nil
21 | _ [key (xml.escape value)]))
22 |
23 | (fn xml.render-attrs [attrs]
24 | "Turn a map into a string of XML attributes."
25 | (var str "")
26 | (when attrs
27 | (each [attr value (pairs attrs)]
28 | (let [[k v] (xml.format-attr attr value)]
29 | (when k
30 | (set str (.. str " " k "=\"" v "\""))))))
31 | str)
32 |
33 | (lambda xml.create-element [tag ?attrs ...]
34 | "Render TAG with attributes ?ATTRS and restargs as children."
35 | (..
36 | "<" tag (xml.render-attrs ?attrs) ">"
37 | (-> (if (= ... nil) [""] [...])
38 | (map
39 | (fn [v]
40 | (match (type v)
41 | "table" (let [ftv (type (. v 1))]
42 | (match ftv
43 | "string" (let [[t a c] v] (xml.create-element t a c))
44 | "table" (-> v
45 | (map (fn [tbl]
46 | (let [[t a c] tbl]
47 | (xml.create-element t a c))))
48 | (reduce (fn [a b] (.. a b)) ""))
49 | nil ""
50 | _ (error
51 | (..
52 | "Found " ftv ", expected string or table"))))
53 | _ (or v ""))))
54 | (reduce (fn [a b] (.. a b))))
55 | "" tag ">"))
56 |
57 | xml
58 |
--------------------------------------------------------------------------------
/vendor/bencode.lua:
--------------------------------------------------------------------------------
1 | local unpack = unpack or table.unpack
2 | local encode, decode
3 |
4 | local function decode_list(str, t, total_len)
5 | -- print(" list", str, lume.serialize(t))
6 | if(str:sub(1,1) == "e") then return t, total_len + 1 end
7 | local value, v_len = decode(str)
8 | if(not value) then return value, v_len end
9 | table.insert(t, value)
10 | total_len = total_len + v_len
11 | return decode_list(str:sub(v_len + 1), t, total_len)
12 | end
13 |
14 | local function decode_table(str, t, total_len)
15 | -- print(" table", str, lume.serialize(t))
16 | if(str:sub(1,1) == "e") then return t, total_len + 1 end
17 | local key, k_len = decode(str)
18 | if(not key) then return key, k_len end
19 | local value, v_len = decode(str:sub(k_len+1))
20 | if(not value) then return value, v_len end
21 | local end_pos = 1 + k_len + v_len
22 | t[key] = value
23 | total_len = total_len + k_len + v_len
24 | return decode_table(str:sub(end_pos), t, total_len)
25 | end
26 |
27 | function decode(str)
28 | -- print("decoding", str)
29 | if(str:sub(1,1) == "l") then
30 | return decode_list(str:sub(2), {}, 1)
31 | elseif(str:sub(1,1) == "d") then
32 | return decode_table(str:sub(2), {}, 1)
33 | elseif(str:sub(1,1) == "i") then
34 | -- print(" number", tonumber(str:sub(2, str:find("e") - 1)))
35 | return(tonumber(str:sub(2, str:find("e") - 1))), str:find("e")
36 | elseif(str:match("[0-9]+")) then
37 | local num_str = str:match("[0-9]+")
38 | local beginning_of_string = #num_str + 2
39 | local str_len = tonumber(num_str)
40 | local total_len = beginning_of_string + str_len - 1
41 | -- print(" string", str:sub(beginning_of_string))
42 | return str:sub(beginning_of_string, total_len), total_len
43 | else
44 | return false, "Could not parse "..str
45 | end
46 | end
47 |
48 | local function decode_all(str, already)
49 | local decoded, len_or_err = decode(str)
50 | if(not decoded) then return decoded, len_or_err, already end
51 | already = already or {}
52 | table.insert(already, decoded)
53 | if(decoded and #str == len_or_err) then
54 | return already
55 | elseif(decoded) then
56 | return decode_all(str:sub(len_or_err + 1), already)
57 | else
58 | return false, len_or_err
59 | end
60 | end
61 |
62 | local function encode_str(s) return #s .. ":" .. s end
63 | local function encode_int(n) return "i" .. tostring(n) .. "e" end
64 |
65 | local function encode_table(t)
66 | local s, keys = "d", {}
67 | for k in pairs(t) do table.insert(keys, k) end
68 | table.sort(keys)
69 | for _,k in ipairs(keys) do s = s .. encode(k) .. encode(t[k]) end
70 | return s .. "e"
71 | end
72 |
73 | local function encode_list(l)
74 | local s = "l"
75 | for _,x in ipairs(l) do s = s .. encode(x) end
76 | return s .. "e"
77 | end
78 |
79 | local function count(tbl)
80 | local i = 0
81 | for _ in pairs(tbl) do i = i + 1 end
82 | return i
83 | end
84 |
85 | function encode(x)
86 | if(type(x) == "table" and select("#", unpack(x)) == count(x)) then
87 | return encode_list(x)
88 | elseif(type(x) == "table") then
89 | return encode_table(x)
90 | elseif(type(x) == "number" and math.floor(x) == x) then
91 | return encode_int(x)
92 | elseif(type(x) == "string") then
93 | return encode_str(x)
94 | else
95 | return false, "Could not encode " .. type(x) .. ": " .. tostring(x)
96 | end
97 | end
98 |
99 | return {decode=decode, decode_all=decode_all, encode=encode}
100 |
--------------------------------------------------------------------------------
/vendor/jeejah/fenneleval.lua:
--------------------------------------------------------------------------------
1 | local fennel = require("fennel")
2 | -- fall back to pre-0.8.0 if necessary
3 | local fennelview = fennel.view or require("fennelview")
4 |
5 | local d = os.getenv("DEBUG") and print or function(_) end
6 |
7 | local repls = {}
8 |
9 | local print_for = function(write)
10 | return function(...)
11 | local args = {...}
12 | for i,x in ipairs(args) do args[i] = tostring(x) end
13 | table.insert(args, "\n")
14 | write(table.concat(args, " "))
15 | end
16 | end
17 |
18 | local make_repl = function(session, repls)
19 | local on_values = function(xs)
20 | session.values(xs)
21 | session.done({status={"done"}})
22 | end
23 | local read = function()
24 | -- If we skip empty input, it confuses the client.
25 | local input = coroutine.yield()
26 | if(input:find("^%s*$")) then return "nil\n" else return input end
27 | end
28 | local err = function(errtype, msg)
29 | session.write(table.concat({errtype, msg}, ": ")) session.done()
30 | end
31 |
32 | local env = session.sandbox
33 | if not env then
34 | env = {}
35 | for k, v in pairs(_G) do env[k] = v end
36 | env.io = {}
37 | end
38 | env.print = print_for(session.write)
39 | env.io.write = session.write
40 | env.io.read = function()
41 | session.needinput()
42 | local input, done = coroutine.yield()
43 | done()
44 | return input
45 | end
46 |
47 | local f = function()
48 | return fennel.repl({readChunk = read,
49 | onValues = on_values,
50 | onError = err,
51 | env = env,
52 | pp = fennelview})
53 | end
54 | repls[session.id] = coroutine.wrap(f)
55 | repls[session.id]()
56 | return repls[session.id]
57 | end
58 |
59 | return function(conn, msg, session, send, response_for)
60 | local repl = repls[session.id] or make_repl(session, repls)
61 | if msg.op == "eval" then
62 | d("Evaluating", msg.code)
63 | session.values = function(xs)
64 | send(conn, response_for(msg, {value=table.concat(xs, "\n") .. "\n"}))
65 | end
66 | session.done = function()
67 | send(conn, response_for(msg, {status={"done"}}))
68 | end
69 | session.needinput = function()
70 | send(conn, response_for(msg, {status={"need-input"}}))
71 | end
72 | repl(msg.code .. "\n")
73 | elseif msg.op == "stdin" then
74 | d("Evaluating", msg.code)
75 | repl(msg.stdin,
76 | function() send(conn, response_for(msg, {status={"done"}})) end)
77 | elseif msg.op == "completions" then
78 | d("Completions", msg.prefix)
79 | session.values = function(xs)
80 | local result = {}
81 | for _, v in pairs(xs) do
82 | table.insert(result, {candidate = v})
83 | end
84 | send(conn, response_for(msg, {completions = result}))
85 | end
86 | session.done = function()
87 | send(conn, response_for(msg, {status={"done"}}))
88 | end
89 | session.needinput = function()
90 | send(conn, response_for(msg, {status={"need-input"}}))
91 | end
92 | repl(",complete " .. msg.prefix .. "\n")
93 | elseif msg.op == "lookup" then
94 | d("Lookup", msg.sym)
95 | session.values = function(xs)
96 | if #xs == 0 or
97 | string.find(xs[1], "#") or
98 | xs[1] == msg.sym .. " not found" then
99 | -- nREPL spec has no error-signalling for this, just empty map
100 | send(conn, response_for(msg, {info = {}}))
101 | else
102 | local i = string.find(xs[1], "\n")
103 | local top_line = string.sub(xs[1], 1, i)
104 | local j = string.find(top_line, " ") + 1
105 | local k = string.find(top_line, "%)") - 1
106 | local info = {name = msg.sym,
107 | arglists = "([" .. string.sub(top_line, j, k) .. "])",
108 | doc = string.sub(xs[1], i + 1)}
109 | send(conn, response_for(msg, {info = info}))
110 | end
111 | end
112 | session.done = function()
113 | send(conn, response_for(msg, {status={"done"}}))
114 | end
115 | session.needinput = function()
116 | send(conn, response_for(msg, {status={"need-input"}}))
117 | end
118 | repl(",doc " .. msg.sym .. "\n")
119 | end
120 | end
121 |
--------------------------------------------------------------------------------
/widgets/layout-switcher.fnl:
--------------------------------------------------------------------------------
1 | (local wibox (require "wibox"))
2 | (local awful (require "awful"))
3 | (local beautiful (require "beautiful"))
4 | (local icon-loader (require "icons.loader"))
5 | (local layout-icons (require "icons.layouts"))
6 | (local widget-utils (require "utils.widgets"))
7 |
8 | (local ls {})
9 |
10 | (set ls.reset
11 | (fn []
12 | (let [img (icon-loader.load
13 | :layouts
14 | (awful.layout.getname)
15 | {:stroke-color beautiful.fg})]
16 | (tset ls.switcher :image img))))
17 |
18 | (set ls.on-click
19 | (fn []
20 | (awful.layout.inc 1 (awful.screen.focused) awful.layout.layouts)
21 | (ls.on-hover-in)))
22 |
23 | (set ls.on-hover-in
24 | (fn []
25 | (let [img (icon-loader.load
26 | :layouts
27 | (awful.layout.getname)
28 | {:stroke-color beautiful.fg
29 | :fill beautiful.fg})]
30 | (tset ls.switcher :image img))))
31 |
32 | (set ls.switcher
33 | (widget-utils.buttonize
34 | (wibox.widget {:widget wibox.widget.imagebox})
35 | ls.on-click
36 | {:on-hover-in ls.on-hover-in
37 | :on-hover-out ls.reset}))
38 |
39 | (awful.tag.attached_connect_signal nil "property::layout" ls.reset)
40 | (awful.tag.attached_connect_signal nil "property::selected" ls.reset)
41 |
42 | (awesome.connect_signal "persistence::loaded" ls.reset)
43 |
44 | ls
45 |
--------------------------------------------------------------------------------
/widgets/workspace-switcher.fnl:
--------------------------------------------------------------------------------
1 | (local wibox (require "wibox"))
2 | (local workspaces (require :features.workspaces))
3 | (local { : notify } (require :api.lawful))
4 | (require-macros :awesome-macros)
5 |
6 | (local ws {})
7 |
8 | (set ws.indicator
9 | (wibox.widget
10 | (/<
11 | :widget wibox.widget.imagebox
12 | :resize true
13 | :image (workspaces.get-icon workspaces.current))))
14 |
15 | (awesome.connect_signal
16 | "workspaces::applied"
17 | (fn [sig]
18 | (notify.msg (.. "Changed to " sig " workspace."))
19 | (tset ws.indicator :image (workspaces.get-icon sig))))
20 |
21 | ws
22 |
--------------------------------------------------------------------------------