├── README.md
├── fennelview.fnl
├── fennelview.lua
├── friar.el
├── recipes
└── friar
└── starter-kit
├── config.fnl
└── rc.lua
/README.md:
--------------------------------------------------------------------------------
1 | # friar
2 |
3 | _Fennel Repl In Awesome Repl_
4 |
5 | ## What?
6 | Interact with your running AwesomeWM session from inside Emacs,
7 | using [Fennel](https://fennel-lang.org), a lua-based Lisp.
8 |
9 | Interested in using Fennel for writing your own AwesomeWM configuration?
10 | Check the `starter-kit` included in this repo.
11 | It contains an `rc.lua` that bootstraps loading the provided `config.fnl` file,
12 | which is a line-for-line translation of the default AwesomeWM configuration.
13 |
14 | ## Installation
15 | Via `use-package` with `straight.el`:
16 |
17 | ```
18 | (use-package friar
19 | :straight (:host github :repo "warreq/friar" :branch "master"
20 | :files (:defaults "*.lua" "*.fnl")))
21 | ```
22 |
23 | ## Usage
24 |
25 | `M-x friar` and he's ready to serve you.
26 |
27 | ## Troubleshooting
28 |
29 | ### I can't connect to AwesomeWM (D-Bus error)
30 |
31 | Try slapping this into your `.xinitrc` to ensure you have a `DBUS_SESSION_BUS_ADDRESS`,
32 | which Emacs needs in order to connect to your D-Bus.
33 | ```
34 | if test -z "$DBUS_SESSION_BUS_ADDRESS" ; then
35 | eval `dbus-launch --sh-syntax --exit-with-session`
36 | fi
37 | ```
38 |
39 | Make sure `dbus-launch` exists on your path -- you may need to install a package like `dbus-x11`.
40 |
41 |
--------------------------------------------------------------------------------
/fennelview.fnl:
--------------------------------------------------------------------------------
1 | ;; A pretty-printer that outputs tables in Fennel syntax.
2 | ;; Loosely based on inspect.lua: http://github.com/kikito/inspect.lua
3 |
4 | (fn view-quote [str] (.. '"' (: str :gsub '"' '\\"') '"'))
5 |
6 | (local short-control-char-escapes
7 | {"\a" "\\a" "\b" "\\b" "\f" "\\f" "\n" "\\n"
8 | "\r" "\\r" "\t" "\\t" "\v" "\\v"})
9 |
10 | (local long-control-char-esapes
11 | (let [long {}]
12 | (for [i 0 31]
13 | (let [ch (string.char i)]
14 | (when (not (. short-control-char-escapes ch))
15 | (tset short-control-char-escapes ch (.. "\\" i))
16 | (tset long ch (: "\\%03d" :format i)))))
17 | long))
18 |
19 | (fn escape [str]
20 | (let [str (: str :gsub "\\" "\\\\")
21 | str (: str :gsub "(%c)%f[0-9]" long-control-char-esapes)]
22 | (: str :gsub "%c" short-control-char-escapes)))
23 |
24 | (fn sequence-key? [k len]
25 | (and (= (type k) "number")
26 | (<= 1 k)
27 | (<= k len)
28 | (= (math.floor k) k)))
29 |
30 | (local type-order {:number 1 :boolean 2 :string 3 :table 4
31 | :function 5 :userdata 6 :thread 7})
32 |
33 | (fn sort-keys [a b]
34 | (let [ta (type a) tb (type b)]
35 | (if (and (= ta tb) (~= ta "boolean")
36 | (or (= ta "string") (= ta "number")))
37 | (< a b)
38 | (let [dta (. type-order a)
39 | dtb (. type-order b)]
40 | (if (and dta dtb)
41 | (< dta dtb)
42 | dta true
43 | dtb false
44 | :else (< ta tb))))))
45 |
46 | (fn get-sequence-length [t]
47 | (var len 1)
48 | (each [i (ipairs t)] (set len i))
49 | len)
50 |
51 | (fn get-nonsequential-keys [t]
52 | (let [keys {}
53 | sequence-length (get-sequence-length t)]
54 | (each [k (pairs t)]
55 | (when (not (sequence-key? k sequence-length))
56 | (table.insert keys k)))
57 | (table.sort keys sort-keys)
58 | (values keys sequence-length)))
59 |
60 | (fn count-table-appearances [t appearances]
61 | (if (= (type t) "table")
62 | (when (not (. appearances t))
63 | (tset appearances t 1)
64 | (each [k v (pairs t)]
65 | (count-table-appearances k appearances)
66 | (count-table-appearances v appearances)))
67 | (when (and t (= t t)) ; no nans please
68 | (tset appearances t (+ (or (. appearances t) 0) 1))))
69 | appearances)
70 |
71 |
72 |
73 | (var put-value nil) ; mutual recursion going on; defined below
74 |
75 | (fn puts [self ...]
76 | (each [_ v (ipairs [...])]
77 | (table.insert self.buffer v)))
78 |
79 | (fn tabify [self] (puts self "\n" (: self.indent :rep self.level)))
80 |
81 | (fn already-visited? [self v] (~= (. self.ids v) nil))
82 |
83 | (fn get-id [self v]
84 | (var id (. self.ids v))
85 | (when (not id)
86 | (let [tv (type v)]
87 | (set id (+ (or (. self.max-ids tv) 0) 1))
88 | (tset self.max-ids tv id)
89 | (tset self.ids v id)))
90 | (tostring id))
91 |
92 | (fn put-sequential-table [self t length]
93 | (puts self "[")
94 | (set self.level (+ self.level 1))
95 | (for [i 1 length]
96 | (puts self " ")
97 | (put-value self (. t i)))
98 | (set self.level (- self.level 1))
99 | (puts self " ]"))
100 |
101 | (fn put-key [self k]
102 | (if (and (= (type k) "string")
103 | (: k :find "^[-%w?\\^_`!#$%&*+./@~:|<=>]+$"))
104 | (puts self ":" k)
105 | (put-value self k)))
106 |
107 | (fn put-kv-table [self t]
108 | (puts self "{")
109 | (set self.level (+ self.level 1))
110 | (each [k v (pairs t)]
111 | (when (not (: (tostring k) :find "^_"))
112 | (do
113 | (tabify self)
114 | (put-key self k)
115 | (puts self " ")
116 | (put-value self v))))
117 | (set self.level (- self.level 1))
118 | (tabify self)
119 | (puts self "}"))
120 |
121 | (fn put-table [self t]
122 | (if (already-visited? self t)
123 | (puts self "#
")
124 | (>= self.level self.depth)
125 | (puts self "{...}")
126 | :else
127 | (let [(non-seq-keys length) (get-nonsequential-keys t)
128 | id (get-id self t)]
129 | (if (> (. self.appearances t) 1)
130 | (puts self "#<" id ">")
131 | (and (= (# non-seq-keys) 0) (= (# t) 0))
132 | (puts self "{}")
133 | (= (# non-seq-keys) 0)
134 | (put-sequential-table self t length)
135 | :else
136 | (put-kv-table self t)))))
137 |
138 | (set put-value (fn [self v]
139 | (let [tv (type v)]
140 | (if (= tv "string")
141 | (puts self (view-quote (escape v)))
142 | (or (= tv "number") (= tv "boolean") (= tv "nil"))
143 | (puts self (tostring v))
144 | (= tv "table")
145 | (put-table self v)
146 | :else
147 | (puts self "#<" (tostring v) ">")))))
148 |
149 | (fn fennelview [root options]
150 | (let [options (or options {})
151 | inspector {:appearances (count-table-appearances root {})
152 | :depth (or options.depth 128)
153 | :level 0 :buffer {} :ids {} :max-ids {}
154 | :indent (or options.indent " ")}]
155 | (put-value inspector root)
156 | (table.concat inspector.buffer)))
157 |
--------------------------------------------------------------------------------
/fennelview.lua:
--------------------------------------------------------------------------------
1 | local function view_quote(str)
2 | return ("\"" .. str:gsub("\"", "\\\"") .. "\"")
3 | end
4 | local short_control_char_escapes = {["\11"] = "\\v", ["\12"] = "\\f", ["\13"] = "\\r", ["\7"] = "\\a", ["\8"] = "\\b", ["\9"] = "\\t", ["\n"] = "\\n"}
5 | local function _0_(...)
6 | local long = {}
7 | for i = 0, 31 do
8 | local ch = string.char(i)
9 | local function _1_(...)
10 | if not short_control_char_escapes[ch] then
11 | short_control_char_escapes[ch] = ("\\" .. i)
12 | long[ch] = ("\\%03d"):format(i)
13 | return nil
14 | end
15 | end
16 | _1_(...)
17 | end
18 | return long
19 | end
20 | local long_control_char_esapes = _0_(...)
21 | local function escape(str)
22 | local str = str:gsub("\\", "\\\\")
23 | local str = str:gsub("(%c)%f[0-9]", long_control_char_esapes)
24 | return str:gsub("%c", short_control_char_escapes)
25 | end
26 | local function sequence_key_3f(k, len)
27 | return ((type(k) == "number") and (1 <= k) and (k <= len) and (math.floor(k) == k))
28 | end
29 | local type_order = {["function"] = 5, boolean = 2, number = 1, string = 3, table = 4, thread = 7, userdata = 6}
30 | local function sort_keys(a, b)
31 | local ta = type(a)
32 | local tb = type(b)
33 | if ((ta == tb) and (ta ~= "boolean") and ((ta == "string") or (ta == "number"))) then
34 | return (a < b)
35 | else
36 | local dta = type_order[a]
37 | local dtb = type_order[b]
38 | if (dta and dtb) then
39 | return (dta < dtb)
40 | elseif dta then
41 | return true
42 | elseif dtb then
43 | return false
44 | elseif "else" then
45 | return (ta < tb)
46 | end
47 | end
48 | end
49 | local function get_sequence_length(t)
50 | local len = 1
51 | for i in ipairs(t) do
52 | len = i
53 | end
54 | return len
55 | end
56 | local function get_nonsequential_keys(t)
57 | local keys = {}
58 | local sequence_length = get_sequence_length(t)
59 | for k in pairs(t) do
60 | local function _1_()
61 | if not sequence_key_3f(k, sequence_length) then
62 | return table.insert(keys, k)
63 | end
64 | end
65 | _1_()
66 | end
67 | table.sort(keys, sort_keys)
68 | return keys, sequence_length
69 | end
70 | local function count_table_appearances(t, appearances)
71 | local function _1_()
72 | if (type(t) == "table") then
73 | if not appearances[t] then
74 | appearances[t] = 1
75 | for k, v in pairs(t) do
76 | count_table_appearances(k, appearances)
77 | count_table_appearances(v, appearances)
78 | end
79 | return nil
80 | end
81 | else
82 | if (t and (t == t)) then
83 | appearances[t] = ((appearances[t] or 0) + 1)
84 | return nil
85 | end
86 | end
87 | end
88 | _1_()
89 | return appearances
90 | end
91 | local put_value = nil
92 | local function puts(self, ...)
93 | for _, v in ipairs({...}) do
94 | table.insert(self.buffer, v)
95 | end
96 | return nil
97 | end
98 | local function tabify(self)
99 | return puts(self, "\n", self.indent:rep(self.level))
100 | end
101 | local function already_visited_3f(self, v)
102 | return (self.ids[v] ~= nil)
103 | end
104 | local function get_id(self, v)
105 | local id = self.ids[v]
106 | local function _1_()
107 | if not id then
108 | local tv = type(v)
109 | id = ((self["max-ids"][tv] or 0) + 1)
110 | self["max-ids"][tv] = id
111 | self.ids[v] = id
112 | return nil
113 | end
114 | end
115 | _1_()
116 | return tostring(id)
117 | end
118 | local function put_sequential_table(self, t, length)
119 | puts(self, "[")
120 | self.level = (self.level + 1)
121 | for i = 1, length do
122 | puts(self, " ")
123 | put_value(self, t[i])
124 | end
125 | self.level = (self.level - 1)
126 | return puts(self, " ]")
127 | end
128 | local function put_key(self, k)
129 | if ((type(k) == "string") and k:find("^[-%w?\\^_`!#$%&*+./@~:|<=>]+$")) then
130 | return puts(self, ":", k)
131 | else
132 | return put_value(self, k)
133 | end
134 | end
135 | local function put_kv_table(self, t)
136 | puts(self, "{")
137 | self.level = (self.level + 1)
138 | for k, v in pairs(t) do
139 | local function _1_()
140 | if not tostring(k):find("^_") then
141 | tabify(self)
142 | put_key(self, k)
143 | puts(self, " ")
144 | return put_value(self, v)
145 | end
146 | end
147 | _1_()
148 | end
149 | self.level = (self.level - 1)
150 | tabify(self)
151 | return puts(self, "}")
152 | end
153 | local function put_table(self, t)
154 | if already_visited_3f(self, t) then
155 | return puts(self, "#")
156 | elseif (self.level >= self.depth) then
157 | return puts(self, "{...}")
158 | elseif "else" then
159 | local non_seq_keys, length = get_nonsequential_keys(t)
160 | local id = get_id(self, t)
161 | if (self.appearances[t] > 1) then
162 | return puts(self, "#<", id, ">")
163 | elseif ((#non_seq_keys == 0) and (#t == 0)) then
164 | return puts(self, "{}")
165 | elseif (#non_seq_keys == 0) then
166 | return put_sequential_table(self, t, length)
167 | elseif "else" then
168 | return put_kv_table(self, t)
169 | end
170 | end
171 | end
172 | local function _1_(self, v)
173 | local tv = type(v)
174 | if (tv == "string") then
175 | return puts(self, view_quote(escape(v)))
176 | elseif ((tv == "number") or (tv == "boolean") or (tv == "nil")) then
177 | return puts(self, tostring(v))
178 | elseif (tv == "table") then
179 | return put_table(self, v)
180 | elseif "else" then
181 | return puts(self, "#<", tostring(v), ">")
182 | end
183 | end
184 | put_value = _1_
185 | local function fennelview(root, options)
186 | local options = (options or {})
187 | local inspector = {["max-ids"] = {}, appearances = count_table_appearances(root, {}), buffer = {}, depth = (options.depth or 128), ids = {}, indent = (options.indent or " "), level = 0}
188 | put_value(inspector, root)
189 | return table.concat(inspector.buffer)
190 | end
191 |
--------------------------------------------------------------------------------
/friar.el:
--------------------------------------------------------------------------------
1 | ;;; friar.el --- Fennel REPL In Awesome REPL -*- lexical-binding: t -*-
2 |
3 | ;;; Commentary:
4 |
5 | ;; Provides a REPL for interacting with the Awesome window manager
6 | ;; using Fennel -- a zero-overhead Lisp implementation in Lua.
7 | ;; Input is handled by the comint package, and output is pretty-printed
8 | ;; via fennelview within Awesome's Lua context.
9 |
10 | ;; To start: M-x friar. Type C-h m in the *friar* buffer for more info.
11 |
12 | (require 'dbus)
13 | (require 'comint)
14 | (require 'pp)
15 | (require 'fennel-mode)
16 |
17 | ;;; User variables
18 |
19 | (defgroup friar nil
20 | "Fennel REPL In Awesome REPL."
21 | :group 'lisp)
22 |
23 | (defvar friar-awesome-logo "
24 | ██████████
25 | ▀▀▀▀▀▀▀███
26 | ██████████
27 | ███▀▀▀▀███
28 | ██████ ███
29 | ▀▀▀▀▀▀ ▀▀▀")
30 |
31 | (defcustom friar-fennel-file-path "/usr/bin/fennel"
32 | "Path to the `fennel` executable, which will be used for interacting with Awesome."
33 | :group 'friar)
34 |
35 | (defcustom friar-mode-hook nil
36 | "Hooks to be run when friar (`friar-mode') is started."
37 | :options '(eldoc-mode)
38 | :type 'hook
39 | :group 'friar)
40 |
41 | (defvar friar-header
42 | (concat ";; The friar welcomes you."
43 | "\n\n"
44 | friar-awesome-logo
45 | "\n\n")
46 | "Message displayed on REPL-start.")
47 |
48 | (defvar friar-prompt "> ")
49 |
50 | (defconst friar-directory
51 | (if load-file-name
52 | (file-name-directory load-file-name)
53 | default-directory))
54 |
55 | (defvar friar-fennelview
56 | (with-temp-buffer
57 | (insert-file-contents-literally (expand-file-name "./fennelview.lua" friar-directory))
58 | (buffer-substring-no-properties (point-min) (point-max))))
59 |
60 | (defun friar-awesome-eval (lua-chunk)
61 | (dbus-call-method
62 | :session
63 | "org.awesomewm.awful"
64 | "/"
65 | "org.awesomewm.awful.Remote"
66 | "Eval"
67 | lua-chunk))
68 |
69 | (defun friar-process nil
70 | (get-buffer-process (current-buffer)))
71 |
72 | (defun friar-pm nil
73 | (process-mark (get-buffer-process (current-buffer))))
74 |
75 | (defun friar-set-pm (pos)
76 | (set-marker (process-mark (get-buffer-process (current-buffer))) pos))
77 |
78 | (defun friar-is-whitespace-or-comment (string)
79 | "Return non-nil if STRING is all whitespace or a comment."
80 | (or (string= string "")
81 | (string-match-p "\\`[ \t\n]*\\(?:;.*\\)*\\'" string)))
82 |
83 | (defun friar-input-sender (_proc input)
84 | (setq friar-input input)
85 | (friar-send-input))
86 |
87 | (defun friar-send-input (&optional for-effect)
88 | "Eval Fennel expression at prompt."
89 | (interactive)
90 | (friar-eval-input friar-input for-effect))
91 |
92 | (defun friar-format-command (str)
93 | "Generate a shell command for invoking Fennel and compiling given string."
94 | (format "printf '%s' | %s --compile /dev/stdin" str friar-fennel-file-path))
95 |
96 | (defun friar-compile-input (str)
97 | "Compile given string to Lua using Fennel."
98 | (let* ((command (friar-format-command str)))
99 | (with-temp-buffer
100 | (shell-command command (current-buffer) "*friar-stderr*")
101 | (let* ((output (buffer-substring-no-properties (point-min) (point-max)))
102 | (err (with-current-buffer "*friar-stderr*" (buffer-substring-no-properties (point-min) (point-max)))))
103 | (with-current-buffer "*friar-stderr*" (erase-buffer))
104 | (if (string= "" err)
105 | `((:is-error nil) (:result ,output))
106 | `((:is-error t) (:result ,err)))))))
107 |
108 | (defun friar-eval-input (input-string &optional for-effect)
109 | "Evaluate the Fennel expression INPUT-STRING, and pretty-print the result."
110 | ;; This function compiles the input with Fennel and chucks it over to
111 | ;; Awesome over D-Bus.
112 | (let ((string input-string)
113 | (output "")
114 | (pmark (friar-pm)))
115 | (unless (friar-is-whitespace-or-comment string)
116 | (let* ((wrapped-expression (format "(fennelview %s)" input-string))
117 | (compilation (friar-compile-input input-string))
118 | (compile-output (car (alist-get :result compilation)))
119 | (was-compilation-error (car (alist-get :is-error compilation))))
120 | (setq res (format "%s\n%s" "[Compilation error]:" compile-output))
121 | (unless was-compilation-error
122 | (let* ((expr (car (alist-get :result (friar-compile-input wrapped-expression))))
123 | (chunk (format "%s\n%s" friar-fennelview expr)))
124 | (setq res (friar-awesome-eval chunk))))
125 | (goto-char pmark)
126 | (when (or (not for-effect) (not (equal res "")))
127 | (setq output (format "%s\n" res)))
128 | (setq output (format "%s\n%s" output friar-prompt))
129 | (comint-output-filter (friar-process) output)
130 | (friar-set-pm (point-max))))))
131 |
132 | (define-derived-mode friar-mode comint-mode "Friar"
133 | "Major mode for interacting with the Awesome window manager via Fennel.
134 | Uses `comint-mode` as an interface.
135 |
136 | \\"
137 | nil "Friar"
138 | ;; this transpiles the input from the comint buffer from Fennel to Lua
139 | (setq comint-input-sender 'friar-input-sender)
140 |
141 | (setq comint-use-prompt-regexp t)
142 |
143 | (setq comint-input-sender-no-newline t)
144 | (setq comint-process-echoes nil)
145 |
146 | (setq comint-prompt-regexp (concat "^" (regexp-quote friar-prompt)))
147 | (setq comint-prompt-read-only t)
148 | (set (make-local-variable 'paragraph-separate) "\\'") ;; allows M-{ to work;
149 | (set (make-local-variable 'paragraph-start) comint-prompt-regexp)
150 | (set (make-local-variable 'fill-paragraph-function) 'lisp-fill-paragraph)
151 | (setq-local comment-start ";")
152 | (setq-local comment-use-syntax t)
153 | (set-syntax-table fennel-mode-syntax-table)
154 | (fennel-font-lock-setup)
155 |
156 | ;; A dummy process for comint. The friar was a fraud all along!
157 | (unless (comint-check-proc (current-buffer))
158 | ;; use hexl as the dummy process, because it'll be there if emacs is.
159 | (start-process "friar" (current-buffer) "hexl")
160 | (set-process-query-on-exit-flag (friar-process) nil)
161 | (goto-char (point-max))
162 |
163 | (set (make-local-variable 'comint-inhibit-carriage-motion) t)
164 |
165 | ;; Print the welcome message
166 | (insert friar-header)
167 | (friar-set-pm (point-max))
168 | (unless comint-use-prompt-regexp
169 | (let ((inhibit-read-only t))
170 | (add-text-properties
171 | (point-min) (point-max)
172 | '(rear-nonsticky t field output inhibit-line-move-field-capture t))))
173 | (comint-output-filter (friar-process) friar-prompt)
174 | (set-marker comint-last-input-start (friar-pm))
175 | (set-process-filter (get-buffer-process (current-buffer)) 'comint-output-filter)))
176 |
177 | ;;;###autoload
178 | (defun friar (&optional buf-name)
179 | "Interact with the Awesome window manager via a Fennel REPL.
180 | Switches to the buffer named BUF-NAME if provided (`*friar*' by default),
181 | or creates it if it does not exist."
182 | (interactive)
183 | (get-buffer-create "*friar-stderr*") ;; create stderr buffer aot
184 | (let (old-point
185 | (buf-name (or buf-name "*friar*")))
186 | (unless (comint-check-proc buf-name)
187 | (with-current-buffer (get-buffer-create buf-name)
188 | (unless (zerop (buffer-size)) (setq old-point (point)))
189 | (friar-mode)))
190 | (pop-to-buffer-same-window buf-name)
191 | (when old-point (push-mark old-point))))
192 |
193 | (provide 'friar)
194 |
--------------------------------------------------------------------------------
/recipes/friar:
--------------------------------------------------------------------------------
1 | (friar :fetcher github :repo "warreq/friar"
2 | :files (:defaults "*.lua" "*.fnl"))
--------------------------------------------------------------------------------
/starter-kit/config.fnl:
--------------------------------------------------------------------------------
1 | ;; Standard awesome library
2 | (local gears (require "gears"))
3 | (local awful (require "awful"))
4 | (require "awful.autofocus")
5 | ;; Widget and layout library
6 | (local wibox (require "wibox"))
7 | ;; Theme handling library
8 | (local beautiful (require "beautiful"))
9 | ;; Notification library
10 | (local naughty (require "naughty"))
11 | (local menubar (require "menubar"))
12 | ;; Enable hotkeys help widget for VIM and other apps
13 | ;; when client with a matching name is opened:
14 | (local hotkeys_popup (require "awful.hotkeys_popup"))
15 | (require "awful.hotkeys_popup.keys")
16 |
17 | ;; Error handling
18 | ;; Check if awesome encountered an error during startup and fell back to
19 | ;; another config (This code will only ever execute for the fallback config)
20 | (when awesome.startup_errors
21 | (naughty.notify {:preset naughty.config.presets.critical
22 | :title "Oops, there were errors during startup!"
23 | :text awesome.startup_errors}))
24 |
25 | ;; Handle runtime errors after startup
26 | (do
27 | (var in_error false)
28 | (awesome.connect_signal "debug::error" (fn [err]
29 | ;; Make sure we don't go into an endless error loop
30 | (when (not in_error)
31 | (set in_error true)
32 | (naughty.notify {:preset naughty.config.presets.critical
33 | :title "Oops, an error happened!"
34 | :text (tostring err)})
35 | (set in_error false)))))
36 |
37 | ;; Variable definitions
38 | ;; Themes define colours, icons, font and wallpapers.
39 | (beautiful.init (.. (gears.filesystem.get_themes_dir) "default/theme.lua"))
40 |
41 | ;; This is used later as the default terminal and editor to run.
42 | (var terminal "xterm")
43 | (var editor (or (os.getenv "EDITOR") "nano"))
44 | (var editor_cmd (.. terminal " -e " editor))
45 |
46 | ;; Default modkey.
47 | ;; Usually, Mod4 is the key with a logo between Control and Alt.
48 | ;; If you do not like this or do not have such a key,
49 | ;; I suggest you to remap Mod4 to another key using xmodmap or other tools.
50 | ;; However, you can use another modifier like Mod1, but it may interact with others.
51 | (var modkey "Mod4")
52 |
53 | ;; Table of layouts to cover with awful.layout.inc, order matters.
54 | (set awful.layout.layouts [
55 | awful.layout.suit.floating
56 | awful.layout.suit.tile
57 | awful.layout.suit.tile.left
58 | awful.layout.suit.tile.bottom
59 | awful.layout.suit.tile.top
60 | awful.layout.suit.fair
61 | awful.layout.suit.fair.horizontal
62 | awful.layout.suit.spiral
63 | awful.layout.suit.spiral.dwindle
64 | awful.layout.suit.max
65 | awful.layout.suit.max.fullscreen
66 | awful.layout.suit.magnifier
67 | awful.layout.suit.corner.nw
68 | ;; awful.layout.suit.corner.ne
69 | ;; awful.layout.suit.corner.sw
70 | ;; awful.layout.suit.corner.se
71 | ])
72 |
73 | ;; Menu
74 | ;; Create a launcher widget and a main menu
75 | (global myawesomemenu [
76 | [ "hotkeys" (fn [] (hotkeys_popup.show_help nil (awful.screen.focused))) ]
77 | [ "manual" (.. terminal " -e man awesome") ]
78 | [ "edit config" (.. editor_cmd " " awesome.conffile) ]
79 | [ "restart" awesome.restart ]
80 | [ "quit" (fn [] (awesome.quit)) ]])
81 |
82 | (global mymainmenu (awful.menu {:items [
83 | [ "awesome" myawesomemenu beautiful.awesome_icon ]
84 | [ "open terminal" terminal ]]}))
85 |
86 | (global mylauncher (awful.widget.launcher {:image beautiful.awesome_icon
87 | :menu mymainmenu }))
88 |
89 | ;; Menubar configuration
90 | (set menubar.utils.terminal terminal) ;; Set the terminal for applications that require it
91 |
92 | ;; Keyboard map indicator and switcher
93 | (global mykeyboardlayout (awful.widget.keyboardlayout))
94 |
95 | ;; Wibar
96 | ;; Create a textclock widget
97 | (global mytextclock (wibox.widget.textclock))
98 |
99 | ;; Create a wibox for each screen and add it
100 | (local taglist_buttons
101 | (gears.table.join
102 | (awful.button [] 1 (fn [t] (: t :view_only)))
103 | (awful.button [ modkey ] 1 (fn [t] (when client.focus (: client.focus :move_to_tag t))))
104 | (awful.button [] 3 awful.tag.viewtoggle)
105 | (awful.button [ modkey ] 3 (fn [t] (when client.focus (: client.focus :toggle_tag t))))
106 | (awful.button [] 4 (fn [t] (awful.tag.viewnext t.screen)))
107 | (awful.button [] 5 (fn [t] (awful.tag.viewprev t.screen)))))
108 |
109 | (local tasklist_buttons
110 | (gears.table.join
111 | (awful.button [] 1 (fn [c]
112 | (if (= c client.focus)
113 | (set c.minimized true)
114 | (: c :emit_signal
115 | "request::activate"
116 | "tasklist"
117 | {:raise true}
118 | ))))
119 | (awful.button [] 3 (fn [] (awful.menu.client_list {:theme {:width 250 }})))
120 | (awful.button [] 4 (fn [] (awful.client.focus.byidx 1)))
121 | (awful.button [] 5 (fn [] (awful.client.focus.byidx -1)))))
122 |
123 | (fn set_wallpaper [s]
124 | ;; Wallpaper
125 | (when beautiful.wallpaper
126 | (var wallpaper beautiful.wallpaper)
127 | ;; If wallpaper is a function, call it with the screen
128 | (when (= (type wallpaper) "function")
129 | (set wallpaper (wallpaper s)))
130 | (gears.wallpaper.maximized wallpaper s true)))
131 |
132 | ;; Re-set wallpaper when a screen's geometry changes (e.g. different resolution)
133 | (screen.connect_signal "property::geometry" set_wallpaper)
134 |
135 | (awful.screen.connect_for_each_screen
136 | (fn [s]
137 | ;; Wallpaper
138 | (set_wallpaper s)
139 |
140 | ;; Each screen has its own tag table.
141 | (awful.tag [ "1" "2" "3" "4" "5" "6" "7" "8" "9" ] s (. awful.layout.layouts 1))
142 |
143 | ;; Create a promptbox for each screen
144 | (set s.mypromptbox (awful.widget.prompt))
145 | ;; Create an imagebox widget which will contain an icon indicating which layout we're using.
146 | ;; We need one layoutbox per screen.
147 | (set s.mylayoutbox (awful.widget.layoutbox s))
148 | (: s.mylayoutbox :buttons (gears.table.join
149 | (awful.button [] 1 (fn [] (awful.layout.inc 1 s awful.layout.layouts)))
150 | (awful.button [] 3 (fn [] (awful.layout.inc -1 s)))
151 | (awful.button [] 4 (fn [] (awful.layout.inc 1 s)))
152 | (awful.button [] 5 (fn [] (awful.layout.inc -1 s)))))
153 | ;; Create a taglist widget
154 | (set s.mytaglist (awful.widget.taglist {
155 | :screen s
156 | :filter awful.widget.taglist.filter.all
157 | :buttons taglist_buttons
158 | }))
159 |
160 | ;; Create a tasklist widget
161 | (set s.mytasklist (awful.widget.tasklist {
162 | :screen s
163 | :filter awful.widget.tasklist.filter.currenttags
164 | :buttons tasklist_buttons
165 | }))
166 |
167 | ;; Create the wibox
168 | (set s.mywibox (awful.wibar { :position "top" :screen s }))
169 |
170 | ;; Add widgets to the wibox
171 | (: s.mywibox :setup {
172 | :layout wibox.layout.align.horizontal
173 | 1 { ;; Left widgets
174 | :layout wibox.layout.fixed.horizontal
175 | 1 mylauncher
176 | 2 s.mytaglist
177 | 3 s.mypromptbox
178 | }
179 | 2 s.mytasklist ;; Middle widget
180 | 3 { ;; Right widgets
181 | :layout wibox.layout.fixed.horizontal
182 | 1 mykeyboardlayout
183 | 2 (wibox.widget.systray)
184 | 3 mytextclock
185 | 4 s.mylayoutbox
186 | }
187 | })))
188 |
189 |
190 | ;; Mouse bindings
191 | (root.buttons (gears.table.join
192 | (awful.button [ ] 3 (fn [] (: mymainmenu :toggle)))
193 | (awful.button [ ] 4 awful.tag.viewnext)
194 | (awful.button [ ] 5 awful.tag.viewprev)))
195 |
196 | ;; key bindings
197 | (global globalkeys
198 | (gears.table.join
199 | (awful.key [ modkey ] "s" hotkeys_popup.show_help
200 | { :description "show help" :group "awesome"})
201 | (awful.key [ modkey ] "Left" awful.tag.viewprev
202 | {:description "view previous" :group "tag"})
203 | (awful.key [ modkey ] "Right" awful.tag.viewnext
204 | {:description "view next" :group "tag"})
205 | (awful.key [ modkey ] "Escape" awful.tag.history.restore
206 | {:description "go back" :group "tag"})
207 | (awful.key [ modkey ] "j" (fn [] (awful.client.focus.byidx 1))
208 | {:description "focus next by index" :group "client"})
209 | (awful.key [ modkey ] "k" (fn [] (awful.client.focus.byidx -1))
210 | {:description "focus previous by index" :group "client"})
211 | (awful.key [ modkey ] "w" (fn [] (: mymainmenu :show))
212 | {:description "show main menu" :group "awesome"})
213 |
214 | ;; Layout manipulation
215 | (awful.key [ modkey "Shift" ] "j" (fn [] (awful.client.swap.byidx 1))
216 | {:description "swap with next client by index" :group "client"})
217 | (awful.key [ modkey "Shift" ], "k" (fn [] (awful.client.swap.byidx -1))
218 | {:description "swap with previous client by index" :group "client"})
219 | (awful.key [ modkey "Control" ] "j" (fn [] (awful.screen.focus_relative 1))
220 | {:description "focus the next screen" :group "screen"})
221 | (awful.key [ modkey "Control" ] "k" (fn [] (awful.screen.focus_relative -1))
222 | {:description "focus the previous screen" :group "screen"})
223 | (awful.key [ modkey ] "u" awful.client.urgent.jumpto
224 | {:description "jump to urgent client" :group "client"})
225 | (awful.key [ modkey ] "Tab" (fn []
226 | (awful.client.focus.history.previous)
227 | (when client.focus (: client.focus :raise)))
228 | {:description "go back" :group "client"})
229 |
230 | ;; Standard program
231 | (awful.key [ modkey ] "Return" (fn [] (awful.spawn terminal))
232 | {:description "open a terminal" :group "launcher"})
233 | (awful.key [ modkey "Control" ] "r" awesome.restart
234 | {:description "reload awesome" :group "awesome"})
235 | (awful.key [ modkey "Shift" ] "q" awesome.quit
236 | {:description "quit awesome" :group "awesome"})
237 | (awful.key [ modkey ] "l" (fn [] (awful.tag.incmwfact 0.05))
238 | {:description "increase master width factor" :group "layout"})
239 | (awful.key [ modkey ] "h" (fn [] (awful.tag.incmwfact -0.05))
240 | {:description "decrease master width factor" :group "layout"})
241 | (awful.key [ modkey "Shift" ] "h" (fn [] (awful.tag.incnmaster 1 nil true))
242 | {:description "increase the number of master clients" :group "layout"})
243 | (awful.key [ modkey "Shift" ] "l" (fn [] (awful.tag.incnmaster -1 nil true))
244 | {:description "decrease the number of master clients" :group "layout"})
245 | (awful.key [ modkey "Control" ] "h" (fn [] (awful.tag.incncol 1 nil true))
246 | {:description "increase the number of columns" :group "layout"})
247 | (awful.key [ modkey "Control" ] "l" (fn [] (awful.tag.incncol -1 nil true))
248 | {:description "decrease the number of columns" :group "layout"})
249 | (awful.key [ modkey ] "space" (fn [] (awful.layout.inc 1))
250 | {:description "select next" :group "layout"})
251 | (awful.key [ modkey "Shift" ] "space" (fn [] (awful.layout.inc -1))
252 | {:description "select previous" :group "layout"})
253 | (awful.key [ modkey "Control" ] "n" (fn []
254 | (local c (awful.client.restore))
255 | (when c ;; Focus restored client
256 | (: c :emit_signal "request::activate" "key.unminimize" {:raise true})))
257 | {:description "restore minimized" :group "client"})
258 |
259 | ;; Prompt
260 | (awful.key [ modkey ] "r" (fn [] (: (. (awful.screen.focused) :mypromptbox) :run))
261 | {:description "run prompt" :group "launcher"})
262 |
263 | (awful.key [ modkey ] "x" (fn []
264 | (let [fscr (awful.screen.focused)]
265 | (awful.prompt.run {
266 | :prompt "Run Lua code: "
267 | :textbox fscr.mypromptbox.widget
268 | :exe_callback awful.util.eval
269 | :history_path (.. (awful.util.get_cache_dir) "/history_eval")
270 | })))
271 | {:description "lua execute prompt" :group "awesome"})
272 | ;; Menubar
273 | (awful.key [ modkey ] "p" (fn [] (menubar.show))
274 | {:description "show the menubar" :group "launcher"})))
275 |
276 | (global clientkeys
277 | (gears.table.join
278 | (awful.key [ modkey ] "f" (fn [c] (set c.fullscreen (not c.fullscreen)) (: c :raise))
279 | {:description "toggle fullscreen" :group "client"})
280 | (awful.key [ modkey "Shift" ] "c" (fn [c] (: c :kill))
281 | {:description "close" :group "client"})
282 | (awful.key [ modkey "Control" ] "space" awful.client.floating.toggle
283 | {:description "toggle floating" :group "client"})
284 | (awful.key [ modkey "Control" ] "Return" (fn [c] (: c :swap (awful.client.getmaster)))
285 | {:description "move to master" :group "client"})
286 | (awful.key [ modkey ] "o" (fn [c] (: c :move_to_screen))
287 | {:description "move to screen" :group "client"})
288 | (awful.key [ modkey ] "t"(fn [c] (set c.ontop (not c.ontop)))
289 | {:description "toggle keep on top" :group "client"})
290 | (awful.key [ modkey ] "n" (fn [c]
291 | ;; The client currently has the input focus, so it cannot be
292 | ;; minimized, since minimized clients can't have the focus.
293 | (set c.minimized true))
294 | {:description "minimize" :group "client"}),
295 | (awful.key [ modkey ] "m" (fn [c] (set c.maximized (not c.maximized)) (: c :raise))
296 | {:description "(un)maximize" :group "client"}),
297 | (awful.key [ modkey "Control" ] "m" (fn [c] (set c.maximized_vertical (not c.maximized_vertical)) (: c :raise))
298 | {:description "(un)maximize vertically" :group "client"}),
299 | (awful.key [modkey "Shift" ] "m" (fn [c] (set c.maximized_horizontal (not c.maximized_horizontal)) (: c :raise))
300 | {:description "(un)maximize horizontally" :group "client"})))
301 |
302 | ;; Bind all key numbers to tags.
303 | ;; Be careful: we use keycodes to make it work on any keyboard layout.
304 | ;; This should map on the top row of your keyboard, usually 1 to 9.
305 | (for [i 1 9]
306 | (global globalkeys
307 | (gears.table.join
308 | globalkeys
309 | ;; View tag only.
310 | (awful.key [ modkey ] (.. "#" (+ i 9))
311 | (fn []
312 | (let [screen (awful.screen.focused)
313 | tag (. screen.tags i)]
314 | (when tag
315 | (: tag :view_only))))
316 | {:description (.. "view tag #" i) :group "tag"})
317 | ;; Toggle tag display
318 | (awful.key [ modkey "Control" ] (.. "#" (+ i 9))
319 | (fn []
320 | (let [screen (awful.screen.focused)
321 | tag (. screen.tags i)]
322 | (when tag
323 | (awful.tag.viewtoggle))))
324 | {:description (.. "toggle tag #" i) :group "tag"})
325 | ;; Move client to tag
326 | (awful.key [ modkey, "Shift" ] (.. "#" (+ i 9))
327 | (fn []
328 | (when client.focus
329 | (let [tag (. client.focus.screen.tags i)]
330 | (when tag
331 | (: client.focus :move_to_tag tag)))))
332 | {:description (.. "move focused client to tag #" i) :group "tag"})
333 | ;; Toggle tag on focused client.
334 | (awful.key [ modkey "Control" "Shift" ] (.. "#" (+ i 9))
335 | (fn []
336 | (when client.focus
337 | (let [tag (. client.focus.screen.tags i)]
338 | (when tag
339 | (: client.focus :toggle_tag tag)))))
340 | {:description (.. "toggle focused client on tag #" i) :group "tag"}))))
341 |
342 | (global clientbuttons
343 | (gears.table.join
344 | (awful.button [] 1 (fn [c] (: c :emit_signal "request::activate" "mouse_click" {:raise true})))
345 | (awful.button [ modkey ] 1 (fn [c]
346 | (: c :emit_signal "request::activate" "mouse_click" {:raise true})
347 | (awful.mouse.client.move c)))
348 | (awful.button [ modkey ] 3 (fn [c]
349 | (: c :emit_signal "request::activate" "mouse_click" {:raise true})
350 | (awful.mouse.client.resize c)))))
351 | ;; Set keys
352 | (root.keys globalkeys)
353 |
354 | ;; Rules
355 | ;; Rules to apply to new clients (through the "manage" signal)
356 | (set awful.rules.rules
357 | [
358 | ;; All clients will match this rule.
359 | {
360 | :rule { }
361 | :properties { :border_width beautiful.border_width
362 | :border_color beautiful.border_normal
363 | :focus awful.client.focus.filter
364 | :raise true
365 | :keys clientkeys
366 | :buttons clientbuttons
367 | :screen awful.screen.preferred
368 | :placement (+ awful.placement.no_overlap awful.placement.no_offscreen)
369 | }
370 | }
371 |
372 | ;; Floating clients.
373 | {
374 | :rule_any {
375 | :instance [
376 | "DTA" ;; Firefox addon DownThemAll.
377 | "copyq" ;; Includes session name in class.
378 | "pinentry"
379 | ]
380 | :class [
381 | "Arandr"
382 | "Blueman-manager"
383 | "Gpick"
384 | "Kruler"
385 | "MessageWin" ;; kalarm.
386 | "Sxiv"
387 | "Tor Browser" ;; Needs a fixed window size to avoid fingerprinting by screen size.
388 | "Wpa_gui"
389 | "veromix"
390 | "xtightvncviewer"
391 | ]
392 | ;; Note that the name property shown in xprop might be set slightly after creation of the client
393 | ;; and the name shown there might not match defined rules here.
394 | :name [
395 | "Event Tester" ;; xev
396 | ]
397 | :role [
398 | "AlarmWindow" ;; Thunderbird's calendar.
399 | "ConfigManager" ;; Thunderbird's about:config.
400 | "pop-up" ;; e.g. Google Chrome's (detached) Developer Tools.
401 | ]
402 | }
403 | :properties {:floating true }}
404 |
405 | ;; Add titlebars to normal clients and dialogs
406 | {
407 | :rule_any {:type [ "normal", "dialog" ] }
408 | :properties {:titlebars_enabled true }
409 | }
410 |
411 | ;; Set Firefox to always map on the tag named "2" on screen 1.
412 | ;; { :rule { :class "Firefox" }
413 | ;; :properties { :screen 1 :tag "2" } }
414 | ])
415 |
416 | ;; Signals
417 | ;; Signal function to execute when a new client appears.
418 | (client.connect_signal
419 | "manage"
420 | (fn [c]
421 | ;; Set the windows at the slave,
422 | ;; i.e. put it at the end of others instead of setting it master.
423 | ;; (when (not awesome.startup) (awful.client.setslave c))
424 |
425 | (when (and awesome.startup
426 | (not c.size_hints.user_position)
427 | (not c.size_hints.program_position))
428 | ;; Prevent clients from being unreachable after screen count changes.
429 | (awful.placement.no_offscreen c))))
430 |
431 | ;; Add a titlebar if titlebars_enabled is set to true in the rules.
432 | (client.connect_signal
433 | "request::titlebars"
434 | (fn [c]
435 | ;; buttons for the titlebar
436 | (let [buttons (gears.table.join
437 | (awful.button [] 1 (fn []
438 | (: c :emit_signal "request::activate" "titlebar" {:raise true})
439 | (awful.mouse.client.move c)))
440 | (awful.button [] 3 (fn []
441 | (: c :emit_signal "request::activate" "titlebar" {:raise true})
442 | (awful.mouse.client.resize c))))
443 | titlebar (awful.titlebar c)]
444 | (: titlebar :setup {
445 | 1 { ;; Left
446 | 1 (awful.titlebar.widget.iconwidget c)
447 | :buttons buttons
448 | :layout wibox.layout.fixed.horizontal
449 | }
450 | 2 { ;; Middle
451 | 1 { ;; Title
452 | :align "center"
453 | :widget (awful.titlebar.widget.titlewidget c)
454 | }
455 | :buttons buttons
456 | :layout wibox.layout.flex.horizontal
457 | }
458 | 3 { ;; Right
459 | 1 (awful.titlebar.widget.floatingbutton c)
460 | 2 (awful.titlebar.widget.maximizedbutton c)
461 | 3 (awful.titlebar.widget.stickybutton c)
462 | 4 (awful.titlebar.widget.ontopbutton c)
463 | 5 (awful.titlebar.widget.closebutton c)
464 | :layout (wibox.layout.fixed.horizontal)
465 | }
466 | :layout wibox.layout.align.horizontal
467 | }))))
468 |
469 | ;; Enable sloppy focus, so that focus follows mouse.
470 | (client.connect_signal "mouse::enter"
471 | (fn [c]
472 | (: c :emit_signal "request::activate" "mouse_enter" {:raise false})))
473 |
474 | (client.connect_signal "focus" (fn [c] (set c.border_color beautiful.border_focus)))
475 | (client.connect_signal "unfocus" (fn [c] (set c.border_color beautiful.border_normal)))
476 |
--------------------------------------------------------------------------------
/starter-kit/rc.lua:
--------------------------------------------------------------------------------
1 | fennel = require("fennel")
2 | local gears = require("gears")
3 | fennel.path = fennel.path .. ";.config/awesome/?.fnl"
4 | table.insert(package.loaders or package.searchers, fennel.searcher)
5 |
6 | require("config") -- load ~/.config/awesome/config.fnl
7 |
--------------------------------------------------------------------------------