├── 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 | --------------------------------------------------------------------------------