├── .github └── README.md ├── .gitmodules ├── bindings ├── client │ ├── key.lua │ └── mouse.lua ├── global │ ├── key.lua │ └── mouse.lua ├── init.lua ├── mod.lua └── widgets │ ├── layoutbox.lua │ ├── taglist.lua │ └── tasklist.lua ├── config ├── apps.lua ├── auto.lua ├── init.lua ├── user.lua └── vars.lua ├── helpers.lua ├── modules ├── json.lua └── overflow.lua ├── rc.lua ├── rules └── init.lua ├── script ├── screenshot.lua ├── tym.lua └── xresources.lua ├── signals ├── client │ └── init.lua ├── init.lua ├── naughty │ ├── error.lua │ └── init.lua ├── ruled │ └── init.lua ├── screen │ └── init.lua ├── system │ ├── init.lua │ └── weather.lua └── tag │ └── init.lua ├── theme ├── assets │ ├── arrow │ │ ├── down.svg │ │ └── up.svg │ ├── audio │ │ ├── off.svg │ │ └── on.svg │ ├── awesome.svg │ ├── bluetooth │ │ ├── off.svg │ │ └── on.svg │ ├── dot.svg │ ├── image.svg │ ├── layout │ │ ├── float.svg │ │ ├── tile_bottom.svg │ │ ├── tile_left.svg │ │ └── tile_right.svg │ ├── network │ │ ├── off.svg │ │ └── on.svg │ ├── settings.svg │ ├── tile.svg │ └── weather │ │ ├── clear-n.svg │ │ ├── clear.svg │ │ ├── clouds.svg │ │ ├── few-clouds-n.svg │ │ ├── few-clouds.svg │ │ ├── fog.svg │ │ ├── rain-light.svg │ │ ├── rain.svg │ │ ├── snow.svg │ │ └── storm.svg ├── colorscheme │ ├── adwaita │ │ └── init.lua │ ├── fullerene │ │ └── init.lua │ ├── gruvbox │ │ └── init.lua │ ├── init.lua │ ├── janleigh │ │ └── init.lua │ └── solarized │ │ └── init.lua ├── init.lua └── settings.lua └── widgets ├── config ├── init.lua └── module │ ├── colors.lua │ ├── init.lua │ └── util.lua ├── dock ├── init.lua └── module │ ├── colors.lua │ ├── init.lua │ └── pinned.lua ├── init.lua ├── menu ├── colors.lua └── init.lua ├── notification ├── colors.lua ├── init.lua └── normal.lua ├── timepanel ├── init.lua └── module │ ├── calendar.lua │ ├── colors.lua │ ├── init.lua │ └── weather.lua ├── titlebar ├── colors.lua ├── init.lua └── normal.lua └── wibar ├── init.lua └── module ├── cfg.lua ├── clock.lua ├── colors.lua ├── init.lua ├── layoutbox.lua ├── systray.lua ├── taglist.lua └── tasklist.lua /.github/README.md: -------------------------------------------------------------------------------- 1 | > **Warning** 2 | I am tremendously stupid and may often commit completely broken changes, please watch out for that. 3 | 4 | # Welcome! 5 | 6 | This is my second attempt at ricing **AwesomeWM** and really turn it into my dreamed desktop UX. 7 | My [previous setup](https://github.com/gwynsav/gwdawful) I worked on for several months and it 8 | was my introduction to this framework WM. As such, I ended up carrying on a bunch of poor decisions 9 | and mistakes that I made early on and as I learnt. 10 | 11 | This setup is a reimagining of the original, and hopefully will go entirely its own way, but 12 | with me being more familiar with both Lua and the Awesome API. 13 | 14 | Please keep in mind that this repository is **FOR REFERENCE ONLY**. It is **NOT** advised that you 15 | attempt to use this configuration. For your own good really. 16 | 17 | ## Not TODO 18 | 19 | - I will not make an install script. As I just said, this is for **REFERENCE**. 20 | - I will not make a nix flake either, for the same reason. 21 | - I will try to keep external libraries to a minimum. The objective is for this code to be 22 | as easy for me to just put on another of my devices as possible, as well as for it to be 23 | robust. 24 | 25 | 26 | # Structure 27 | 28 | Most of this project follows the structure of [Suconakh's project](https://github.com/suconakh/awesome-awesome-rc). 29 | However there exist some additions of mine to this. 30 | 31 | `config/user.lua` aggregates user options like the wallpaper, avatar, as well as gaps, borders, etc. 32 | 33 | `config/auto.lua` contains autostart commands to be executed: 34 | 35 | - At the start of an X session. 36 | - Every time Awesome is loaded (and reloaded). 37 | - Shell code. 38 | 39 | `theme/` contains within itself the `colorscheme/` directory in which several colorschemes are 40 | defined as a table of colors and a wallpaper path, as well as `assets/` which contains the images 41 | used throughout the config. `init.lua` in this case plays the role of the 42 | [theme file](https://awesomewm.org/apidoc/documentation/06-appearance.md.html). 43 | 44 | `script/` consists of lua scripts to perform useful actions like taking screenshots. 45 | 46 | ## Dependencies 47 | 48 | This setup requires **awesome-git**, which needs to be compiled from source or is **unofficially** 49 | packaged for distros like Arch and Fedora. I mainly use the **IBM Plex** fonts which are very likely 50 | packaged for your distro. I don't use any icon font or nerd font, they fucking suck. 51 | 52 | The screenshot script uses **maim** and **xclip**. Both are common packages. 53 | 54 | You should set the `TERMINAL` and `EDITOR` environmental variables, those are used. In any other case, 55 | just have **xterm** and **nano** installed, Awesome looks for those by default. 56 | 57 | ## Installation 58 | 59 | Check your dependencies and just: 60 | 61 | ``` 62 | mkdir -p ~/.local/share/awesome 63 | git clone --recursive https://github.com/gwynsav/gwileful ~/.config/awesome 64 | ``` 65 | 66 | 67 | # Gallery 68 | 69 | ## Actually TODO 70 | 71 | Just don't have much to show so far. 72 | 73 | 74 | # Credits 75 | 76 | The people who have inspired and really helped me, directly or indirectly, make this setup: 77 | 78 | - [The project this is forked from](https://github.com/suconakh/awesome-awesome-rc). 79 | - [Janleigh](https://github.com/janleigh), massive design reference and colorscheme. 80 | - [Crylia](https://github.com/crylia/crylia-theme), code reference and some snippets and functions yoinked. 81 | - [rxyhn](https://github.com/rxyhn/yoru), same as Crylia. 82 | - [Stardust-kyun](https://github.com/stardust-kyun), also reference for a few widgets. 83 | - [Aproxia](https://github.com/aproxia-dev), came up with the notification timeout thingy. 84 | - [Blyaticon](https://git.gemia.net/paul.s/homedots/), Cairo image cropping helper. 85 | - [ChadCat](https://github.com/chadcat7/crystal/blob/the-awesome-config), weather signal. 86 | - [Feather Icons](https://github.com/feathericons/feather), some of their icons are used here. 87 | 88 | ## Thank you for actually taking the time to read this. 89 | 90 | Seriously, it seems like it's extremely hard to get people to read the document shoved into 91 | their face that explains everything relevant about the project. 92 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/rubato"] 2 | path = modules/rubato 3 | url = https://github.com/andOrlando/rubato.git 4 | -------------------------------------------------------------------------------- /bindings/client/key.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | 3 | local mod = require('bindings.mod') 4 | 5 | client.connect_signal('request::default_keybindings', function() 6 | awful.keyboard.append_client_keybindings { 7 | awful.key { 8 | modifiers = { mod.super }, 9 | key = 'f', 10 | description = 'toggle fullscreen', 11 | group = 'client', 12 | on_press = function(c) 13 | c.fullscreen = not c.fullscreen 14 | c:raise() 15 | end 16 | }, 17 | awful.key { 18 | modifiers = { mod.super }, 19 | key = 'q', 20 | description = 'close', 21 | group = 'client', 22 | on_press = function(c) c:kill() end 23 | }, 24 | awful.key { 25 | modifiers = { mod.super, mod.ctrl }, 26 | key = 'space', 27 | description = 'toggle floating', 28 | group = 'client', 29 | on_press = awful.client.floating.toggle 30 | }, 31 | awful.key { 32 | modifiers = { mod.super, mod.alt }, 33 | key = 'space', 34 | description = 'toggle sticky', 35 | group = 'client', 36 | on_press = function(c) 37 | c.sticky = not c.sticky 38 | end 39 | }, 40 | awful.key { 41 | modifiers = { mod.super, mod.ctrl }, 42 | key = 'Return', 43 | description = 'move to master', 44 | group = 'client', 45 | on_press = function(c) c:swap(awful.client.getmaster()) end 46 | }, 47 | awful.key { 48 | modifiers = { mod.super }, 49 | key = 'o', 50 | description = 'move to screen', 51 | group = 'client', 52 | on_press = function(c) c:move_to_screen() end 53 | }, 54 | awful.key { 55 | modifiers = { mod.super }, 56 | key = 't', 57 | description = 'toggle keep on top', 58 | group = 'client', 59 | on_press = function(c) c.ontop = not c.ontop end 60 | }, 61 | awful.key { 62 | modifiers = { mod.super }, 63 | key = 'n', 64 | description = 'minimize', 65 | group = 'client', 66 | on_press = function(c) c.minimized = true end 67 | }, 68 | awful.key { 69 | modifiers = { mod.super }, 70 | key = 'm', 71 | description = '(un)maximize', 72 | group = 'client', 73 | on_press = function(c) 74 | c.maximized = not c.maximized 75 | c:raise() 76 | end 77 | }, 78 | awful.key { 79 | modifiers = { mod.super, mod.ctrl }, 80 | key = 'm', 81 | description = '(un)maximize vertically', 82 | group = 'client', 83 | on_press = function(c) 84 | c.maximized_vertical = not c.maximized_vertical 85 | c:raise() 86 | end 87 | }, 88 | awful.key { 89 | modifiers = { mod.super, mod.shift }, 90 | key = 'm', 91 | description = '(un)maximize horizontally', 92 | group = 'client', 93 | on_press = function(c) 94 | c.maximized_horizontal = not c.maximized_horizontal 95 | c:raise() 96 | end 97 | } 98 | } 99 | end) 100 | -------------------------------------------------------------------------------- /bindings/client/mouse.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | 3 | local mod = require('bindings.mod') 4 | 5 | client.connect_signal('request::default_mousebindings', function() 6 | awful.mouse.append_client_mousebindings({ 7 | awful.button{ 8 | modifiers = {}, 9 | button = 1, 10 | on_press = function(c) c:activate({ context = 'mouse_click' }) end 11 | }, 12 | awful.button{ 13 | modifiers = { mod.super }, 14 | button = 1, 15 | on_press = function(c) c:activate({ context = 'mouse_click', action = 'mouse_move' }) end 16 | }, 17 | awful.button{ 18 | modifiers = { mod.super }, 19 | button = 3, 20 | on_press = function(c) c:activate({ context = 'mouse_click', action = 'mouse_resize' }) end 21 | } 22 | }) 23 | end) 24 | -------------------------------------------------------------------------------- /bindings/global/key.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local hotkeys_popup = require('awful.hotkeys_popup') 3 | require('awful.hotkeys_popup.keys') 4 | local menubar = require('menubar') 5 | 6 | local apps = require('config.apps') 7 | local mod = require('bindings.mod') 8 | local widgets = require('widgets') 9 | local screenshot = require('script.screenshot') 10 | 11 | menubar.utils.terminal = apps.terminal 12 | 13 | -- general awesome keys 14 | awful.keyboard.append_global_keybindings { 15 | awful.key { 16 | modifiers = { mod.super }, 17 | key = 's', 18 | description = 'show help', 19 | group = 'awesome', 20 | on_press = hotkeys_popup.show_help 21 | }, 22 | awful.key { 23 | modifiers = { mod.super }, 24 | key = 'w', 25 | description = 'show main menu', 26 | group = 'awesome', 27 | on_press = function() widgets.menu.mainmenu:show() end 28 | }, 29 | awful.key { 30 | modifiers = { mod.super, mod.ctrl }, 31 | key = 'r', 32 | description = 'reload awesome', 33 | group = 'awesome', 34 | on_press = awesome.restart 35 | }, 36 | -- awful.key { 37 | -- modifiers = { mod.super, mod.shift }, 38 | -- key = 'q', 39 | -- description = 'quit awesome', 40 | -- group = 'awesome', 41 | -- on_press = awesome.quit 42 | -- }, 43 | awful.key { 44 | modifiers = { mod.super }, 45 | key = 'Return', 46 | description = 'open a terminal', 47 | group = 'launcher', 48 | on_press = function() awful.spawn(apps.terminal) end 49 | }, 50 | awful.key { 51 | modifiers = { mod.super }, 52 | key = 'p', 53 | description = 'show the menubar', 54 | group = 'launcher', 55 | on_press = function() menubar.show() end 56 | } 57 | } 58 | 59 | -- tags related keybindings 60 | awful.keyboard.append_global_keybindings { 61 | awful.key { 62 | modifiers = { mod.super }, 63 | key = 'Left', 64 | description = 'view preivous', 65 | group = 'tag', 66 | on_press = awful.tag.viewprev 67 | }, 68 | awful.key { 69 | modifiers = { mod.super }, 70 | key = 'Right', 71 | description = 'view next', 72 | group = 'tag', 73 | on_press = awful.tag.viewnext 74 | }, 75 | awful.key { 76 | modifiers = { mod.super }, 77 | key = 'Escape', 78 | description = 'go back', 79 | group = 'tag', 80 | on_press = awful.tag.history.restore 81 | } 82 | } 83 | 84 | -- focus related keybindings 85 | awful.keyboard.append_global_keybindings { 86 | awful.key { 87 | modifiers = { mod.super }, 88 | key = 'j', 89 | description = 'focus next by index', 90 | group = 'client', 91 | on_press = function() awful.client.focus.byidx(1) end 92 | }, 93 | awful.key { 94 | modifiers = { mod.super }, 95 | key = 'k', 96 | description = 'focus previous by index', 97 | group = 'client', 98 | on_press = function() awful.client.focus.byidx(-1) end 99 | }, 100 | awful.key { 101 | modifiers = { mod.super }, 102 | key = 'Tab', 103 | description = 'go back', 104 | group = 'client', 105 | on_press = function() 106 | awful.client.focus.history.previous() 107 | if client.focus then 108 | client.focus:raise() 109 | end 110 | end 111 | }, 112 | awful.key { 113 | modifiers = { mod.super, mod.ctrl }, 114 | key = 'j', 115 | description = 'focus the next screen', 116 | group = 'screen', 117 | on_press = function() awful.screen.focus_relative(1) end 118 | }, 119 | awful.key { 120 | modifiers = { mod.super, mod.ctrl }, 121 | key = 'n', 122 | description = 'restore minimized', 123 | group = 'client', 124 | on_press = function() 125 | local c = awful.client.restore() 126 | if c then 127 | c:active { raise = true, context = 'key.unminimize' } 128 | end 129 | end 130 | } 131 | } 132 | 133 | -- layout related keybindings 134 | awful.keyboard.append_global_keybindings { 135 | awful.key { 136 | modifiers = { mod.super, mod.shift }, 137 | key = 'j', 138 | description = 'swap with next client by index', 139 | group = 'client', 140 | on_press = function() awful.client.swap.byidx(1) end 141 | }, 142 | awful.key { 143 | modifiers = { mod.super, mod.shift }, 144 | key = 'k', 145 | description = 'swap with previous client by index', 146 | group = 'client', 147 | on_press = function() awful.client.swap.byidx(-1) end 148 | }, 149 | awful.key { 150 | modifiers = { mod.super }, 151 | key = 'u', 152 | description = 'jump to urgent client', 153 | group = 'client', 154 | on_press = awful.client.urgent.jumpto 155 | }, 156 | awful.key { 157 | modifiers = { mod.super }, 158 | key = 'l', 159 | description = 'increase master width factor', 160 | group = 'layout', 161 | on_press = function() awful.tag.incmwfact(0.05) end 162 | }, 163 | awful.key { 164 | modifiers = { mod.super }, 165 | key = 'h', 166 | description = 'decrease master width factor', 167 | group = 'layout', 168 | on_press = function() awful.tag.incmwfact(-0.05) end 169 | }, 170 | awful.key { 171 | modifiers = { mod.super, mod.shift }, 172 | key = 'h', 173 | description = 'increase the number of master clients', 174 | group = 'layout', 175 | on_press = function() awful.tag.incnmaster(1, nil, true) end 176 | }, 177 | awful.key { 178 | modifiers = { mod.super, mod.shift }, 179 | key = 'l', 180 | description = 'decrease the number of master clients', 181 | group = 'layout', 182 | on_press = function() awful.tag.incnmaster(-1, nil, true) end 183 | }, 184 | awful.key { 185 | modifiers = { mod.super, mod.alt }, 186 | key = 'k', 187 | description = 'increase client width factor', 188 | group = 'layout', 189 | on_press = function() awful.client.incwfact(0.05) end 190 | }, 191 | awful.key { 192 | modifiers = { mod.super, mod.alt }, 193 | key = 'j', 194 | description = 'decrease client width factor', 195 | group = 'layout', 196 | on_press = function() awful.client.incwfact(-0.05) end 197 | }, 198 | awful.key { 199 | modifiers = { mod.super, mod.ctrl }, 200 | key = 'h', 201 | description = 'increase the number of columns', 202 | group = 'layout', 203 | on_press = function() awful.tag.incnmaster(1, nil, true) end 204 | }, 205 | awful.key { 206 | modifiers = { mod.super, mod.ctrl }, 207 | key = 'l', 208 | description = 'decrease the number of columns', 209 | group = 'layout', 210 | on_press = function() awful.tag.incnmaster(-1, nil, true) end 211 | }, 212 | awful.key { 213 | modifiers = { mod.super }, 214 | key = 'space', 215 | description = 'select next', 216 | group = 'layout', 217 | on_press = function() awful.layout.inc(1) end 218 | }, 219 | awful.key { 220 | modifiers = { mod.super, mod.shift }, 221 | key = 'space', 222 | description = 'select previous', 223 | group = 'layout', 224 | on_press = function() awful.layout.inc(-1) end 225 | } 226 | } 227 | 228 | awful.keyboard.append_global_keybindings { 229 | awful.key { 230 | modifiers = { mod.super }, 231 | keygroup = 'numrow', 232 | description = 'only view tag', 233 | group = 'tag', 234 | on_press = function(index) 235 | local screen = awful.screen.focused() 236 | local tag = screen.tags[index] 237 | if tag then 238 | tag:view_only(tag) 239 | end 240 | end 241 | }, 242 | awful.key { 243 | modifiers = { mod.super, mod.ctrl }, 244 | keygroup = 'numrow', 245 | description = 'toggle tag', 246 | group = 'tag', 247 | on_press = function(index) 248 | local screen = awful.screen.focused() 249 | local tag = screen.tags[index] 250 | if tag then 251 | awful.tag.viewtoggle(tag) 252 | end 253 | end 254 | }, 255 | awful.key { 256 | modifiers = { mod.super, mod.shift }, 257 | keygroup = 'numrow', 258 | description = 'move focused client to tag', 259 | group = 'tag', 260 | on_press = function(index) 261 | if client.focus then 262 | local tag = client.focus.screen.tags[index] 263 | if tag then 264 | client.focus:move_to_tag(tag) 265 | end 266 | end 267 | end 268 | }, 269 | awful.key { 270 | modifiers = { mod.super, mod.ctrl, mod.shift }, 271 | keygroup = 'numrow', 272 | description = 'toggle focused client on tag', 273 | group = 'tag', 274 | on_press = function(index) 275 | if client.focus then 276 | local tag = client.focus.screen.tags[index] 277 | if tag then 278 | client.focus:toggle_tag(tag) 279 | end 280 | end 281 | end 282 | }, 283 | awful.key { 284 | modifiers = { mod.super }, 285 | keygroup = 'numpad', 286 | description = 'select layout directrly', 287 | group = 'layout', 288 | on_press = function(index) 289 | local tag = awful.screen.focused().selected_tag 290 | if tag then 291 | tag.layout = tag.layouts[index] or tag.layout 292 | end 293 | end 294 | }, 295 | 296 | -- miscelaneous 297 | awful.key { 298 | modifiers = { mod.super }, 299 | key = 'Print', 300 | description = 'takes a screenshot', 301 | group = 'miscelaneous', 302 | on_press = screenshot.screen 303 | }, 304 | awful.key { 305 | modifiers = {}, 306 | key = 'Print', 307 | description = 'takes a selection screenshot', 308 | group = 'miscelaneous', 309 | on_press = screenshot.selection 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /bindings/global/mouse.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local widgets = require('widgets') 3 | 4 | awful.mouse.append_global_mousebindings { 5 | awful.button { 6 | modifiers = {}, 7 | button = 3, 8 | on_press = function() widgets.menu.mainmenu:toggle() end 9 | }, 10 | awful.button { 11 | modifiers = {}, 12 | button = 4, 13 | on_press = awful.tag.viewprev 14 | }, 15 | awful.button { 16 | modifiers = {}, 17 | button = 5, 18 | on_press = awful.tag.viewnext 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bindings/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | client = { 3 | key = require'bindings.client.key', 4 | mouse = require'bindings.client.mouse', 5 | }, 6 | global = { 7 | key = require'bindings.global.key', 8 | mouse = require'bindings.global.mouse', 9 | }, 10 | mod = require'bindings.mod' 11 | } 12 | 13 | -------------------------------------------------------------------------------- /bindings/mod.lua: -------------------------------------------------------------------------------- 1 | return { 2 | alt = 'Mod1', 3 | super = 'Mod4', 4 | shift = 'Shift', 5 | ctrl = 'Control', 6 | } 7 | -------------------------------------------------------------------------------- /bindings/widgets/layoutbox.lua: -------------------------------------------------------------------------------- 1 | local awful = require'awful' 2 | 3 | return { 4 | buttons = { 5 | awful.button{ 6 | modifiers = {}, 7 | button = 1, 8 | on_press = function() awful.layout.inc(1) end, 9 | }, 10 | awful.button{ 11 | modifiers = {}, 12 | button = 3, 13 | on_press = function() awful.layout.inc(-1) end, 14 | }, 15 | awful.button{ 16 | modifiers = {}, 17 | button = 4, 18 | on_press = function() awful.layout.inc(-1) end, 19 | }, 20 | awful.button{ 21 | modifiers = {}, 22 | button = 5, 23 | on_press = function() awful.layout.inc(1) end, 24 | }, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bindings/widgets/taglist.lua: -------------------------------------------------------------------------------- 1 | local awful = require'awful' 2 | 3 | local mod = require'bindings.mod' 4 | 5 | return { 6 | buttons = { 7 | awful.button{ 8 | modifiers = {}, 9 | button = 1, 10 | on_press = function(t) t:view_only() end, 11 | }, 12 | awful.button{ 13 | modifiers = {mod.super}, 14 | button = 1, 15 | on_press = function(t) 16 | if client.focus then 17 | client.focus:move_to_tag(t) 18 | end 19 | end, 20 | }, 21 | awful.button{ 22 | modifiers = {}, 23 | button = 3, 24 | on_press = awful.tag.viewtoggle, 25 | }, 26 | awful.button{ 27 | modifiers = {mod.super}, 28 | button = 3, 29 | on_press = function(t) 30 | if client.focus then 31 | client.focus:toggle_tag(t) 32 | end 33 | end 34 | }, 35 | awful.button{ 36 | modifiers = {}, 37 | button = 4, 38 | on_press = function(t) awful.tag.viewprev(t.screen) end, 39 | }, 40 | awful.button{ 41 | modifiers = {}, 42 | button = 5, 43 | on_press = function(t) awful.tag.viewnext(t.screen) end, 44 | }, 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /bindings/widgets/tasklist.lua: -------------------------------------------------------------------------------- 1 | local awful = require'awful' 2 | 3 | return { 4 | buttons = { 5 | awful.button{ 6 | modifiers = {}, 7 | button = 1, 8 | on_press = function(c) 9 | c:activate{context = 'tasklist', action = 'toggle_minimization'} 10 | end, 11 | }, 12 | awful.button{ 13 | modifiers = {}, 14 | button = 3, 15 | on_press = function() awful.menu.client_list{theme = {width = 250}} end, 16 | }, 17 | awful.button{ 18 | modifiers = {}, 19 | button = 4, 20 | on_press = function() awful.client.focus.byidx(-1) end 21 | }, 22 | awful.button{ 23 | modifiers = {}, 24 | button = 5, 25 | on_press = function() awful.client.focus.byidx(1) end 26 | }, 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /config/apps.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | _M.terminal = os.getenv('TERM') or 'xterm' 4 | _M.editor = os.getenv('EDITOR') or 'nano' 5 | _M.browser = os.getenv('BROWSER') or 'firefox' 6 | 7 | _M.editor_cmd = _M.editor 8 | _M.manual_cmd = _M.terminal .. ' -e man awesome' 9 | 10 | return _M 11 | -------------------------------------------------------------------------------- /config/auto.lua: -------------------------------------------------------------------------------- 1 | -- Programs to be executed on startup 2 | 3 | local awful = require('awful') 4 | local json = require('modules.json') 5 | 6 | local home = os.getenv('HOME') .. '/' 7 | local autostart_path = home .. '.local/share/awesome/autostart.json' 8 | 9 | local opts = {} 10 | 11 | local json_file = io.open(autostart_path, 'r') 12 | if json_file then 13 | opts = json.decode(assert(json_file):read()) 14 | json_file:close() 15 | else 16 | opts = { 17 | { cmd = 'notify-send "Welcome!"', mode = 'start' }, 18 | { cmd = 'notify-send "Test!" "This is a test startup item!"', mode = 'start' } 19 | } 20 | local autostart = assert(io.open(autostart_path, 'w')) 21 | autostart:write(json.encode(opts)) 22 | end 23 | 24 | for _, v in ipairs(opts) do 25 | -- Autostarts with no command are ignored. 26 | if v.cmd == nil then goto continue end 27 | -- Otherwise, autostarts with invalid mode are 'executed once'. 28 | if v.mode == 'shell' then 29 | awful.spawn.with_shell(v.cmd) 30 | elseif v.mode == 'reload' then 31 | awful.spawn(v.cmd) 32 | else 33 | awful.spawn.once(v.cmd) 34 | end 35 | ::continue:: 36 | end 37 | 38 | return opts 39 | -------------------------------------------------------------------------------- /config/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | apps = require('config.apps'), 3 | vars = require('config.vars'), 4 | user = require('config.user') 5 | } -------------------------------------------------------------------------------- /config/user.lua: -------------------------------------------------------------------------------- 1 | local json = require('modules.json') 2 | 3 | local home = os.getenv('HOME') .. '/' 4 | local config_path = home .. '.local/share/awesome/config.json' 5 | 6 | local user = {} 7 | 8 | local json_file = io.open(config_path, 'r') 9 | if json_file then 10 | user = json.decode(assert(json_file):read()) 11 | json_file:close() 12 | else 13 | -- Configuration now defaults to being read from `~/.local/share/awesome/config.json`. 14 | -- Only when that file doesn't exist is the following table read. 15 | user = { 16 | -- Variables 17 | gaps = 4, 18 | borders = 3, 19 | 20 | -- Supported colorschemes: 21 | -- adwaita, fullerene, gruvbox, janleigh 22 | colorscheme = 'fullerene', 23 | dark = true, 24 | 25 | -- Files 26 | icon_theme = 'Papirus-Dark', 27 | avatar = '', 28 | wallpaper = '', 29 | screenshot_dir = '', 30 | 31 | -- OpenWeather 32 | owkey = 'prettybighexadecimalnumber', 33 | coords = { 0, 0 } 34 | } 35 | -- And this default is written to that file. 36 | local config = assert(io.open(config_path, 'w')) 37 | config:write(json.encode(user)) 38 | end 39 | 40 | return user 41 | -------------------------------------------------------------------------------- /config/vars.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local awful = require('awful') 4 | 5 | _M.layouts = { 6 | awful.layout.suit.tile, 7 | awful.layout.suit.tile.left, 8 | awful.layout.suit.tile.bottom, 9 | -- awful.layout.suit.tile.top, 10 | -- awful.layout.suit.fair, 11 | -- awful.layout.suit.fair.horizontal, 12 | -- awful.layout.suit.spiral, 13 | -- awful.layout.suit.spiral.dwindle, 14 | -- awful.layout.suit.max, 15 | -- awful.layout.suit.max.fullscreen, 16 | -- awful.layout.suit.magnifier, 17 | -- awful.layout.suit.corner.nw, 18 | awful.layout.suit.floating 19 | } 20 | 21 | -- Tags respecting _M.layouts, another FLOATING tag will 22 | -- be appended to the end of the list. 23 | _M.tags = { '1', '2', '3', '4', '5', '6', '7' } 24 | 25 | return _M 26 | -------------------------------------------------------------------------------- /helpers.lua: -------------------------------------------------------------------------------- 1 | -- Useful functions used throughout the configuration 2 | 3 | local gears = require('gears') 4 | local cairo = require('lgi').cairo 5 | 6 | local _F = {} 7 | 8 | -- Blyaticon's image cropping function, uses a cairo surface which it crops to a ratio. 9 | -- https://git.gemia.net/paul.s/homedots/-/blob/main/awesome/helpers.lua#L133 10 | function _F.crop_surface(ratio, surf) 11 | local old_w, old_h = gears.surface.get_size(surf) 12 | local old_ratio = old_w / old_h 13 | if old_ratio == ratio then return surf end 14 | 15 | local new_w = old_w 16 | local new_h = old_h 17 | local offset_w, offset_h = 0, 0 18 | -- quick mafs 19 | if (old_ratio < ratio) then 20 | new_h = math.ceil(old_w * (1 / ratio)) 21 | offset_h = math.ceil((old_h - new_h) / 2) 22 | else 23 | new_w = math.ceil(old_h * ratio) 24 | offset_w = math.ceil((old_w - new_w) / 2) 25 | end 26 | 27 | local out_surf = cairo.ImageSurface(cairo.Format.ARGB32, new_w, new_h) 28 | local cr = cairo.Context(out_surf) 29 | cr:set_source_surface(surf, -offset_w, -offset_h) 30 | cr.operator = cairo.Operator.SOURCE 31 | cr:paint() 32 | 33 | return out_surf 34 | end 35 | 36 | -- Crylia's app icon fetching function. Scans `/usr/share/icons` for application 37 | -- icons of a set theme and application. Otherwise defaults to Papirus. Requires 38 | -- `/usr/share/icons/Papirus-Dark` to exist to work as intended. 39 | -- https://github.com/Crylia/crylia-theme/blob/main/awesome/src/tools/icon_handler.lua 40 | local icon_cache = {} 41 | -- Define a default icon. 42 | _F.DEFAULT_ICON = '/usr/share/icons/Papirus-Dark/128x128/apps/application-default-icon.svg' 43 | function _F.get_icon(theme, client, program_string, class_string) 44 | client = client or nil 45 | program_string = program_string or nil 46 | class_string = class_string or nil 47 | 48 | if theme and (client or program_string or class_string) then 49 | local clientName 50 | if client then 51 | if client.class then 52 | clientName = string.lower(client.class:gsub(" ", "")) .. ".svg" 53 | elseif client.name then 54 | clientName = string.lower(client.name:gsub(" ", "")) .. ".svg" 55 | else 56 | if client.icon then 57 | return client.icon 58 | else 59 | return _F.DEFAULT_ICON 60 | end 61 | end 62 | else 63 | if program_string then 64 | clientName = program_string .. ".svg" 65 | else 66 | clientName = class_string .. ".svg" 67 | end 68 | end 69 | 70 | for _, icon in ipairs(icon_cache) do 71 | if icon:match(clientName) then 72 | return icon 73 | end 74 | end 75 | 76 | local resolutions = { 77 | -- This is the format Papirus follows. 78 | "128x128", "96x96", "64x64", "48x48", "42x42", "32x32", "24x24", "16x16", 79 | -- But some themes actually do this. 80 | '128', '96', '64', '48', '42', '32', '24', '16' 81 | } 82 | for _, res in ipairs(resolutions) do 83 | local iconDir = "/usr/share/icons/" .. theme .. "/" .. res .. "/apps/" 84 | local ioStream = io.open(iconDir .. clientName, "r") 85 | if ioStream ~= nil then 86 | icon_cache[#icon_cache + 1] = iconDir .. clientName 87 | return iconDir .. clientName 88 | else 89 | clientName = clientName:gsub("^%l", string.upper) 90 | iconDir = "/usr/share/icons/" .. theme .. "/" .. res .. "/apps/" 91 | ioStream = io.open(iconDir .. clientName, "r") 92 | if ioStream ~= nil then 93 | icon_cache[#icon_cache + 1] = iconDir .. clientName 94 | return iconDir .. clientName 95 | elseif not class_string then 96 | return _F.DEFAULT_ICON 97 | else 98 | clientName = class_string .. ".svg" 99 | iconDir = "/usr/share/icons/" .. theme .. "/" .. res .. "/apps/" 100 | ioStream = io.open(iconDir .. clientName, "r") 101 | if ioStream ~= nil then 102 | icon_cache[#icon_cache + 1] = iconDir .. clientName 103 | return iconDir .. clientName 104 | else 105 | return _F.DEFAULT_ICON 106 | end 107 | end 108 | end 109 | end 110 | if client then 111 | return _F.DEFAULT_ICON 112 | end 113 | end 114 | end 115 | 116 | function _F.in_table(t, v) 117 | for _, value in ipairs(t) do 118 | if value == v then return true end 119 | end 120 | return false 121 | end 122 | 123 | return _F 124 | -------------------------------------------------------------------------------- /modules/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2020 rxi 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | -- this software and associated documentation files (the "Software"), to deal in 8 | -- the Software without restriction, including without limitation the rights to 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | -- of the Software, and to permit persons to whom the Software is furnished to do 11 | -- so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all 14 | -- copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | -- SOFTWARE. 23 | -- 24 | 25 | local json = { _version = "0.1.2" } 26 | 27 | ------------------------------------------------------------------------------- 28 | -- Encode 29 | ------------------------------------------------------------------------------- 30 | 31 | local encode 32 | 33 | local escape_char_map = { 34 | ["\\"] = "\\", 35 | ["\""] = "\"", 36 | ["\b"] = "b", 37 | ["\f"] = "f", 38 | ["\n"] = "n", 39 | ["\r"] = "r", 40 | ["\t"] = "t", 41 | } 42 | 43 | local escape_char_map_inv = { ["/"] = "/" } 44 | for k, v in pairs(escape_char_map) do 45 | escape_char_map_inv[v] = k 46 | end 47 | 48 | 49 | local function escape_char(c) 50 | return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) 51 | end 52 | 53 | 54 | local function encode_nil(val) 55 | return "null" 56 | end 57 | 58 | 59 | local function encode_table(val, stack) 60 | local res = {} 61 | stack = stack or {} 62 | 63 | -- Circular reference? 64 | if stack[val] then error("circular reference") end 65 | 66 | stack[val] = true 67 | 68 | if rawget(val, 1) ~= nil or next(val) == nil then 69 | -- Treat as array -- check keys are valid and it is not sparse 70 | local n = 0 71 | for k in pairs(val) do 72 | if type(k) ~= "number" then 73 | error("invalid table: mixed or invalid key types") 74 | end 75 | n = n + 1 76 | end 77 | if n ~= #val then 78 | error("invalid table: sparse array") 79 | end 80 | -- Encode 81 | for i, v in ipairs(val) do 82 | table.insert(res, encode(v, stack)) 83 | end 84 | stack[val] = nil 85 | return "[" .. table.concat(res, ",") .. "]" 86 | else 87 | -- Treat as an object 88 | for k, v in pairs(val) do 89 | if type(k) ~= "string" then 90 | error("invalid table: mixed or invalid key types") 91 | end 92 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 93 | end 94 | stack[val] = nil 95 | return "{" .. table.concat(res, ",") .. "}" 96 | end 97 | end 98 | 99 | 100 | local function encode_string(val) 101 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 102 | end 103 | 104 | 105 | local function encode_number(val) 106 | -- Check for NaN, -inf and inf 107 | if val ~= val or val <= -math.huge or val >= math.huge then 108 | error("unexpected number value '" .. tostring(val) .. "'") 109 | end 110 | return string.format("%.14g", val) 111 | end 112 | 113 | 114 | local type_func_map = { 115 | ["nil"] = encode_nil, 116 | ["table"] = encode_table, 117 | ["string"] = encode_string, 118 | ["number"] = encode_number, 119 | ["boolean"] = tostring, 120 | } 121 | 122 | 123 | encode = function(val, stack) 124 | local t = type(val) 125 | local f = type_func_map[t] 126 | if f then 127 | return f(val, stack) 128 | end 129 | error("unexpected type '" .. t .. "'") 130 | end 131 | 132 | 133 | function json.encode(val) 134 | return (encode(val)) 135 | end 136 | 137 | ------------------------------------------------------------------------------- 138 | -- Decode 139 | ------------------------------------------------------------------------------- 140 | 141 | local parse 142 | 143 | local function create_set(...) 144 | local res = {} 145 | for i = 1, select("#", ...) do 146 | res[select(i, ...)] = true 147 | end 148 | return res 149 | end 150 | 151 | local space_chars = create_set(" ", "\t", "\r", "\n") 152 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 153 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 154 | local literals = create_set("true", "false", "null") 155 | 156 | local literal_map = { 157 | ["true"] = true, 158 | ["false"] = false, 159 | ["null"] = nil, 160 | } 161 | 162 | 163 | local function next_char(str, idx, set, negate) 164 | for i = idx, #str do 165 | if set[str:sub(i, i)] ~= negate then 166 | return i 167 | end 168 | end 169 | return #str + 1 170 | end 171 | 172 | 173 | local function decode_error(str, idx, msg) 174 | local line_count = 1 175 | local col_count = 1 176 | for i = 1, idx - 1 do 177 | col_count = col_count + 1 178 | if str:sub(i, i) == "\n" then 179 | line_count = line_count + 1 180 | col_count = 1 181 | end 182 | end 183 | error(string.format("%s at line %d col %d", msg, line_count, col_count)) 184 | end 185 | 186 | 187 | local function codepoint_to_utf8(n) 188 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 189 | local f = math.floor 190 | if n <= 0x7f then 191 | return string.char(n) 192 | elseif n <= 0x7ff then 193 | return string.char(f(n / 64) + 192, n % 64 + 128) 194 | elseif n <= 0xffff then 195 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 196 | elseif n <= 0x10ffff then 197 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 198 | f(n % 4096 / 64) + 128, n % 64 + 128) 199 | end 200 | error(string.format("invalid unicode codepoint '%x'", n)) 201 | end 202 | 203 | 204 | local function parse_unicode_escape(s) 205 | local n1 = tonumber(s:sub(1, 4), 16) 206 | local n2 = tonumber(s:sub(7, 10), 16) 207 | -- Surrogate pair? 208 | if n2 then 209 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 210 | else 211 | return codepoint_to_utf8(n1) 212 | end 213 | end 214 | 215 | 216 | local function parse_string(str, i) 217 | local res = "" 218 | local j = i + 1 219 | local k = j 220 | 221 | while j <= #str do 222 | local x = str:byte(j) 223 | 224 | if x < 32 then 225 | decode_error(str, j, "control character in string") 226 | elseif x == 92 then -- `\`: Escape 227 | res = res .. str:sub(k, j - 1) 228 | j = j + 1 229 | local c = str:sub(j, j) 230 | if c == "u" then 231 | local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) 232 | or str:match("^%x%x%x%x", j + 1) 233 | or decode_error(str, j - 1, "invalid unicode escape in string") 234 | res = res .. parse_unicode_escape(hex) 235 | j = j + #hex 236 | else 237 | if not escape_chars[c] then 238 | decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") 239 | end 240 | res = res .. escape_char_map_inv[c] 241 | end 242 | k = j + 1 243 | elseif x == 34 then -- `"`: End of string 244 | res = res .. str:sub(k, j - 1) 245 | return res, j + 1 246 | end 247 | 248 | j = j + 1 249 | end 250 | 251 | decode_error(str, i, "expected closing quote for string") 252 | end 253 | 254 | 255 | local function parse_number(str, i) 256 | local x = next_char(str, i, delim_chars) 257 | local s = str:sub(i, x - 1) 258 | local n = tonumber(s) 259 | if not n then 260 | decode_error(str, i, "invalid number '" .. s .. "'") 261 | end 262 | return n, x 263 | end 264 | 265 | 266 | local function parse_literal(str, i) 267 | local x = next_char(str, i, delim_chars) 268 | local word = str:sub(i, x - 1) 269 | if not literals[word] then 270 | decode_error(str, i, "invalid literal '" .. word .. "'") 271 | end 272 | return literal_map[word], x 273 | end 274 | 275 | 276 | local function parse_array(str, i) 277 | local res = {} 278 | local n = 1 279 | i = i + 1 280 | while 1 do 281 | local x 282 | i = next_char(str, i, space_chars, true) 283 | -- Empty / end of array? 284 | if str:sub(i, i) == "]" then 285 | i = i + 1 286 | break 287 | end 288 | -- Read token 289 | x, i = parse(str, i) 290 | res[n] = x 291 | n = n + 1 292 | -- Next token 293 | i = next_char(str, i, space_chars, true) 294 | local chr = str:sub(i, i) 295 | i = i + 1 296 | if chr == "]" then break end 297 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 298 | end 299 | return res, i 300 | end 301 | 302 | 303 | local function parse_object(str, i) 304 | local res = {} 305 | i = i + 1 306 | while 1 do 307 | local key, val 308 | i = next_char(str, i, space_chars, true) 309 | -- Empty / end of object? 310 | if str:sub(i, i) == "}" then 311 | i = i + 1 312 | break 313 | end 314 | -- Read key 315 | if str:sub(i, i) ~= '"' then 316 | decode_error(str, i, "expected string for key") 317 | end 318 | key, i = parse(str, i) 319 | -- Read ':' delimiter 320 | i = next_char(str, i, space_chars, true) 321 | if str:sub(i, i) ~= ":" then 322 | decode_error(str, i, "expected ':' after key") 323 | end 324 | i = next_char(str, i + 1, space_chars, true) 325 | -- Read value 326 | val, i = parse(str, i) 327 | -- Set 328 | res[key] = val 329 | -- Next token 330 | i = next_char(str, i, space_chars, true) 331 | local chr = str:sub(i, i) 332 | i = i + 1 333 | if chr == "}" then break end 334 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 335 | end 336 | return res, i 337 | end 338 | 339 | 340 | local char_func_map = { 341 | ['"'] = parse_string, 342 | ["0"] = parse_number, 343 | ["1"] = parse_number, 344 | ["2"] = parse_number, 345 | ["3"] = parse_number, 346 | ["4"] = parse_number, 347 | ["5"] = parse_number, 348 | ["6"] = parse_number, 349 | ["7"] = parse_number, 350 | ["8"] = parse_number, 351 | ["9"] = parse_number, 352 | ["-"] = parse_number, 353 | ["t"] = parse_literal, 354 | ["f"] = parse_literal, 355 | ["n"] = parse_literal, 356 | ["["] = parse_array, 357 | ["{"] = parse_object, 358 | } 359 | 360 | 361 | parse = function(str, idx) 362 | local chr = str:sub(idx, idx) 363 | local f = char_func_map[chr] 364 | if f then 365 | return f(str, idx) 366 | end 367 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 368 | end 369 | 370 | 371 | function json.decode(str) 372 | if type(str) ~= "string" then 373 | error("expected argument of type string, got " .. type(str)) 374 | end 375 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) 376 | idx = next_char(str, idx, space_chars, true) 377 | if idx <= #str then 378 | decode_error(str, idx, "trailing garbage") 379 | end 380 | return res 381 | end 382 | 383 | return json 384 | 385 | -------------------------------------------------------------------------------- /modules/overflow.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------- 2 | -- A layout that allows its children to take more space than what's available 3 | -- in the surrounding container. If the content does exceed the available 4 | -- size, a scrollbar is added and scrolling behavior enabled. 5 | -- 6 | --@DOC_wibox_layout_defaults_overflow_EXAMPLE@ 7 | -- @author Lucas Schwiderski 8 | -- @copyright 2021 Lucas Schwiderski 9 | -- @layoutmod wibox.layout.overflow 10 | -- @supermodule wibox.layout.fixed 11 | --------------------------------------------------------------------------- 12 | 13 | local base = require('wibox.widget.base') 14 | local fixed = require('wibox.layout.fixed') 15 | local separator = require('wibox.widget.separator') 16 | local gtable = require('gears.table') 17 | local gshape = require('gears.shape') 18 | local gobject = require('gears.object') 19 | local mousegrabber = mousegrabber 20 | 21 | local overflow = { mt = {} } 22 | 23 | -- Determine the required space to draw the layout's children and, if necessary, 24 | -- the scrollbar. 25 | function overflow:fit(context, orig_width, orig_height) 26 | local widgets = self._private.widgets 27 | local num_widgets = #widgets 28 | if num_widgets < 1 then 29 | return 0, 0 30 | end 31 | 32 | local width, height = orig_width, orig_height 33 | local scrollbar_width = self._private.scrollbar_width 34 | local scrollbar_enabled = self._private.scrollbar_enabled 35 | local used_in_dir, used_max = 0, 0 36 | local is_y = self._private.dir == "y" 37 | local avail_in_dir = is_y and orig_height or orig_width 38 | 39 | -- Set the direction covered by scrolling to the maximum value 40 | -- to allow widgets to take as much space as they want. 41 | if is_y then 42 | height = math.huge 43 | else 44 | width = math.huge 45 | end 46 | 47 | -- First, determine widget sizes. 48 | -- Only when the content doesn't fit and needs scrolling should 49 | -- we reduce content size to make space for a scrollbar. 50 | for _, widget in ipairs(widgets) do 51 | local w, h = base.fit_widget(self, context, widget, width, height) 52 | 53 | if is_y then 54 | used_max = math.max(used_max, w) 55 | used_in_dir = used_in_dir + h 56 | else 57 | used_max = math.max(used_max, h) 58 | used_in_dir = used_in_dir + w 59 | end 60 | end 61 | 62 | local spacing = self._private.spacing * (num_widgets - 1) 63 | used_in_dir = used_in_dir + spacing 64 | 65 | local need_scrollbar = scrollbar_enabled and used_in_dir > avail_in_dir 66 | 67 | -- Even if `used_max == orig_(width|height)` already, `base.fit_widget` 68 | -- will clamp return values, so we can "overextend" here. 69 | if need_scrollbar then 70 | used_max = used_max + scrollbar_width 71 | end 72 | 73 | if is_y then 74 | return used_max, used_in_dir 75 | else 76 | return used_in_dir, used_max 77 | end 78 | end 79 | 80 | -- Layout children, scrollbar and spacing widgets. 81 | -- Only those widgets that are currently visible will be placed. 82 | function overflow:layout(context, orig_width, orig_height) 83 | local result = {} 84 | local is_y = self._private.dir == "y" 85 | local widgets = self._private.widgets 86 | local avail_in_dir = is_y and orig_height or orig_width 87 | local scrollbar_width = self._private.scrollbar_width 88 | local scrollbar_enabled = self._private.scrollbar_enabled 89 | local scrollbar_position = self._private.scrollbar_position 90 | local width, height = orig_width, orig_height 91 | local widget_x, widget_y = 0, 0 92 | local used_in_dir, used_max = 0, 0 93 | 94 | -- Set the direction covered by scrolling to the maximum value 95 | -- to allow widgets to take as much space as they want. 96 | if is_y then 97 | height = math.huge 98 | else 99 | width = math.huge 100 | end 101 | 102 | -- First, determine widget sizes. 103 | -- Only when the content doesn't fit and needs scrolling should 104 | -- we reduce content size to make space for a scrollbar. 105 | for _, widget in pairs(widgets) do 106 | local w, h = base.fit_widget(self, context, widget, width, height) 107 | 108 | if is_y then 109 | used_max = math.max(used_max, w) 110 | used_in_dir = used_in_dir + h 111 | else 112 | used_max = math.max(used_max, h) 113 | used_in_dir = used_in_dir + w 114 | end 115 | end 116 | 117 | used_in_dir = used_in_dir + self._private.spacing * (#widgets-1) 118 | 119 | -- Save size for scrolling behavior 120 | self._private.avail_in_dir = avail_in_dir 121 | self._private.used_in_dir = used_in_dir 122 | 123 | local need_scrollbar = used_in_dir > avail_in_dir and scrollbar_enabled 124 | 125 | local scroll_position = self._private.scroll_factor 126 | 127 | if need_scrollbar then 128 | local scrollbar_widget = self._private.scrollbar_widget 129 | local bar_x, bar_y = 0, 0 130 | local bar_w, bar_h 131 | -- The percentage of how much of the content can be visible within 132 | -- the available space 133 | local visible_percent = avail_in_dir / used_in_dir 134 | -- Make scrollbar length reflect `visible_percent` 135 | -- TODO: Apply a default minimum length 136 | local bar_length = math.floor(visible_percent * avail_in_dir) 137 | local bar_pos = (avail_in_dir - bar_length) * self._private.scroll_factor 138 | 139 | if is_y then 140 | bar_w, bar_h = base.fit_widget(self, context, scrollbar_widget, scrollbar_width, bar_length) 141 | bar_y = bar_pos 142 | 143 | if scrollbar_position == "left" then 144 | widget_x = widget_x + bar_w 145 | elseif scrollbar_position == "right" then 146 | bar_x = orig_width - bar_w 147 | end 148 | 149 | self._private.bar_length = bar_h 150 | 151 | width = width - bar_w 152 | else 153 | bar_w, bar_h = base.fit_widget(self, context, scrollbar_widget, bar_length, scrollbar_width) 154 | bar_x = bar_pos 155 | 156 | if scrollbar_position == "top" then 157 | widget_y = widget_y + bar_h 158 | elseif scrollbar_position == "bottom" then 159 | bar_y = orig_height - bar_h 160 | end 161 | 162 | self._private.bar_length = bar_w 163 | 164 | height = height - bar_h 165 | end 166 | 167 | table.insert(result, base.place_widget_at( 168 | scrollbar_widget, 169 | math.floor(bar_x), 170 | math.floor(bar_y), 171 | math.floor(bar_w), 172 | math.floor(bar_h) 173 | )) 174 | end 175 | 176 | local pos, spacing = 0, self._private.spacing 177 | local interval = used_in_dir - avail_in_dir 178 | 179 | local spacing_widget = self._private.spacing_widget 180 | if spacing_widget then 181 | if is_y then 182 | local _ 183 | _, spacing = base.fit_widget(self, context, spacing_widget, width, spacing) 184 | else 185 | spacing = base.fit_widget(self, context, spacing_widget, spacing, height) 186 | end 187 | end 188 | 189 | for i, w in ipairs(widgets) do 190 | local content_x, content_y 191 | local content_w, content_h = base.fit_widget(self, context, w, width, height) 192 | 193 | -- When scrolling down, the content itself moves up -> substract 194 | local scrolled_pos = pos - (scroll_position * interval) 195 | 196 | -- Stop processing completely once we're passed the visible portion 197 | if scrolled_pos > avail_in_dir then 198 | break 199 | end 200 | 201 | if is_y then 202 | content_x, content_y = widget_x, scrolled_pos 203 | pos = pos + content_h + spacing 204 | 205 | if self._private.fill_space then 206 | content_w = width 207 | end 208 | else 209 | content_x, content_y = scrolled_pos, widget_y 210 | pos = pos + content_w + spacing 211 | 212 | if self._private.fill_space then 213 | content_h = height 214 | end 215 | end 216 | 217 | local is_in_view = is_y 218 | and (scrolled_pos + content_h > 0) 219 | or (scrolled_pos + content_w > 0) 220 | 221 | if is_in_view then 222 | -- Add the spacing widget, but not before the first widget 223 | if i > 1 and spacing_widget then 224 | table.insert(result, base.place_widget_at( 225 | spacing_widget, 226 | -- The way how spacing is added for regular widgets 227 | -- and the `spacing_widget` is disconnected: 228 | -- The offset for regular widgets is added to `pos` one 229 | -- iteration _before_ the one where the widget is actually 230 | -- placed. 231 | -- Because of that, the placement for the spacing widget 232 | -- needs to substract that offset to be placed right after 233 | -- the previous regular widget. 234 | math.floor(is_y and content_x or (content_x - spacing)), 235 | math.floor(is_y and (content_y - spacing) or content_y), 236 | math.floor(is_y and content_w or spacing), 237 | math.floor(is_y and spacing or content_h) 238 | )) 239 | end 240 | 241 | table.insert(result, base.place_widget_at( 242 | w, 243 | math.floor(content_x), 244 | math.floor(content_y), 245 | math.floor(content_w), 246 | math.floor(content_h) 247 | )) 248 | end 249 | end 250 | 251 | return result 252 | end 253 | 254 | function overflow:before_draw_children(_, cr, width, height) 255 | -- Clip drawing for children to the space we're allowed to draw in 256 | cr:rectangle(0, 0, width, height) 257 | cr:clip() 258 | end 259 | 260 | 261 | --- The amount of units to advance per scroll event. 262 | -- 263 | -- This affects calls to `scroll` and the default mouse wheel handler. 264 | -- 265 | -- The default is `10`. 266 | -- 267 | -- @property step 268 | -- @tparam number step The step size. 269 | function overflow:set_step(step) 270 | self._private.step = step 271 | -- We don't need to emit enything here, since changing step only really 272 | -- takes effect the next time the user scrolls 273 | end 274 | 275 | 276 | --- Scroll the layout's content by `amount * step`. 277 | -- 278 | -- A positive amount scroll down/right, a negative amount scrolls up/left. 279 | -- 280 | -- The amount of units scrolled is affected by `step`. 281 | -- 282 | -- @method overflow:scroll 283 | -- @tparam number amount The amount to scroll by. 284 | -- @emits property::overflow::scroll_factor 285 | -- @emitstparam property::overflow::scroll_factor number scroll_factor The new 286 | -- scroll factor. 287 | -- @emits widget::layout_changed 288 | -- @emits widget::redraw_needed 289 | function overflow:scroll(amount) 290 | if amount == 0 then 291 | return 292 | end 293 | local interval = self._private.used_in_dir 294 | local delta = self._private.step / interval 295 | 296 | local factor = self._private.scroll_factor + (delta * amount) 297 | self:set_scroll_factor(factor) 298 | end 299 | 300 | 301 | --- The scroll factor. 302 | -- 303 | -- The scroll factor represents how far the layout's content is currently 304 | -- scrolled. It is represented as a fraction from `0` to `1`, where `0` is the 305 | -- start of the content and `1` is the end. 306 | -- 307 | -- @property scroll_factor 308 | -- @tparam number scroll_factor The scroll factor. 309 | -- @propemits true false 310 | 311 | function overflow:set_scroll_factor(factor) 312 | local current = self._private.scroll_factor 313 | local interval = self._private.used_in_dir - self._private.avail_in_dir 314 | if current == factor 315 | -- the content takes less space than what is available, i.e. everything 316 | -- is already visible 317 | or interval <= 0 318 | -- the scroll factor is out of range 319 | or (current <= 0 and factor < 0) 320 | or (current >= 1 and factor > 1) then 321 | return 322 | end 323 | 324 | self._private.scroll_factor = math.min(1, math.max(factor, 0)) 325 | 326 | self:emit_signal("widget::layout_changed") 327 | self:emit_signal("property::scroll_factor", factor) 328 | end 329 | 330 | function overflow:get_scroll_factor() 331 | return self._private.scroll_factor 332 | end 333 | 334 | 335 | --- The scrollbar width. 336 | -- 337 | -- For horizontal scrollbars, this is the scrollbar height 338 | -- 339 | -- The default is `5`. 340 | -- 341 | --@DOC_wibox_layout_overflow_scrollbar_width_EXAMPLE@ 342 | -- 343 | -- @property scrollbar_width 344 | -- @tparam number scrollbar_width The scrollbar width. 345 | -- @propemits true false 346 | 347 | function overflow:set_scrollbar_width(width) 348 | if self._private.scrollbar_width == width then 349 | return 350 | end 351 | 352 | self._private.scrollbar_width = width 353 | 354 | self:emit_signal("widget::layout_changed") 355 | self:emit_signal("property::scrollbar_width", width) 356 | end 357 | 358 | 359 | --- The scrollbar position. 360 | -- 361 | -- For horizontal scrollbars, this can be `"top"` or `"bottom"`, 362 | -- for vertical scrollbars this can be `"left"` or `"right"`. 363 | -- The default is `"right"`/`"bottom"`. 364 | -- 365 | --@DOC_wibox_layout_overflow_scrollbar_position_EXAMPLE@ 366 | -- 367 | -- @property scrollbar_position 368 | -- @tparam string scrollbar_position The scrollbar position. 369 | -- @propemits true false 370 | 371 | function overflow:set_scrollbar_position(position) 372 | if self._private.scrollbar_position == position then 373 | return 374 | end 375 | 376 | self._private.scrollbar_position = position 377 | 378 | self:emit_signal("widget::layout_changed") 379 | self:emit_signal("property::scrollbar_position", position) 380 | end 381 | 382 | function overflow:get_scrollbar_position() 383 | return self._private.scrollbar_position 384 | end 385 | 386 | 387 | --- The scrollbar visibility. 388 | -- 389 | -- If this is set to `false`, no scrollbar will be rendered, even if the layout's 390 | -- content overflows. Mouse wheel scrolling will work regardless. 391 | -- 392 | -- The default is `true`. 393 | -- 394 | -- @property scrollbar_enabled 395 | -- @tparam boolean scrollbar_enabled The scrollbar visibility. 396 | -- @propemits true false 397 | 398 | function overflow:set_scrollbar_enabled(enabled) 399 | if self._private.scrollbar_enabled == enabled then 400 | return 401 | end 402 | 403 | self._private.scrollbar_enabled = enabled 404 | 405 | self:emit_signal("widget::layout_changed") 406 | self:emit_signal("property::scrollbar_enabled", enabled) 407 | end 408 | 409 | function overflow:get_scrollbar_enabled() 410 | return self._private.scrollbar_enabled 411 | end 412 | 413 | -- Wraps a callback function for `mousegrabber` that is capable of 414 | -- updating the scroll factor. 415 | local function build_grabber(container, initial_x, initial_y, geo) 416 | local is_y = container._private.dir == "y" 417 | local bar_interval = container._private.avail_in_dir - container._private.bar_length 418 | local start_pos = container._private.scroll_factor * bar_interval 419 | local start = is_y and initial_y or initial_x 420 | 421 | -- Calculate a matrix transforming from screen coordinates into widget 422 | -- coordinates. 423 | -- This is required for mouse movement to work when the widget has been 424 | -- transformed by something like `wibox.container.rotate`. 425 | local matrix_from_device = geo.hierarchy:get_matrix_from_device() 426 | local wgeo = geo.drawable.drawable:geometry() 427 | local matrix = matrix_from_device:translate(-wgeo.x, -wgeo.y) 428 | 429 | return function(mouse) 430 | if not mouse.buttons[1] then 431 | return false 432 | end 433 | 434 | local x, y = matrix:transform_point(mouse.x, mouse.y) 435 | local pos = is_y and x and y 436 | container:set_scroll_factor((start_pos + (pos - start)) / bar_interval) 437 | 438 | return true 439 | end 440 | end 441 | 442 | -- Applies a mouse button signal using `build_grabber` to a scrollbar widget. 443 | local function apply_scrollbar_mouse_signal(container, w) 444 | w:connect_signal('button::press', function(_, x, y, button_id, _, geo) 445 | if button_id ~= 1 then 446 | return 447 | end 448 | mousegrabber.run(build_grabber(container, x, y, geo), "fleur") 449 | end) 450 | end 451 | 452 | 453 | --- The scrollbar widget. 454 | -- This widget is rendered as the scrollbar element. 455 | -- 456 | -- The default is `wibox.widget.separator{ shape = gears.shape.rectangle }`. 457 | -- 458 | --@DOC_wibox_layout_overflow_scrollbar_widget_EXAMPLE@ 459 | -- 460 | -- @property scrollbar_widget 461 | -- @tparam widget scrollbar_widget The scrollbar widget. 462 | -- @propemits true false 463 | 464 | function overflow:set_scrollbar_widget(widget) 465 | local w = base.make_widget_from_value(widget) 466 | 467 | apply_scrollbar_mouse_signal(self, w) 468 | 469 | self._private.scrollbar_widget = w 470 | 471 | self:emit_signal("widget::layout_changed") 472 | self:emit_signal("property::scrollbar_widget", widget) 473 | end 474 | 475 | function overflow:get_scrollbar_widget() 476 | return self._private.scrollbar_widget 477 | end 478 | 479 | local function new(dir, ...) 480 | local ret = fixed[dir](...) 481 | 482 | gtable.crush(ret, overflow, true) 483 | ret.widget_name = gobject.modulename(2) 484 | -- Tell the widget system to prevent clicks outside the layout's extends 485 | -- to register with child widgets, even if they actually extend that far. 486 | -- This prevents triggering button presses on hidden/clipped widgets. 487 | ret.clip_child_extends = true 488 | 489 | -- Manually set the scroll factor here. We don't know the bounding size yet. 490 | ret._private.scroll_factor = 0 491 | 492 | -- Apply defaults. Bypass setters to avoid signals. 493 | ret._private.step = 10 494 | ret._private.fill_space = true 495 | ret._private.scrollbar_width = 5 496 | ret._private.scrollbar_enabled = true 497 | ret._private.scrollbar_position = dir == "vertical" and "right" or "bottom" 498 | 499 | local scrollbar_widget = separator({ shape = gshape.rectangle }) 500 | apply_scrollbar_mouse_signal(ret, scrollbar_widget) 501 | ret._private.scrollbar_widget = scrollbar_widget 502 | 503 | ret:connect_signal('button::press', function(self, _, _, button) 504 | if button == 4 then 505 | self:scroll(-1) 506 | elseif button == 5 then 507 | self:scroll(1) 508 | end 509 | end) 510 | 511 | return ret 512 | end 513 | 514 | 515 | --- Returns a new horizontal overflow layout. 516 | -- Child widgets are placed similar to `wibox.layout.fixed`, except that 517 | -- they may take as much width as they want. If the total width of all child 518 | -- widgets exceeds the width available whithin the layout's outer container 519 | -- a scrollbar will be added and scrolling behavior enabled. 520 | -- @tparam widget ... Widgets that should be added to the layout. 521 | -- @constructorfct wibox.layout.overflow.horizontal 522 | function overflow.horizontal(...) 523 | return new("horizontal", ...) 524 | end 525 | 526 | 527 | --- Returns a new vertical overflow layout. 528 | -- Child widgets are placed similar to `wibox.layout.fixed`, except that 529 | -- they may take as much height as they want. If the total height of all child 530 | -- widgets exceeds the height available whithin the layout's outer container 531 | -- a scrollbar will be added and scrolling behavior enabled. 532 | -- @tparam widget ... Widgets that should be added to the layout. 533 | -- @constructorfct wibox.layout.overflow.horizontal 534 | function overflow.vertical(...) 535 | return new("vertical", ...) 536 | end 537 | 538 | return setmetatable(overflow, overflow.mt) 539 | -------------------------------------------------------------------------------- /rc.lua: -------------------------------------------------------------------------------- 1 | -- awesome_mode: api-level=4:screen=on 2 | 3 | -- load luarocks if installed 4 | pcall(require, 'luarocks.loader') 5 | 6 | -- load theme 7 | require('theme') 8 | 9 | -- load key and mouse bindings 10 | require('bindings') 11 | 12 | -- load rules 13 | require('rules') 14 | 15 | -- load signals 16 | require('signals') 17 | 18 | -- load autoexecs 19 | require('config.auto') 20 | -------------------------------------------------------------------------------- /rules/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local ruled = require('ruled') 3 | 4 | ruled.client.connect_signal('request::rules', function() 5 | -- All clients will match this rule. 6 | ruled.client.append_rule { 7 | id = 'global', 8 | rule = {}, 9 | properties = { 10 | focus = awful.client.focus.filter, 11 | raise = true, 12 | screen = awful.screen.preferred, 13 | placement = awful.placement.no_overlap+awful.placement.no_offscreen 14 | +awful.placement.centered, 15 | size_hints_honor = false, 16 | callback = awful.client.setslave 17 | } 18 | } 19 | 20 | -- Floating clients. 21 | ruled.client.append_rule { 22 | id = 'floating', 23 | rule_any = { 24 | instance = {'copyq', 'pinentry'}, 25 | class = { 26 | 'Arandr', 27 | 'Blueman-manager', 28 | 'Gpick', 29 | 'Sxiv', 'Nsxiv', 'Viewnior', -- Image viewers. 30 | 'mpv', 31 | -- Tor randomizes its size and position as a floating window to avoid resolution fingerprinting. 32 | 'Tor Browser' 33 | }, 34 | -- Note that the name property shown in xprop might be set slightly after creation of the client 35 | -- and the name shown there might not match defined rules here. 36 | name = { 37 | 'Event Tester', -- xev. 38 | }, 39 | role = { 40 | 'AlarmWindow', -- Thunderbird's calendar. 41 | 'ConfigManager', -- Thunderbird's about:config. 42 | 'pop-up', -- e.g. Google Chrome's (detached) Developer Tools. 43 | } 44 | }, 45 | properties = { floating = true } 46 | } 47 | 48 | -- Add titlebars to normal clients and dialogs 49 | ruled.client.append_rule { 50 | id = 'titlebars', 51 | rule_any = { type = { 'normal', 'dialog' } }, 52 | except_any = { class = { 'feh', 'Viewnior', 'Sxiv', 'mpv' } }, 53 | properties = { titlebars_enabled = true } 54 | } 55 | 56 | -- Map 'Discord' to open in Tag '6', 'Steam' and 'Heroic' in '7'. 57 | ruled.client.append_rule { 58 | rule_any = { class = { 'steam', 'heroic' } }, 59 | properties = { tag = '7' } 60 | } 61 | ruled.client.append_rule { 62 | rule = { class = 'discord' }, 63 | properties = { tag = '6' } 64 | } 65 | end) 66 | -------------------------------------------------------------------------------- /script/screenshot.lua: -------------------------------------------------------------------------------- 1 | -- Screenshot script using maim and xclip. `awful.screenshot` has yet to be 2 | -- reliable enough to be usable. 3 | 4 | local awful = require('awful') 5 | local naughty = require('naughty') 6 | local beautiful = require('beautiful') 7 | local gears = require('gears') 8 | 9 | local gfs = gears.filesystem 10 | local gc = gears.color 11 | 12 | local user = require('config.user') 13 | local colors = require('theme.colorscheme') 14 | 15 | -- The directory where PERMANENT files would be stored. 16 | local perm_dir = user.screenshot_dir or os.getenv('HOME') 17 | -- The icon shown for interactions or when the screenshot is cancelled. 18 | local default_icon = gfs.get_configuration_dir() .. 'theme/assets/image.svg' 19 | 20 | local function send_notif(path) 21 | local ok = naughty.action { name = 'Ok' } 22 | local save = naughty.action { name = 'Save' } 23 | local discard = naughty.action { name = 'Discard' } 24 | 25 | save:connect_signal('invoked', function() 26 | awful.spawn.easy_async_with_shell('cp ' .. path .. ' ' .. perm_dir, function() 27 | naughty.notification({ 28 | icon = gc.recolor_image(default_icon, colors.green), 29 | title = 'Screenshot', 30 | text = 'Saved to ' .. perm_dir, 31 | actions = { ok } 32 | }) 33 | end) 34 | end) 35 | 36 | discard:connect_signal('invoked', function() 37 | awful.spawn.easy_async_with_shell('rm ' .. path, function() 38 | naughty.notification({ 39 | icon = gc.recolor_image(default_icon, colors.red), 40 | title = 'Screenshot', 41 | text = 'Temporary file removed!', 42 | actions = { ok } 43 | }) 44 | end) 45 | end) 46 | 47 | -- Check whether the screenshot was taken or not. 48 | local file = io.open(path) 49 | if file ~= nil then 50 | -- If it exists: 51 | io.close(file) 52 | naughty.notification({ 53 | icon = path, 54 | title = 'Screenshot', 55 | text = 'Copied to clipboard!', 56 | actions = { save, discard } 57 | }) 58 | else 59 | -- If it doesn't: 60 | naughty.notification({ 61 | icon = gc.recolor_image(default_icon, colors.red), 62 | title = 'Screenshot', 63 | text = 'Cancelled!', 64 | actions = { ok } 65 | }) 66 | end 67 | end 68 | 69 | -- Takes a screenshot and puts it in `/tmp`, then copies it to system clipboard 70 | -- and notifies about the result. 71 | local function take_screenshot(cmd) 72 | local tmp = '/tmp/ss-' .. os.date('%Y%m%d-%H%M%S') .. '.png' 73 | awful.spawn.easy_async_with_shell(cmd .. ' ' .. tmp, function() 74 | awful.spawn.with_shell('xclip -selection clip -t image/png -i ' .. tmp) 75 | send_notif(tmp) 76 | end) 77 | end 78 | 79 | return { 80 | screen = function() take_screenshot('maim') end, 81 | selection = function() take_screenshot('maim -s') end 82 | } 83 | -------------------------------------------------------------------------------- /script/tym.lua: -------------------------------------------------------------------------------- 1 | -- Writes a tym colorscheme using current colorscheme and lightness. 2 | 3 | local colors = require('theme.colorscheme') 4 | local _C = {} 5 | 6 | if require('config.user').dark then 7 | _C.bg = colors.gray100 8 | _C.b01 = colors.red_dark 9 | _C.b02 = colors.green_dark 10 | _C.b03 = colors.yellow_dark 11 | _C.b04 = colors.blue_dark 12 | _C.b05 = colors.magenta_dark 13 | _C.b06 = colors.cyan_dark 14 | _C.b07 = colors.gray30 15 | _C.b08 = colors.gray50 16 | _C.b09 = colors.red 17 | _C.b10 = colors.green 18 | _C.b11 = colors.yellow 19 | _C.b12 = colors.blue 20 | _C.b13 = colors.magenta 21 | _C.b14 = colors.cyan 22 | _C.b15 = colors.gray10 23 | _C.fg = colors.gray10 24 | else 25 | _C.bg = colors.gray10 26 | _C.b01 = colors.red_dark 27 | _C.b02 = colors.green_dark 28 | _C.b03 = colors.yellow_dark 29 | _C.b04 = colors.blue_dark 30 | _C.b05 = colors.magenta_dark 31 | _C.b06 = colors.cyan_dark 32 | _C.b07 = colors.gray70 33 | _C.b08 = colors.gray50 34 | _C.b09 = colors.red 35 | _C.b10 = colors.green 36 | _C.b11 = colors.yellow 37 | _C.b12 = colors.blue 38 | _C.b13 = colors.magenta 39 | _C.b14 = colors.cyan 40 | _C.b15 = colors.gray90 41 | _C.fg = colors.black 42 | end 43 | 44 | local theme = string.format([[ 45 | return { 46 | color_window_background = "%s", 47 | color_cursor_foreground = "%s", 48 | color_background = "%s", 49 | color_0 = "%s", 50 | color_foreground = "%s", 51 | color_7 = "%s", 52 | color_bold = "%s", 53 | color_8 = "%s", 54 | color_15 = "%s", 55 | color_cursor = "%s", 56 | color_9 = "%s", 57 | color_1 = "%s", 58 | color_10 = "%s", 59 | color_2 = "%s", 60 | color_11 = "%s", 61 | color_3 = "%s", 62 | color_12 = "%s", 63 | color_4 = "%s", 64 | color_13 = "%s", 65 | color_5 = "%s", 66 | color_14 = "%s", 67 | color_6 = "%s" 68 | } 69 | ]], 70 | _C.bg, _C.bg, _C.bg, _C.bg, _C.fg, 71 | _C.b07, _C.b15, _C.b08, _C.b15, _C.b15, _C.b09, 72 | _C.b01, _C.b10, _C.b02, _C.b11, _C.b03, 73 | _C.b12, _C.b04, _C.b13, _C.b05, _C.b14, _C.b06 74 | ) 75 | 76 | return function() 77 | local file = io.open(os.getenv('HOME') .. '/.config/tym/theme.lua', 'w+') 78 | if file == nil then return end 79 | file:write(theme) 80 | file:close() 81 | end 82 | -------------------------------------------------------------------------------- /script/xresources.lua: -------------------------------------------------------------------------------- 1 | -- Write .Xresources using colorscheme. 2 | local awful = require('awful') 3 | local beautiful = require('beautiful') 4 | 5 | local colorscheme = require('theme.colorscheme') 6 | 7 | local colors = {} 8 | if require('config.user') then 9 | colors = { 10 | bg_normal = colorscheme.gray100, 11 | bg_light = colorscheme.gray90, 12 | fg_normal = colorscheme.gray10, 13 | marked = colorscheme.yellow, 14 | focused = colorscheme.green 15 | } 16 | else 17 | colors = { 18 | bg_normal = colorscheme.gray10, 19 | bg_light = colorscheme.gray20, 20 | fg_normal = colorscheme.black, 21 | marked = colorscheme.yellow_dark, 22 | focused = colorscheme.green_dark 23 | } 24 | end 25 | 26 | local contents = string.format([[ 27 | *.dpi: %d 28 | Nsxiv.window.background: %s 29 | Nsxiv.window.foreground: %s 30 | Nsxiv.bar.background: %s 31 | Nsxiv.bar.foreground: %s 32 | Nsxiv.mark.foreground: %s 33 | Nsxiv.bar.font: %s-%d 34 | ]], 35 | math.floor(awful.screen.focused().dpi), 36 | colors.bg_normal, colors.focused, 37 | colors.bg_light, colors.fg_normal, 38 | colors.marked, 39 | -- Xresources uses a different font format, which means 40 | -- I need to remove the ' ' at the end of the font name. 41 | beautiful.font_sans:sub(1, -2), 42 | math.ceil(beautiful.xresources.apply_dpi(9)) 43 | ) 44 | 45 | return function() 46 | local file = io.open(os.getenv('HOME') .. '/.Xresources', 'w+') 47 | if file == nil then return end 48 | file:write(contents) 49 | file:close() 50 | awful.spawn.with_shell('xrdb -override ~/.Xresources') 51 | end 52 | -------------------------------------------------------------------------------- /signals/client/init.lua: -------------------------------------------------------------------------------- 1 | require('awful.autofocus') 2 | 3 | -- Focus client on hover. 4 | -- client.connect_signal('mouse::enter', function(c) 5 | -- c:activate { context = 'mouse_enter', raise = false } 6 | -- end) 7 | 8 | client.connect_signal('request::titlebars', function(c) 9 | -- Don't show titlebars on clients that explictly request not to 10 | -- have them, like the Steam client or many fullscreen apps, for 11 | -- example. 12 | if c.requests_no_titlebar and c.class ~= "steam" then return end 13 | 14 | require('widgets.titlebar').normal(c) 15 | end) 16 | 17 | -- Floating windows always on top. 18 | client.connect_signal('property::floating', function(c) c:raise() end) 19 | client.connect_signal('property::sticky', function(c) c.ontop = c.sticky end) 20 | -------------------------------------------------------------------------------- /signals/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | naughty = require('signals.naughty'), 3 | tag = require('signals.tag'), 4 | screen = require('signals.screen'), 5 | client = require('signals.client'), 6 | ruled = require('signals.ruled'), 7 | system = require('signals.system') 8 | } 9 | -------------------------------------------------------------------------------- /signals/naughty/error.lua: -------------------------------------------------------------------------------- 1 | local naughty = require('naughty') 2 | 3 | -- Errors 4 | naughty.connect_signal('request::display_error', function(message, startup) 5 | naughty.notification { 6 | urgency = 'critical', 7 | title = 'Oops, and error happened' .. (startup and ' during startup!' or '!'), 8 | message = message 9 | } 10 | end) 11 | -------------------------------------------------------------------------------- /signals/naughty/init.lua: -------------------------------------------------------------------------------- 1 | require('signals.naughty.error') 2 | 3 | local naughty = require('naughty') 4 | 5 | naughty.connect_signal('request::display', function(n) 6 | require('widgets.notification').normal(n) 7 | end) 8 | -------------------------------------------------------------------------------- /signals/ruled/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require'awful' 2 | local ruled = require'ruled' 3 | 4 | ruled.notification.connect_signal('request::rules', function() 5 | -- All notifications will match this rule. 6 | ruled.notification.append_rule { 7 | rule = {}, 8 | properties = { 9 | screen = awful.screen.preferred, 10 | implicit_timeout = 5, 11 | } 12 | } 13 | end) 14 | -------------------------------------------------------------------------------- /signals/screen/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local beautiful = require('beautiful') 3 | local wibox = require('wibox') 4 | local gears = require('gears') 5 | 6 | local dpi = beautiful.xresources.apply_dpi 7 | 8 | local vars = require('config.vars') 9 | local widgets = require('widgets') 10 | 11 | local wall = require('config.user').wallpaper 12 | 13 | screen.connect_signal('request::wallpaper', function(s) 14 | -- Let me explain the different choice of tools for setting the wall here. 15 | -- Precondition: wall is not nil as it is contained in the config user table. 16 | -- It may however be an empty string, therefore this check is needed. 17 | if #wall > 0 then 18 | -- We use `gears.wallpaper` for setting images as wall because it's a 19 | -- "one-shot" operation, meaning it's done once and garbage collected; and 20 | -- because it actually provides proper image scaling and cropping. 21 | gears.wallpaper.maximized(wall, s, false, nil) 22 | else 23 | -- We use `awful.wallpaper` only for setting solid color walls for 2 reasons: 24 | -- 1* this renders the wallpaper as a widget, which is somewhat expensive on 25 | -- resources if used for images, on top of requiring manual cropping. 26 | -- 2* awful.wallpaper is ideal for passing colors since it's a widget. 27 | local tile = gears.filesystem.get_configuration_dir() .. 'theme/assets/tile.svg' 28 | awful.wallpaper { 29 | screen = s, 30 | honor_workarea = true, 31 | bg = beautiful.mid_normal, 32 | widget = { 33 | widget = wibox.container.margin, 34 | margins = dpi(16), 35 | { 36 | widget = wibox.container.tile, 37 | { 38 | widget = wibox.widget.imagebox, 39 | forced_height = dpi(150), 40 | forced_width = dpi(150), 41 | halign = 'center', 42 | valign = 'center', 43 | image = gears.color.recolor_image(tile, beautiful.mid_light) 44 | } 45 | } 46 | } 47 | } 48 | end 49 | end) 50 | 51 | screen.connect_signal('request::desktop_decoration', function(s) 52 | -- Tag configuration. Creates #vars.tags tags respecting `config.vars` settings, 53 | -- then a single floating tag at the end of the list. 54 | local tags = vars.tags 55 | table.remove(tags, #tags) 56 | awful.tag(tags, s, awful.layout.layouts[1]) 57 | awful.tag.add(#tags + 1, { screen = s, layout = awful.layout.suit.floating }) 58 | 59 | -- Show the wibar in the main screen. 60 | widgets.wibar(s) 61 | widgets.dock(s) 62 | end) 63 | 64 | awful.screen.connect_for_each_screen(function(s) 65 | -- Sets a tag padding using the already dpi calculated `beautiful.useless_gap`. 66 | s.padding = { 67 | bottom = beautiful.useless_gap * 2, 68 | top = beautiful.useless_gap * 2, 69 | left = beautiful.useless_gap * 2, 70 | right = beautiful.useless_gap * 2 71 | } 72 | end) 73 | -------------------------------------------------------------------------------- /signals/system/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | weather = require('signals.system.weather') 3 | } 4 | -------------------------------------------------------------------------------- /signals/system/weather.lua: -------------------------------------------------------------------------------- 1 | -- Largely based on ChadCat's weather signal. 2 | -- https://github.com/chadcat7/crystal/blob/the-awesome-config/signals/stat/weather.lua 3 | 4 | local awful = require('awful') 5 | local gears = require('gears') 6 | 7 | local json = require('modules.json') 8 | local user = require('config.user') 9 | 10 | -- The OpenWeather API outputs icon codes, we need to actually 11 | -- do something with those if we intend to use icons. 12 | local icon_dir = require('gears.filesystem').get_configuration_dir() .. 'theme/assets/weather/' 13 | local icons = { 14 | -- Daytime 15 | ['01d'] = 'clear', 16 | ['02d'] = 'few-clouds', 17 | ['04d'] = 'few-clouds', 18 | ['03d'] = 'clouds', 19 | ['09d'] = 'rain-light', 20 | ['10d'] = 'rain', 21 | ['11d'] = 'storm', 22 | ['13d'] = 'snow', 23 | ['50d'] = 'fog', 24 | -- Nighttime 25 | ['01n'] = 'clear-n', 26 | ['02n'] = 'few-clouds-n', 27 | ['03n'] = 'few-clouds-n', 28 | ['04n'] = 'clouds', 29 | ['09n'] = 'rain-light', 30 | ['10n'] = 'rain', 31 | ['11n'] = 'storm', 32 | ['13n'] = 'snow', 33 | ['50n'] = 'fog' 34 | } 35 | 36 | -- Get the URL to call the OWAPI. 37 | local key = user.owkey 38 | local coords = user.coords 39 | if not key or not coords then return end 40 | 41 | local url = ( 42 | 'https://api.openweathermap.org/data/2.5/onecall?lat=' .. coords[1] .. 43 | '&lon=' .. coords[2] .. '&appid=' .. key .. '&units=metric&exclude=minutely' 44 | ) 45 | 46 | -- TODO: make algorith to recheck if the _W table is nil, on a shorter refresh rate, until 47 | -- proper values are found or the function times out, then switching to the normal rate. 48 | -- This would address the current situation where if the system starts without a connection 49 | -- or connection is lost, the weather info will not update for the following 15 minutes 50 | -- even if the system gets an internet connection and information becomes available. 51 | 52 | -- Obtain a json object, that is then dumped into a table accessible through the 53 | -- `connect::weather` signal, that exposes a description, humidity, current and 54 | -- feels like temperature, icon, and forecast by hour and day. Checks every 15 minutes. 55 | awful.widget.watch(string.format([[bash -c "curl -s --show-error -X GET '%s'"]], url), 720, 56 | function(_, stdout, _) 57 | local res = json.decode(stdout) 58 | local _W = {} 59 | _W.description = res.current.weather[1].description:gsub('^%l', string.upper) 60 | -- Something like this interrupts data flow if the first bit of data is already nil. 61 | -- It also stops retrying after 5 minutes, instead waiting the whole 15 minute cycle. 62 | -- local retry_count = 0 63 | -- while _W.description == nil and retry_count < 20 do 64 | -- wait(15) 65 | -- retry() 66 | -- retry_count = retry_count + 1 67 | -- end 68 | 69 | _W.humidity = res.current.humidity 70 | _W.temperature = math.floor(res.current.temp) 71 | _W.feels_like = math.floor(res.current.feels_like) 72 | _W.icon = icon_dir .. icons[res.current.weather[1].icon] .. '.svg' 73 | -- The entire day is necessary for calculating max/min temp for the day. 74 | _W.by_hour = {} 75 | for i = 1, 23, 1 do 76 | table.insert(_W.by_hour, res.hourly[i]) 77 | end 78 | _W.by_day = {} 79 | for i = 1, 7, 1 do 80 | table.insert(_W.by_day, res.daily[i]) 81 | end 82 | 83 | awesome.emit_signal('connect::weather', _W) 84 | end) 85 | 86 | -- local function get_weather() 87 | -- local weather 88 | -- awful.spawn.easy_async_with_shell(string.format([[bash -c "curl -s --show-error -X GET '%s'"]], url), function(stdout) 89 | -- local res = json.decode(stdout) 90 | -- local _W = {} 91 | -- _W.description = res.current.weather[1].description:gsub('^%l', string.upper) 92 | -- if _W.description == nil then return end 93 | -- 94 | -- _W.humidity = res.current.humidity 95 | -- _W.temperature = math.floor(res.current.temp) 96 | -- _W.feels_like = math.floor(res.current.feels_like) 97 | -- _W.icon = icon_dir .. icons[res.current.weather[1].icon] .. '.svg' 98 | -- -- The entire day is necessary for calculating max/min temp for the day. 99 | -- _W.by_hour = {} 100 | -- for i = 1, 23, 1 do 101 | -- table.insert(_W.by_hour, res.hourly[i]) 102 | -- end 103 | -- _W.by_day = { 104 | -- res.daily[1], 105 | -- res.daily[2], 106 | -- res.daily[3], 107 | -- res.daily[4], 108 | -- res.daily[5] 109 | -- } 110 | -- 111 | -- weather = _W 112 | -- end) 113 | -- return weather 114 | -- end 115 | -- 116 | -- local weather, retries 117 | -- 118 | -- gears.timer({ 119 | -- timeout = 720, 120 | -- autostart = true, 121 | -- call_now = true, 122 | -- callback = function() 123 | -- weather = get_weather() 124 | -- retries = 0 125 | -- if weather.description == nil then 126 | -- gears.timer({ 127 | -- timeout = 15, 128 | -- autostart = true, 129 | -- callback = function(self) 130 | -- weather = get_weather() 131 | -- if retries >= 12 or weather.description ~= nil then 132 | -- self:stop() 133 | -- else 134 | -- retries = retries + 1 135 | -- end 136 | -- end 137 | -- }) 138 | -- end 139 | -- awesome.emit_signal('connect::weather', weather) 140 | -- end 141 | -- }) 142 | -------------------------------------------------------------------------------- /signals/tag/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | 3 | local vars = require('config.vars') 4 | 5 | tag.connect_signal('request::default_layouts', function() 6 | awful.layout.append_default_layouts(vars.layouts) 7 | end) 8 | -------------------------------------------------------------------------------- /theme/assets/arrow/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 38 | 42 | 43 | -------------------------------------------------------------------------------- /theme/assets/arrow/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 38 | 42 | 43 | -------------------------------------------------------------------------------- /theme/assets/audio/off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/audio/on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/awesome.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 39 | 43 | 44 | -------------------------------------------------------------------------------- /theme/assets/bluetooth/off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/bluetooth/on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/dot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 28 | 30 | 34 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /theme/assets/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/layout/float.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 21 | 25 | 26 | -------------------------------------------------------------------------------- /theme/assets/layout/tile_bottom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 41 | 45 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /theme/assets/layout/tile_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 41 | 45 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /theme/assets/layout/tile_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 41 | 45 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /theme/assets/network/off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/network/on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/tile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 40 | 42 | 47 | 51 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /theme/assets/weather/clear-n.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/clear.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/clouds.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/few-clouds-n.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/few-clouds.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/fog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/rain-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/rain.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/snow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/assets/weather/storm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /theme/colorscheme/adwaita/init.lua: -------------------------------------------------------------------------------- 1 | -- Adwaita, the GTK colorscheme. 2 | local _C = {} 3 | 4 | -- Monochrome 5 | _C.black = '#1e1e1e' 6 | _C.gray100 = '#282828' 7 | _C.gray90 = '#353535' 8 | _C.gray80 = '#444444' 9 | _C.gray70 = '#565656' 10 | _C.gray60 = '#696969' 11 | _C.gray50 = '#8b8b8b' 12 | _C.gray40 = '#a3a3a3' 13 | _C.gray30 = '#c0c0c0' 14 | _C.gray20 = '#d2d2d2' 15 | _C.gray10 = '#eeeeee' 16 | _C.white = '#ffffff' 17 | 18 | -- Colors 19 | _C.red = '#ed333b' 20 | _C.red_dark = '#c01c28' 21 | _C.green = '#57e389' 22 | _C.green_dark = '#2ec27e' 23 | _C.yellow = '#ffa348' 24 | _C.yellow_dark = '#e66100' 25 | _C.blue = '#62a0ea' 26 | _C.blue_dark = '#1c71d8' 27 | _C.magenta = '#c061cb' 28 | _C.magenta_dark = '#813d9c' 29 | _C.cyan = '#5bc8af' 30 | _C.cyan_dark = '#26a1a2' 31 | 32 | return _C 33 | -------------------------------------------------------------------------------- /theme/colorscheme/fullerene/init.lua: -------------------------------------------------------------------------------- 1 | -- IBM Carbon my beloved. 2 | local _C = {} 3 | 4 | -- Monochrome 5 | _C.black = '#0c0c0c' 6 | _C.gray100 = '#161616' 7 | _C.gray90 = '#1f1f1f' 8 | _C.gray80 = '#262626' 9 | _C.gray70 = '#393939' 10 | _C.gray60 = '#525252' 11 | _C.gray50 = '#6f6f6f' 12 | _C.gray40 = '#8d8d8d' 13 | _C.gray30 = '#a8a8a8' 14 | _C.gray20 = '#c6c6c6' 15 | _C.gray10 = '#e0e0e0' 16 | _C.white = '#f4f4f4' 17 | 18 | -- Colors 19 | _C.red = '#c1374b' 20 | _C.red_dark = '#902c3b' 21 | _C.green = '#32ae80' 22 | _C.green_dark = '#2a8664' 23 | _C.yellow = '#e1a36b' 24 | _C.yellow_dark = '#d78438' 25 | _C.blue = '#738be7' 26 | _C.blue_dark = '#546ad8' 27 | _C.magenta = '#9e7ad5' 28 | _C.magenta_dark = '#875fd4' 29 | _C.cyan = '#2b9eb0' 30 | _C.cyan_dark = '#1f7a89' 31 | 32 | return _C 33 | -------------------------------------------------------------------------------- /theme/colorscheme/gruvbox/init.lua: -------------------------------------------------------------------------------- 1 | -- The gruvbox colorscheme by morhetz. 2 | local _C = {} 3 | 4 | -- Monochrome 5 | _C.black = '#1d2021' 6 | _C.gray100 = '#282828' 7 | _C.gray90 = '#32302f' 8 | _C.gray80 = '#3c3836' 9 | _C.gray70 = '#504945' 10 | _C.gray60 = '#665c54' 11 | _C.gray50 = '#7c6f64' 12 | _C.gray40 = '#928374' 13 | _C.gray30 = '#bdae93' 14 | _C.gray20 = '#d5c4a1' 15 | _C.gray10 = '#ebdbb2' 16 | _C.white = '#fbf1c7' 17 | 18 | -- Colors 19 | _C.red = '#fb4934' 20 | _C.red_dark = '#cc241d' 21 | _C.green = '#b8bb26' 22 | _C.green_dark = '#98971a' 23 | _C.yellow = '#fabd2f' 24 | _C.yellow_dark = '#d79921' 25 | _C.blue = '#83a598' 26 | _C.blue_dark = '#458588' 27 | _C.magenta = '#d3869b' 28 | _C.magenta_dark = '#b16286' 29 | _C.cyan = '#8ec07c' 30 | _C.cyan_dark = '#689d6a' 31 | 32 | return _C 33 | -------------------------------------------------------------------------------- /theme/colorscheme/init.lua: -------------------------------------------------------------------------------- 1 | local user = require('config.user') 2 | 3 | local colorscheme = require('theme.colorscheme.fullerene') 4 | if user.colorscheme ~= nil then 5 | colorscheme = require('theme.colorscheme.' .. user.colorscheme) 6 | end 7 | 8 | return colorscheme 9 | -------------------------------------------------------------------------------- /theme/colorscheme/janleigh/init.lua: -------------------------------------------------------------------------------- 1 | -- Janleigh's (aka Kizu) custom, untitled, colorscheme. 2 | -- see: https://github.com/janleigh/dotfiles 3 | local _C = {} 4 | 5 | -- Monochrome 6 | _C.black = '#090909' 7 | _C.gray100 = '#0b0f10' 8 | _C.gray90 = '#101415' 9 | _C.gray80 = '#15191a' 10 | _C.gray70 = '#1c2325' 11 | _C.gray60 = '#2a3133' 12 | _C.gray50 = '#3d4142' 13 | _C.gray40 = '#636667' 14 | _C.gray30 = '#919596' 15 | _C.gray20 = '#afb3b4' 16 | _C.gray10 = '#c5c8c9' 17 | _C.white = '#dde1e2' 18 | 19 | -- Colors 20 | _C.red = '#ee6a70' 21 | _C.red_dark = '#df5b61' 22 | _C.green = '#96d6b0' 23 | _C.green_dark = '#87c7a1' 24 | _C.yellow = '#ffb29b' 25 | _C.yellow_dark = '#de8f78' 26 | _C.blue = '#7ba5dd' 27 | _C.blue_dark = '#6791c9' 28 | _C.magenta = '#cb92f2' 29 | _C.magenta_dark = '#bc83e3' 30 | _C.cyan = '#7fc8db' 31 | _C.cyan_dark = '#70b9cc' 32 | 33 | return _C 34 | -------------------------------------------------------------------------------- /theme/colorscheme/solarized/init.lua: -------------------------------------------------------------------------------- 1 | -- Solarized so you can keep crying about it. 2 | local _C = {} 3 | 4 | -- Monochrome 5 | _C.black = '#002028' 6 | _C.gray100 = '#002b36' 7 | _C.gray90 = '#073642' 8 | _C.gray80 = '#1d4b5b' 9 | _C.gray70 = '#38626f' 10 | _C.gray60 = '#586e75' 11 | _C.gray50 = '#657b83' 12 | _C.gray40 = '#839496' 13 | _C.gray30 = '#93a1a1' 14 | _C.gray20 = '#b4baba' 15 | _C.gray10 = '#eee8d5' 16 | _C.white = '#fdf6e3' 17 | 18 | -- Colors 19 | _C.red = '#dc322f' 20 | _C.red_dark = '#b71b19' 21 | _C.green = '#859900' 22 | _C.green_dark = '#728200' 23 | _C.yellow = '#b58900' 24 | _C.yellow_dark = '#997100' 25 | _C.blue = '#268bd2' 26 | _C.blue_dark = '#186fad' 27 | _C.magenta = '#d33682' 28 | _C.magenta_dark = '#ba236c' 29 | _C.cyan = '#2aa198' 30 | _C.cyan_dark = '#1c857d' 31 | 32 | return _C 33 | -------------------------------------------------------------------------------- /theme/init.lua: -------------------------------------------------------------------------------- 1 | local beautiful = require('beautiful') 2 | local gfs = require('gears.filesystem') 3 | 4 | -- Set theme variables (the beautiful table). 5 | beautiful.init(gfs.get_configuration_dir() .. 'theme/settings.lua') 6 | -- Better safe than sorry. 7 | if beautiful.bg_normal == nil then return end 8 | 9 | -- Apply to Xresources. 10 | require('script.xresources')() 11 | -- Apply to terminal (tym). 12 | require('script.tym')() 13 | -------------------------------------------------------------------------------- /theme/settings.lua: -------------------------------------------------------------------------------- 1 | local gears = require('gears') 2 | 3 | local dpi = require('beautiful').xresources.apply_dpi 4 | local gc = gears.color 5 | local gfs = gears.filesystem 6 | 7 | local color = require('theme.colorscheme') 8 | local user = require('config.user') 9 | local helper = require('helpers') 10 | 11 | local asset_path = gfs.get_configuration_dir() .. 'theme/assets/' 12 | 13 | local _T = {} 14 | 15 | -- Custom Variables 16 | _T.font_sans = 'IBM Plex Sans ' 17 | _T.font_mono = 'IBM Plex Mono ' 18 | _T.font_icon = 'Material Icons Sharp ' 19 | 20 | -- Custom Colors 21 | _T.bg_normal = color.gray100 22 | _T.fg_normal = color.gray10 23 | _T.transparent = '#00000000' 24 | 25 | -- Basic AWM variables 26 | _T.useless_gap = dpi(user.gaps) 27 | _T.master_width_factor = 0.56 28 | _T.font = _T.font_sans .. dpi(10) 29 | _T.icon_theme = user.icon_theme or 'Papirus-Dark' 30 | 31 | local def_icon = asset_path .. 'awesome.svg' 32 | -- This variable may not be nil as it is contained in the config table, 33 | -- but it may still be an empty string, so we check its length. 34 | if #user.avatar > 0 then 35 | -- Cropping the image means that the user can use images of arbitrary 36 | -- aspect ratio, which is very commonly the case. 37 | local surf = gears.surface.load_uncached(user.avatar) 38 | _T.awesome_icon = helper.crop_surface(1, surf) 39 | else 40 | -- Since we know the default logo IS square, there's no need for cropping. 41 | -- _T.awesome_icon = gc.recolor_image(def_icon, _T.fg_normal) 42 | _T.awesome_icon = def_icon 43 | end 44 | 45 | -- Systray 46 | _T.bg_systray = user.dark and _T.gray100 or _T.gray90 47 | _T.systray_icon_spacing = dpi(4) 48 | 49 | -- Layout icons 50 | local icons_path = asset_path .. 'layout/' 51 | _T.layout_tile = gc.recolor_image(icons_path .. 'tile_right.svg', _T.fg_normal) 52 | _T.layout_tileleft = gc.recolor_image(icons_path .. 'tile_left.svg', _T.fg_normal) 53 | _T.layout_tilebottom = gc.recolor_image(icons_path .. 'tile_bottom.svg', _T.fg_normal) 54 | _T.layout_floating = gc.recolor_image(icons_path .. 'float.svg', _T.fg_normal) 55 | 56 | -- Snapping 57 | _T.snap_border_width = dpi(3) 58 | _T.snap_bg = color.blue_dark 59 | _T.snap_shape = gears.shape.rectangle 60 | 61 | -- Tooltips 62 | _T.tooltip_bg = color.gray90 63 | _T.tooltip_fg = color.gray20 64 | _T.tooltip_shape = function(c, w, h) 65 | gears.shape.rounded_rect(c, w, h, dpi(4)) 66 | end 67 | _T.tooltip_align = 'bottom' 68 | _T.tooltip_gaps = dpi(4) 69 | 70 | -- Notification 71 | _T.notification_spacing = dpi(16) 72 | 73 | return _T 74 | -------------------------------------------------------------------------------- /widgets/config/init.lua: -------------------------------------------------------------------------------- 1 | -- Inspired by Stardust-kyun's configuration widget. 2 | -- https://github.com/Stardust-kyun/dotfiles/blob/main/home/.config/awesome/theme/config.lua 3 | 4 | local awful = require('awful') 5 | local wibox = require('wibox') 6 | local beautiful = require('beautiful') 7 | local gears = require('gears') 8 | 9 | local dpi = beautiful.xresources.apply_dpi 10 | 11 | local module = require(... .. '.module') 12 | local colors = module.colors 13 | local utils = module.util 14 | local overflow = require('modules.overflow') 15 | 16 | local tmp = require('config.user') 17 | local colorschemes = { 18 | 'adwaita', 'fullerene', 'gruvbox', 'janleigh', 'solarized' 19 | } 20 | 21 | local config = wibox { 22 | ontop = true, 23 | visible = false, 24 | width = dpi(400), 25 | height = dpi(600), 26 | bg = beautiful.transparent, 27 | shape = function(c, w, h) 28 | gears.shape.rounded_rect(c, w, h, dpi(6)) 29 | end, 30 | placement = awful.placement.centered, 31 | widget = { 32 | widget = wibox.container.background, 33 | bg = colors.bg_normal, 34 | shape = function(c, w, h) 35 | gears.shape.rounded_rect(c, w, h, dpi(8)) 36 | end, 37 | { 38 | layout = wibox.layout.align.vertical, 39 | { 40 | widget = wibox.container.background, 41 | bg = colors.bg_normal, 42 | { 43 | widget = wibox.container.margin, 44 | margins = { 45 | left = dpi(16), right = dpi(16), 46 | top = dpi(12), bottom = dpi(12) 47 | }, 48 | { 49 | widget = wibox.container.background, 50 | fg = colors.fg_normal, 51 | { 52 | widget = wibox.widget.textbox, 53 | markup = 'User Settings', 54 | font = beautiful.font_sans .. dpi(11) 55 | } 56 | } 57 | } 58 | }, 59 | { 60 | widget = wibox.container.background, 61 | bg = { 62 | type = 'linear', 63 | from = { 0, 0 }, 64 | to = { 0, dpi(125) }, 65 | stops = { { 0, colors.bg_light .. '8c' }, { 1, beautiful.transparent } } 66 | }, 67 | { 68 | widget = wibox.container.margin, 69 | margins = dpi(20), 70 | { 71 | layout = wibox.layout.align.vertical, 72 | expand = 'none', 73 | { 74 | widget = wibox.container.constraint, 75 | strategy = 'max', 76 | height = dpi(472), 77 | { 78 | layout = overflow.vertical, 79 | scrollbar_widget = { 80 | widget = wibox.container.margin, 81 | margins = { left = dpi(1) }, 82 | { 83 | widget = wibox.widget.separator, 84 | color = colors.highlight, 85 | shape = function(c, w, h) 86 | gears.shape.rounded_rect(c, w, h, dpi(4)) 87 | end 88 | } 89 | }, 90 | spacing = dpi(8), 91 | -- Numbers 92 | utils.section_title('Scary Numbers'), 93 | utils.integer(tmp, 'gaps', 'Window gaps size', 0), 94 | utils.integer(tmp, 'borders', 'Window borders size', 3), 95 | utils.section_separator(), 96 | -- Colors 97 | utils.section_title('Colors'), 98 | utils.dropdown(tmp, 'colorscheme', 'Colorscheme', colorschemes), 99 | utils.toggle(tmp, 'dark', 'Dark mode'), 100 | utils.section_separator(), 101 | -- Images 102 | utils.section_title('Images'), 103 | utils.string(tmp, 'icon_theme', 'Icon theme'), 104 | utils.path(tmp, 'wallpaper', 'Wallpaper path'), 105 | utils.path(tmp, 'avatar', 'Avatar path'), 106 | utils.path(tmp, 'screenshot_dir', 'Screenshot directory'), 107 | utils.section_separator(), 108 | -- Weather 109 | utils.section_title('Weather'), 110 | utils.string(tmp, 'owkey', 'OpenWeatherMap key'), 111 | utils.coords(tmp, 'coords'), 112 | utils.section_separator() 113 | } 114 | }, 115 | nil, 116 | { 117 | layout = wibox.layout.align.horizontal, 118 | nil, nil, 119 | utils.apply_button(tmp) 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | function config:show() 129 | self.visible = not self.visible 130 | awful.placement.centered(config) 131 | end 132 | 133 | return config 134 | -------------------------------------------------------------------------------- /widgets/config/module/colors.lua: -------------------------------------------------------------------------------- 1 | local colorscheme = require('theme.colorscheme') 2 | 3 | local colors = {} 4 | if require('config.user').dark then 5 | colors = { 6 | bg_normal = colorscheme.black, 7 | bg_light = colorscheme.gray100, 8 | highlight = colorscheme.gray80, 9 | inactive = colorscheme.gray40, 10 | fg_normal = colorscheme.gray10 11 | } 12 | else 13 | colors = { 14 | bg_normal = colorscheme.gray10, 15 | bg_light = colorscheme.gray20, 16 | highlight = colorscheme.gray30, 17 | inactive = colorscheme.gray50, 18 | fg_normal = colorscheme.black 19 | } 20 | end 21 | 22 | return colors 23 | -------------------------------------------------------------------------------- /widgets/config/module/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | colors = require(... .. '.colors'), 3 | util = require(... .. '.util') 4 | } 5 | -------------------------------------------------------------------------------- /widgets/dock/init.lua: -------------------------------------------------------------------------------- 1 | -- Heavily inspired and based on Crylia's and Chadcat's implementations. 2 | -- https://github.com/Crylia/crylia-theme/blob/main/awesome/crylia_bar/dock.lua 3 | -- https://github.com/chadcat7/crystal/blob/bloatedwm/ui/dock/init.lua 4 | 5 | local awful = require('awful') 6 | local beautiful = require('beautiful') 7 | local gears = require('gears') 8 | local wibox = require('wibox') 9 | 10 | local dpi = beautiful.xresources.apply_dpi 11 | 12 | local helpers = require('helpers') 13 | local module = require(... .. '.module') 14 | local colors = module.colors 15 | 16 | -- Lua has actual constants now! :P 17 | -- (yes I am aware they are more similar to Java's `final` in the fact that 18 | -- they are not constant but instead not reassignable, thank you). 19 | local SIZE = 60 20 | 21 | -- Creates the hints for a dock item. 22 | local function app_hints(class, name) 23 | local p_class = string.lower(class) 24 | local p_name = string.lower(name) 25 | local hints = wibox.widget { 26 | layout = wibox.layout.flex.horizontal, 27 | spacing = dpi(3) 28 | } 29 | 30 | for _, c in ipairs(client.get()) do 31 | if not c.class or not c.name then goto continue end 32 | -- If either class or name of a client match the app's. 33 | -- "That condition is hella ugly" Yeah, so are you :| 34 | -- All jokes aside, it is ugly, but it does its job (unlike you). 35 | local c_class = string.lower(c.class) 36 | local c_name = string.lower(c.name) 37 | if c_class:match(p_class) or c_name:match(p_class) or c_class:match(p_name) or 38 | c_name:match(p_name) then 39 | -- Give it a color representative of the client's state. 40 | local color = colors.highlight 41 | local click = function() client.focus = c end 42 | if c == client.focus then 43 | color = colors.accent 44 | click = function() c.minimized = true end 45 | elseif c.urgent then 46 | color = colors.urgent 47 | elseif c.maximized or c.fullscreen then 48 | color = colors.fg_normal 49 | elseif c.minimized then 50 | color = colors.inactive 51 | click = function() c.minimized = false end 52 | end 53 | -- Add the widgets back to the list. 54 | local widget = wibox.widget { 55 | widget = wibox.container.margin, 56 | margins = { 57 | top = dpi(3), bottom = dpi(3), 58 | }, 59 | { 60 | widget = wibox.container.background, 61 | bg = color, 62 | forced_height = dpi(2) 63 | }, 64 | buttons = { 65 | awful.button(nil, 1, function() click() end) 66 | } 67 | } 68 | hints:add(widget) 69 | end 70 | ::continue:: 71 | end 72 | 73 | -- Add a dummy widget if there are no instances of a client present. 74 | if #hints.children < 1 then 75 | hints:add(wibox.widget { 76 | widget = wibox.container.background, 77 | bg = beautiful.transparent, 78 | forced_height = dpi(2) 79 | }) 80 | end 81 | 82 | return hints 83 | end 84 | 85 | -- Creates a dock_item given at least a class and program name. 86 | local function dock_item(class, name, program, client_icon) 87 | if program == nil or class == nil then return end 88 | if name == nil then name = class end 89 | local p_class = string.lower(class) 90 | local p_name = string.lower(name) 91 | 92 | -- Get the best icon. 93 | local icon = helpers.get_icon(require('config.user').icon_theme, nil, name, class) 94 | if icon == helpers.DEFAULT_ICON then 95 | if client_icon ~= nil then 96 | icon = client_icon 97 | end 98 | end 99 | 100 | local widget = wibox.widget { 101 | layout = wibox.layout.stack, 102 | { 103 | widget = wibox.container.margin, 104 | margins = { 105 | top = dpi(8), bottom = dpi(8) 106 | }, 107 | { 108 | widget = wibox.container.background, 109 | bg = beautiful.transparent, 110 | shape = function(c, w, h) 111 | gears.shape.rounded_rect(c, w, h, dpi(4)) 112 | end, 113 | id = 'background', 114 | { 115 | widget = wibox.container.margin, 116 | margins = dpi(2), 117 | { 118 | widget = wibox.widget.imagebox, 119 | image = icon, 120 | buttons = { 121 | awful.button(nil, 1, function() awful.spawn(program) end) 122 | } 123 | } 124 | } 125 | } 126 | }, 127 | { 128 | widget = wibox.container.margin, 129 | margins = { 130 | top = dpi(SIZE - 8), 131 | left = dpi(6), right = dpi(6) 132 | }, 133 | app_hints(class, name) 134 | }, 135 | set_bg = function(self, color) 136 | self:get_children_by_id('background')[1].bg = color 137 | end 138 | } 139 | 140 | -- Cool hover colors based on focus. 141 | -- Initial state. 142 | for _, c in ipairs(client.get()) do 143 | if not c.class or not c.name then goto continue end 144 | if c == client.focus then 145 | local c_class = string.lower(c.class) 146 | local c_name = string.lower(c.name) 147 | if c_class:match(p_class) or c_name:match(p_class) or c_class:match(p_name) 148 | or c_name:match(p_name) then 149 | widget.bg = colors.bg_light 150 | end 151 | end 152 | ::continue:: 153 | end 154 | widget:connect_signal('mouse::enter', function() 155 | widget.bg = colors.bg_light 156 | end) 157 | widget:connect_signal('mouse::leave', function() 158 | widget.bg = beautiful.transparent 159 | for _, c in ipairs(client.get()) do 160 | if not c.class or not c.name then goto continue end 161 | if c == client.focus then 162 | local c_class = string.lower(c.class) 163 | local c_name = string.lower(c.name) 164 | if c_class:match(p_class) or c_name:match(p_class) or c_class:match(p_name) 165 | or c_name:match(p_name) then 166 | widget.bg = colors.bg_light 167 | end 168 | end 169 | ::continue:: 170 | end 171 | end) 172 | 173 | -- Cool tooltip on hover. 174 | awful.tooltip({ 175 | objects = { widget }, 176 | text = name:gsub("^%l", string.upper), 177 | mode = 'outside', 178 | margins = dpi(8) 179 | }) 180 | 181 | return widget 182 | end 183 | 184 | -- Given a table of apps (which have a class, program and optionally icon), 185 | -- returns a widget with their corresponding dock_items. 186 | local function get_dock_items() 187 | local apps = {} 188 | local classes = {} 189 | -- First, add all apps to the 'apps' table and also their classes to the 190 | -- 'classes' table. 191 | for _, app in ipairs(module.pinned) do 192 | table.insert(classes, app.class) 193 | table.insert(apps, app) 194 | end 195 | 196 | -- Then, read all currently open clients and if they don't have the same 197 | -- class as the ones in the 'classes' table, add them to both tables. 198 | for _, c in ipairs(mouse.screen.selected_tag:clients()) do 199 | -- Some clients sometimes don't report a class for a brief period of 200 | -- time and this thing freaks out. 201 | if not c.class or not c.name then goto continue end 202 | 203 | local class = string.lower(c.class) 204 | if not helpers.in_table(classes, class) then 205 | table.insert(classes, class) 206 | table.insert(apps, { 207 | class = c.class, 208 | name = c.name, 209 | exec = class, 210 | icon = c.icon 211 | }) 212 | end 213 | ::continue:: 214 | end 215 | 216 | -- Run through all apps in the 'apps' table and create their widgets. 217 | local items = wibox.widget { layout = wibox.layout.fixed.horizontal } 218 | for _, app in ipairs(apps) do 219 | items:add(dock_item(app.class, app.name, app.exec, app.icon)) 220 | end 221 | return items 222 | end 223 | 224 | local function get_fake_items() 225 | -- Analogous to get_dock_items. 226 | local classes = {} 227 | for _, app in ipairs(module.pinned) do 228 | table.insert(classes, app.class) 229 | end 230 | for _, c in ipairs(mouse.screen.selected_tag:clients()) do 231 | if not c.class then goto continue end 232 | local class = string.lower(c.class) 233 | if not helpers.in_table(classes, class) then 234 | table.insert(classes, class) 235 | end 236 | ::continue:: 237 | end 238 | 239 | local items = wibox.widget { layout = wibox.layout.fixed.horizontal } 240 | for _, _ in ipairs(classes) do 241 | items:add(wibox.widget { 242 | widget = wibox.container.background, 243 | bg = beautiful.transparent, 244 | forced_width = dpi(SIZE * 4/5), 245 | forced_height = dpi(10) 246 | }) 247 | end 248 | return items 249 | end 250 | 251 | return function(s) 252 | local dock = awful.popup { 253 | widget = wibox.container.background, 254 | screen = s, 255 | ontop = true, 256 | visible = true, 257 | type = 'dock', 258 | bg = beautiful.transparent, 259 | shape = function(c, w, h) 260 | gears.shape.partially_rounded_rect(c, w, h, true, true, false, false, dpi(6)) 261 | end, 262 | placement = function(c) awful.placement.bottom(c) end, 263 | maximum_height = dpi(SIZE) 264 | } 265 | dock:setup { 266 | widget = wibox.container.background, 267 | bg = colors.bg_normal, 268 | shape = function(c, w, h) 269 | gears.shape.partially_rounded_rect(c, w, h, true, true, false, false, dpi(8)) 270 | end, 271 | { 272 | widget = wibox.container.margin, 273 | margins = { 274 | left = dpi(8), right = dpi(8) 275 | }, 276 | get_dock_items() 277 | } 278 | } 279 | 280 | local fake_dock = awful.popup { 281 | widget = wibox.container.background, 282 | screen = s, 283 | ontop = true, 284 | visible = true, 285 | type = 'dock', 286 | bg = beautiful.transparent, 287 | placement = function(c) 288 | awful.placement.bottom(c) 289 | end, 290 | height = dpi(10), 291 | } 292 | fake_dock:setup { 293 | widget = wibox.container.background, 294 | { 295 | widget = wibox.container.margin, 296 | margins = dpi(8), 297 | get_fake_items() 298 | } 299 | } 300 | 301 | local function check_for_dock_hide() 302 | for _, client in ipairs(s.selected_tag:clients()) do 303 | if client.fullscreen then 304 | dock.visible = false 305 | fake_dock.visible = false 306 | else 307 | fake_dock.visible = true 308 | end 309 | end 310 | if #s.selected_tag:clients() < 1 then 311 | dock.visible = true 312 | return 313 | end 314 | if s == mouse.screen then 315 | local minimized 316 | for _, c in ipairs(s.selected_tag:clients()) do 317 | if c.minimized then 318 | minimized = true 319 | end 320 | if c.maximized or c.fullscreen then 321 | dock.visible = false 322 | return 323 | end 324 | if not c.minimized then 325 | local y = c:geometry().y 326 | local h = c.height 327 | if (y + h) >= s.geometry.height - (SIZE + beautiful.useless_gap) then 328 | dock.visible = false 329 | return 330 | else 331 | dock.visible = true 332 | end 333 | end 334 | end 335 | if minimized then 336 | dock.visible = true 337 | end 338 | else 339 | dock.visible = false 340 | end 341 | end 342 | 343 | local dock_hide_timer = gears.timer { 344 | timeout = 1, 345 | autostart = true, 346 | call_now = true, 347 | callback = function() check_for_dock_hide() end 348 | } 349 | 350 | local function update() 351 | check_for_dock_hide() 352 | dock:setup { 353 | widget = wibox.container.background, 354 | bg = colors.bg_normal, 355 | shape = function(c, w, h) 356 | gears.shape.partially_rounded_rect(c, w, h, true, true, false, false, dpi(8)) 357 | end, 358 | { 359 | widget = wibox.container.margin, 360 | margins = { 361 | left = dpi(8), right = dpi(8) 362 | }, 363 | get_dock_items() 364 | } 365 | } 366 | end 367 | 368 | -- Bind all conditions in which the dock contents should be redrawn or hidden. 369 | dock:connect_signal('mouse::enter', function() 370 | dock_hide_timer:stop() 371 | end) 372 | dock:connect_signal('mouse::leave', function() 373 | dock_hide_timer:again() 374 | end) 375 | fake_dock:connect_signal('mouse::enter', function() 376 | for _, c in ipairs(s.clients) do 377 | if not c.fullscreen then 378 | dock_hide_timer:stop() 379 | dock.visible = true 380 | end 381 | end 382 | end) 383 | fake_dock:connect_signal('mouse::leave', function() 384 | dock_hide_timer:again() 385 | end) 386 | client.connect_signal('manage', function() update() end) 387 | client.connect_signal('unmanage', function() update() end) 388 | client.connect_signal('focus', function() update() end) 389 | client.connect_signal('property::minimized', function() update() end) 390 | client.connect_signal('property::maximized', function() update() end) 391 | client.connect_signal('property::fullscreen', function() update() end) 392 | end 393 | -------------------------------------------------------------------------------- /widgets/dock/module/colors.lua: -------------------------------------------------------------------------------- 1 | local colorscheme = require('theme.colorscheme') 2 | 3 | local colors = {} 4 | if require('config.user').dark then 5 | colors = { 6 | bg_normal = colorscheme.black, 7 | bg_light = colorscheme.gray100, 8 | highlight = colorscheme.gray80, 9 | inactive = colorscheme.gray40, 10 | fg_normal = colorscheme.gray10, 11 | urgent = colorscheme.red, 12 | accent = colorscheme.yellow 13 | } 14 | else 15 | colors = { 16 | bg_normal = colorscheme.gray10, 17 | bg_light = colorscheme.gray20, 18 | highlight = colorscheme.gray30, 19 | inactive = colorscheme.gray50, 20 | fg_normal = colorscheme.black, 21 | urgent = colorscheme.red_dark, 22 | accent = colorscheme.yellow_dark 23 | } 24 | end 25 | 26 | return colors 27 | -------------------------------------------------------------------------------- /widgets/dock/module/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | colors = require(... .. '.colors'), 3 | pinned = require(... .. '.pinned') 4 | } 5 | -------------------------------------------------------------------------------- /widgets/dock/module/pinned.lua: -------------------------------------------------------------------------------- 1 | local json = require('modules.json') 2 | 3 | local data_path = os.getenv('HOME') .. '/.local/share/awesome/pinned.json' 4 | local pinned = {} 5 | 6 | local json_file = io.open(data_path, 'r') 7 | if json_file then 8 | pinned = json.decode(assert(json_file):read()) 9 | json_file:close() 10 | else 11 | -- Pinned apps now default to being read from `~/.local/share/awesome/pinned.json`. 12 | -- Only when that file doesn't exist is the following table read. 13 | pinned = { 14 | { 15 | class = 'firefox', 16 | name = 'firefox', 17 | exec = 'firefox' 18 | }, 19 | { 20 | class = 'nemo', 21 | name = 'nemo', 22 | exec = 'nemo' 23 | }, 24 | { 25 | class = 'steam', 26 | name = 'steam', 27 | exec = 'steam' 28 | }, 29 | { 30 | class = 'gpick', 31 | name = 'gpick', 32 | exec = 'gpick' 33 | } 34 | } 35 | -- And this default is written to that file. 36 | local data = assert(io.open(data_path, 'w')) 37 | data:write(json.encode(pinned)) 38 | end 39 | 40 | return pinned 41 | -------------------------------------------------------------------------------- /widgets/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | menu = require(... .. '.menu'), 3 | wibar = require(... .. '.wibar'), 4 | time = require(... .. '.timepanel'), 5 | config = require(... .. '.config'), 6 | dock = require(... .. '.dock') 7 | } 8 | -------------------------------------------------------------------------------- /widgets/menu/colors.lua: -------------------------------------------------------------------------------- 1 | local colorscheme = require('theme.colorscheme') 2 | 3 | local colors = {} 4 | if require('config.user').dark then 5 | colors = { 6 | normal = colorscheme.gray100, 7 | focus = colorscheme.gray90, 8 | text = colorscheme.gray10 9 | } 10 | else 11 | colors = { 12 | normal = colorscheme.gray10, 13 | focus = colorscheme.gray20, 14 | text = colorscheme.black 15 | } 16 | end 17 | 18 | return colors 19 | -------------------------------------------------------------------------------- /widgets/menu/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local hotkeys_popup = require('awful.hotkeys_popup') 3 | local beautiful = require('beautiful') 4 | local wibox = require('wibox') 5 | local gears = require('gears') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | local grec = gears.color.recolor_image 9 | 10 | local colors = require(... .. '.colors') 11 | local apps = require('config.apps') 12 | local dot = grec(gears.filesystem.get_configuration_dir() .. 'theme/assets/dot.svg', colors.text) 13 | 14 | -- 'Sections'. 15 | local _S = {} 16 | 17 | _S.awesome = { 18 | { 'Keybinds' , function() hotkeys_popup.show_help(nil, awful.screen.focused()) end }, 19 | { 'Manpage' , apps.manual_cmd }, 20 | -- { 'edit config' , apps.editor_cmd .. ' ' .. awesome.conffile }, 21 | { 'Reload' , awesome.restart } 22 | -- , { 'quit' , function() awesome.quit() end } 23 | } 24 | 25 | _S.power = { 26 | { 'Log off', function() awesome.quit() end }, 27 | { 'Suspend', function() os.execute('suspend') end }, 28 | { 'Reboot', function() os.execute('reboot') end }, 29 | { 'Shutdown', function() os.execute('poweroff') end } 30 | } 31 | 32 | -- The widgets to return. 33 | local _M = {} 34 | 35 | _M.mainmenu = awful.menu { 36 | items = { 37 | { 'Terminal' , apps.terminal }, 38 | { 'Browser' , apps.browser }, 39 | { 'Awesome' , _S.awesome }, 40 | { 'Power' , _S.power } 41 | }, 42 | theme = { 43 | -- Dimensions (per cell). 44 | width = dpi(160), 45 | height = dpi(32), 46 | -- Colors. 47 | bg_normal = colors.normal, 48 | bg_focus = colors.focus, 49 | -- Image. 50 | submenu_icon = dot 51 | } 52 | } 53 | 54 | -- Rounded corners on the menu... 55 | _M.mainmenu.wibox.bg = beautiful.transparent 56 | _M.mainmenu.wibox.shape = function(c, w, h) 57 | gears.shape.rounded_rect(c, w, h, dpi(8)) 58 | end 59 | _M.mainmenu.wibox:set_widget(wibox.widget { 60 | widget = wibox.container.background, 61 | bg = colors.normal, 62 | shape = function(c, w, h) 63 | gears.shape.rounded_rect(c, w, h, dpi(8)) 64 | end, 65 | { 66 | widget = wibox.container.margin, 67 | margins = dpi(16), 68 | { 69 | widget = wibox.container.background, 70 | shape = function(c, w, h) 71 | gears.shape.rounded_rect(c, w, h, dpi(8)) 72 | end, 73 | _M.mainmenu.wibox.widget 74 | } 75 | } 76 | }) 77 | 78 | -- ...and its submenus. 79 | awful.menu.original_new = awful.menu.new 80 | function awful.menu.new(...) 81 | local sub = awful.menu.original_new(...) 82 | sub.wibox.bg = beautiful.transparent 83 | sub.wibox.shape = function(c, w, h) 84 | gears.shape.rounded_rect(c, w, h, dpi(8)) 85 | end 86 | sub.wibox:set_widget(wibox.widget { 87 | widget = wibox.container.background, 88 | shape = function(c, w, h) 89 | gears.shape.rounded_rect(c, w, h, dpi(8)) 90 | end, 91 | bg = colors.normal, 92 | { 93 | widget = wibox.container.margin, 94 | margins = dpi(16), 95 | { 96 | widget = wibox.container.background, 97 | shape = function(c, w, h) 98 | gears.shape.rounded_rect(c, w, h, dpi(8)) 99 | end, 100 | sub.wibox.widget 101 | } 102 | } 103 | }) 104 | return sub 105 | end 106 | 107 | return _M 108 | -------------------------------------------------------------------------------- /widgets/notification/colors.lua: -------------------------------------------------------------------------------- 1 | local colorscheme = require('theme.colorscheme') 2 | 3 | local colors = {} 4 | if require('config.user').dark then 5 | colors = { 6 | bg_normal = colorscheme.black, 7 | bg_light = colorscheme.gray100, 8 | mid_dark = colorscheme.gray90, 9 | fg_dark = colorscheme.gray30, 10 | fg_normal = colorscheme.gray10, 11 | red = colorscheme.red, 12 | green = colorscheme.green 13 | } 14 | else 15 | colors = { 16 | bg_normal = colorscheme.gray100, 17 | bg_light = colorscheme.gray80, 18 | mid_dark = colorscheme.gray60, 19 | fg_dark = colorscheme.gray20, 20 | fg_normal = colorscheme.white, 21 | red = colorscheme.red_dark, 22 | green = colorscheme.green_dark 23 | } 24 | end 25 | 26 | return colors 27 | -------------------------------------------------------------------------------- /widgets/notification/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | normal = require(... .. '.normal') 3 | } 4 | -------------------------------------------------------------------------------- /widgets/notification/normal.lua: -------------------------------------------------------------------------------- 1 | local naughty = require('naughty') 2 | local beautiful = require('beautiful') 3 | local gears = require('gears') 4 | local awful = require('awful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local helpers = require('helpers') 10 | local rubato = require('modules.rubato') 11 | 12 | local colors = require('widgets.notification.colors') 13 | 14 | local def_icon = 15 | gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. 'theme/assets/awesome.svg', colors.fg_normal) 16 | 17 | local _W = {} 18 | 19 | function _W.title(n) 20 | return wibox.widget { 21 | widget = wibox.container.scroll.horizontal, 22 | speed = 66, 23 | rate = 60, 24 | step_function = wibox.container.scroll.step_functions.nonlinear_back_and_forth, 25 | { 26 | widget = wibox.widget.textbox, 27 | markup = n.title ~= nil and '' .. n.title .. '' 28 | or 'Notification', 29 | font = beautiful.font_sans .. dpi(9), 30 | halign = 'center', 31 | valign = 'center' 32 | } 33 | } 34 | end 35 | 36 | function _W.body(n) 37 | return wibox.widget { 38 | widget = wibox.container.scroll.vertical, 39 | speed = 66, 40 | rate = 60, 41 | step_function = wibox.container.scroll.step_functions.nonlinear_back_and_forth, 42 | { 43 | widget = wibox.widget.textbox, 44 | markup = gears.string.xml_unescape(n.message), 45 | font = beautiful.font_sans .. dpi(9), 46 | halign = 'center', 47 | valign = 'center' 48 | } 49 | } 50 | end 51 | 52 | function _W.image(n) 53 | return wibox.widget { 54 | widget = wibox.widget.imagebox, 55 | image = n.icon and helpers.crop_surface(1, gears.surface.load_uncached(n.icon)) 56 | or def_icon, 57 | resize = true, 58 | halign = 'center', 59 | valign = 'center', 60 | clip_shape = function(c, w, h) 61 | gears.shape.rounded_rect(c, w, h, dpi(4)) 62 | end, 63 | forced_height = dpi(40), 64 | forced_width = dpi(40) 65 | } 66 | end 67 | 68 | function _W.timeout() 69 | return wibox.widget { 70 | widget = wibox.widget.progressbar, 71 | min_value = 0, 72 | max_value = 100, 73 | value = 0, 74 | background_color = colors.bg_light, 75 | color = { 76 | type = 'linear', 77 | from = { 0, 0 }, 78 | to = { 0, 100 }, 79 | stops = { { 0, colors.green }, { 1, colors.green .. '8c' } } 80 | }, 81 | forced_height = dpi(6) 82 | } 83 | end 84 | 85 | function _W.actions(n) 86 | if #n.actions == 0 then return nil end 87 | return wibox.widget { 88 | widget = wibox.container.margin, 89 | margins = { top = dpi(2) }, 90 | { 91 | widget = naughty.list.actions, 92 | notification = n, 93 | base_layout = wibox.widget { 94 | spacing = dpi(4), 95 | layout = wibox.layout.flex.horizontal 96 | }, 97 | style = { 98 | underline_normal = false, 99 | underline_selected = false, 100 | bg_normal = colors.mid_dark, 101 | shape_normal = function(c, w, h) 102 | gears.shape.rounded_rect(c, w, h, dpi(6)) 103 | end, 104 | border_width = 0 105 | }, 106 | widget_template = { 107 | widget = wibox.container.background, 108 | bg = colors.mid_dark, 109 | id = 'background_role', 110 | { 111 | widget = wibox.container.margin, 112 | margins = dpi(4), 113 | { 114 | widget = wibox.widget.textbox, 115 | id = 'text_role', 116 | font = beautiful.font_sans .. dpi(7) 117 | } 118 | } 119 | } 120 | } 121 | } 122 | end 123 | 124 | function _W.close(n) 125 | local widget = wibox.widget { 126 | widget = wibox.container.margin, 127 | margins = dpi(9), 128 | { 129 | widget = wibox.container.background, 130 | shape = gears.shape.circle, 131 | bg = colors.red .. '80', 132 | id = 'bg_role', 133 | forced_width = dpi(13) 134 | }, 135 | buttons = { awful.button({}, 1, function() n:destroy() end) }, 136 | set_bg = function(self, bg) 137 | self:get_children_by_id('bg_role')[1].bg = bg 138 | end 139 | } 140 | widget:connect_signal('mouse::enter', function() 141 | widget.bg = colors.red 142 | end) 143 | widget:connect_signal('mouse::leave', function() 144 | widget.bg = colors.red .. '80' 145 | end) 146 | 147 | return widget 148 | end 149 | 150 | -- Default layout 151 | return function(n) 152 | -- Store the original timeout, and change it to a big, unreachable, number. 153 | local timeout = n.timeout 154 | n.timeout = 999999 155 | 156 | local timeout_bar = _W.timeout() 157 | 158 | local widget = naughty.layout.box { 159 | notification = n, 160 | cursor = 'hand2', 161 | border_width = 0, 162 | bg = beautiful.transparent, 163 | shape = function(c, w, h) 164 | gears.shape.rounded_rect(c, w, h, dpi(6)) 165 | end, 166 | widget_template = { 167 | widget = wibox.container.constraint, 168 | strategy = 'max', 169 | height = dpi(320), 170 | { 171 | widget = wibox.container.constraint, 172 | strategy = 'exact', 173 | width = dpi(320), 174 | { 175 | widget = wibox.container.background, 176 | bg = colors.bg_normal, 177 | shape = function(c, w, h) 178 | gears.shape.rounded_rect(c, w, h, dpi(8)) 179 | end, 180 | { 181 | layout = wibox.layout.align.vertical, 182 | { 183 | widget = wibox.container.constraint, 184 | strategy = 'exact', 185 | height = dpi(40), 186 | { 187 | widget = wibox.container.background, 188 | bg = colors.bg_normal, 189 | { 190 | widget = wibox.container.margin, 191 | margins = { 192 | left = dpi(16), right = dpi(4) 193 | }, 194 | { 195 | layout = wibox.layout.align.horizontal, 196 | expand = 'none', 197 | { 198 | widget = wibox.container.constraint, 199 | strategy = 'max', 200 | width = dpi(248), 201 | _W.title(n) 202 | }, 203 | nil, 204 | _W.close(n) 205 | } 206 | } 207 | } 208 | }, 209 | { 210 | widget = wibox.container.background, 211 | bg = { 212 | type = 'linear', 213 | from = { 0, 0 }, 214 | to = { 0, 85 }, 215 | stops = { { 0, colors.bg_light .. '8c' }, { 1, colors.bg_normal } } 216 | }, 217 | { 218 | layout = wibox.layout.align.vertical, 219 | expand = 'none', 220 | { 221 | widget = wibox.container.margin, 222 | margins = dpi(16), 223 | { 224 | layout = wibox.layout.fixed.horizontal, 225 | spacing = dpi(12), 226 | { 227 | layout = wibox.layout.align.vertical, 228 | _W.image(n), 229 | nil, nil 230 | }, 231 | { 232 | widget = wibox.container.constraint, 233 | strategy = 'max', 234 | height = dpi(280), 235 | { 236 | layout = wibox.layout.fixed.vertical, 237 | spacing = dpi(4), 238 | _W.body(n), 239 | _W.actions(n) 240 | } 241 | } 242 | } 243 | }, 244 | nil, 245 | timeout_bar 246 | } 247 | } 248 | } 249 | } 250 | } 251 | } 252 | } 253 | 254 | -- Set an animation for the timeout. 255 | local anim = rubato.timed { 256 | intro = 0, 257 | duration = timeout, 258 | subscribed = function(pos, time) 259 | timeout_bar.value = pos 260 | if time == timeout then n:destroy() end 261 | end 262 | } 263 | -- Whenever the notification is hovered, the animation (and timeout) are paused. 264 | widget:connect_signal('mouse::enter', function() 265 | anim.pause = true 266 | end) 267 | widget:connect_signal('mouse::leave', function() 268 | anim.pause = false 269 | end) 270 | anim.target = 100 271 | 272 | widget.buttons = {} 273 | return widget 274 | end 275 | -------------------------------------------------------------------------------- /widgets/timepanel/init.lua: -------------------------------------------------------------------------------- 1 | local beautiful = require('beautiful') 2 | local wibox = require('wibox') 3 | local gears = require('gears') 4 | 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local widgets = require(... .. '.module') 8 | 9 | local title = wibox.widget { 10 | widget = wibox.container.background, 11 | bg = widgets.colors.bg_normal, 12 | { 13 | widget = wibox.container.margin, 14 | margins = { 15 | left = dpi(20), right = dpi(20), 16 | top = dpi(16), bottom = dpi(16) 17 | }, 18 | { 19 | widget = wibox.widget.textbox, 20 | markup = 'Time and Weather', 21 | font = beautiful.font_sans .. dpi(11) 22 | } 23 | } 24 | } 25 | 26 | local panel = wibox { 27 | ontop = true, 28 | visible = false, 29 | width = dpi(412), 30 | height = dpi(848), 31 | x = dpi(8), 32 | y = dpi(56), 33 | bg = beautiful.transparent, 34 | shape = function(c, w, h) 35 | gears.shape.rounded_rect(c, w, h, dpi(6)) 36 | end, 37 | widget = { 38 | widget = wibox.container.background, 39 | bg = widgets.colors.bg_normal, 40 | shape = function(c, w, h) 41 | gears.shape.rounded_rect(c, w, h, dpi(8)) 42 | end, 43 | { 44 | -- Yes there are 2 stacked backgrounds, cry about it. 45 | widget = wibox.container.background, 46 | bg = { 47 | type = 'linear', 48 | from = { 0, dpi(50) }, 49 | to = { 0, dpi(125) }, 50 | stops = { 51 | { 0, widgets.colors.bg_light .. '8c' }, { 1, widgets.colors.bg_normal } 52 | } 53 | }, 54 | { 55 | -- A portion of my sanity was lost here. 56 | layout = wibox.layout.align.vertical, 57 | title, 58 | nil, 59 | { 60 | widget = wibox.container.margin, 61 | margins = { 62 | left = dpi(24), right = dpi(24), 63 | bottom = dpi(24), top = dpi(16) 64 | }, 65 | { 66 | -- wibox.layout.align I fucking hate you. 67 | layout = wibox.layout.align.vertical, 68 | widgets.calendar(), 69 | { 70 | widget = wibox.container.margin, 71 | margins = { 72 | top = dpi(12), bottom = dpi(12) 73 | }, 74 | { 75 | widget = wibox.widget.separator, 76 | color = widgets.colors.bg_light, 77 | forced_height = dpi(6) 78 | } 79 | }, 80 | widgets.weather() 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | function panel:show() 89 | self.visible = not self.visible 90 | end 91 | 92 | return panel 93 | -------------------------------------------------------------------------------- /widgets/timepanel/module/calendar.lua: -------------------------------------------------------------------------------- 1 | -- Based off rxyhn's Yoru calendar. 2 | -- See: https://github.com/rxyhn/yoru/blob/main/config/awesome/ui/panels/info-panel/calendar.lua 3 | 4 | local awful = require('awful') 5 | local beautiful = require('beautiful') 6 | local wibox = require('wibox') 7 | local gears = require('gears') 8 | 9 | local dpi = beautiful.xresources.apply_dpi 10 | 11 | local colors = require('widgets.timepanel.module.colors') 12 | 13 | local function weekday_widget(name) 14 | return wibox.widget { 15 | widget = wibox.container.background, 16 | fg = colors.fg_dark, 17 | { 18 | widget = wibox.container.margin, 19 | margins = { left = dpi(8) }, 20 | { 21 | widget = wibox.widget.textbox, 22 | text = name 23 | } 24 | } 25 | } 26 | end 27 | 28 | local function day_widget(date, is_current, other_month) 29 | local color = colors.fg_normal 30 | if is_current then 31 | color = colors.bg_normal 32 | elseif other_month then 33 | color = colors.mid_light 34 | end 35 | 36 | return wibox.widget { 37 | widget = wibox.container.background, 38 | bg = is_current and colors.green or beautiful.transparent, 39 | fg = color, 40 | shape = function(c, w, h) 41 | gears.shape.rounded_rect(c, w, h, dpi(4)) 42 | end, 43 | { 44 | widget = wibox.container.margin, 45 | margins = { 46 | left = dpi(8), right = dpi(8), 47 | top = dpi(4), bottom = dpi(4) 48 | }, 49 | { 50 | widget = wibox.widget.textbox, 51 | halign = 'center', 52 | text = date 53 | } 54 | } 55 | } 56 | end 57 | 58 | local calendar = { mt = {} } 59 | 60 | function calendar:set_date(date) 61 | self.date = date 62 | self.days:reset() 63 | self.days:add(weekday_widget('Su')) 64 | self.days:add(weekday_widget('Mo')) 65 | self.days:add(weekday_widget('Tu')) 66 | self.days:add(weekday_widget('We')) 67 | self.days:add(weekday_widget('Th')) 68 | self.days:add(weekday_widget('Fr')) 69 | self.days:add(weekday_widget('Sa')) 70 | 71 | local current_date = os.date('*t') 72 | 73 | local first_day = os.date('*t', os.time({ year = date.year, month = date.month, day = 1})) 74 | local last_day = os.date('*t', os.time({ year = date.year, month = date.month + 1, day = 0})) 75 | local month_days = last_day.day 76 | 77 | self.month:set_text(os.date('%B\n%Y', os.time({ year = date.year, month = date.month, day = 1 }))) 78 | 79 | local days_to_add_at_month_start = first_day.wday - 1 80 | local days_to_add_at_month_end = 42 - last_day.day - days_to_add_at_month_start 81 | 82 | local previous_month_last_day = os.date("*t", os.time({ year = date.year, month = date.month, day = 0 })).day 83 | for day = previous_month_last_day - days_to_add_at_month_start, previous_month_last_day - 1, 1 do 84 | self.days:add(day_widget(day, false, true)) 85 | end 86 | 87 | for day = 1, month_days do 88 | local is_current = day == current_date.day and date.month == current_date.month 89 | self.days:add(day_widget(day, is_current, false)) 90 | end 91 | 92 | for day = 1, days_to_add_at_month_end do 93 | self.days:add(day_widget(day, false, true)) 94 | end 95 | end 96 | 97 | function calendar:update_date() 98 | self:set_date(os.date("*t")) 99 | end 100 | 101 | function calendar:increase_date() 102 | local new_calendar_month = self.date.month + 1 103 | self:set_date({ year = self.date.year, month = new_calendar_month, day = self.date.day }) 104 | end 105 | 106 | function calendar:decrease_date() 107 | local new_calendar_month = self.date.month - 1 108 | self:set_date({ year = self.date.year, month = new_calendar_month, day = self.date.day }) 109 | end 110 | 111 | -- So here's where things actually go down 112 | local function new() 113 | local ret = gears.object({}) 114 | gears.table.crush(ret, calendar, true) 115 | 116 | ret.month = wibox.widget { 117 | widget = wibox.container.background, 118 | bg = beautiful.transparent, 119 | shape = function(c, w, h) 120 | gears.shape.rounded_rect(c, w, h, dpi(4)) 121 | end, 122 | { 123 | widget = wibox.container.margin, 124 | margins = { 125 | left = dpi(8), right = dpi(8), 126 | top = dpi(4), bottom = dpi(4) 127 | }, 128 | { 129 | widget = wibox.widget.textbox, 130 | text = os.date('%B\n%Y'), 131 | halign = 'right', 132 | id = 'text_role' 133 | } 134 | }, 135 | buttons = { 136 | awful.button(nil, 1, function() 137 | ret:update_date() 138 | end) 139 | }, 140 | set_text = function(self, text) 141 | self:get_children_by_id('text_role')[1].text = text 142 | end 143 | } 144 | ret.month:connect_signal('mouse::enter', function() 145 | ret.month.bg = colors.bg_light 146 | end) 147 | ret.month:connect_signal('mouse::leave', function() 148 | ret.month.bg = beautiful.transparent 149 | end) 150 | 151 | local function button(text, action) 152 | local widget = wibox.widget { 153 | widget = wibox.container.background, 154 | bg = beautiful.transparent, 155 | shape = function(c, w, h) 156 | gears.shape.rounded_rect(c, w, h, dpi(4)) 157 | end, 158 | { 159 | widget = wibox.container.margin, 160 | margins = { 161 | left = dpi(6), right = dpi(6), 162 | top = dpi(4), bottom = dpi(4) 163 | }, 164 | { 165 | widget = wibox.widget.textbox, 166 | text = text, 167 | halign = 'center', 168 | valign = 'center' 169 | } 170 | }, 171 | buttons = { 172 | awful.button(nil, 1, action) 173 | } 174 | } 175 | widget:connect_signal('mouse::enter', function() 176 | widget.bg = colors.bg_light 177 | end) 178 | widget:connect_signal('mouse::leave', function() 179 | widget.bg = beautiful.transparent 180 | end) 181 | return widget 182 | end 183 | local month = wibox.widget { 184 | layout = wibox.layout.fixed.vertical, 185 | ret.month, 186 | { 187 | widget = wibox.container.place, 188 | halign = 'right', 189 | { 190 | layout = wibox.layout.fixed.horizontal, 191 | button('prev', function() ret:decrease_date() end), 192 | nil, 193 | button('next', function() ret:increase_date() end), 194 | } 195 | } 196 | } 197 | 198 | ret.days = wibox.widget { 199 | layout = wibox.layout.grid, 200 | forced_num_rows = 6, 201 | forced_num_cols = 7, 202 | spacing = dpi(5), 203 | expand = true 204 | } 205 | 206 | local clock = wibox.widget { 207 | layout = wibox.layout.fixed.vertical, 208 | { 209 | widget = wibox.container.place, 210 | halign = 'right', 211 | { 212 | layout = wibox.layout.fixed.vertical, 213 | -- Hour 214 | { 215 | widget = wibox.widget.textclock, 216 | format = '%H', 217 | font = beautiful.font_mono .. dpi(48) 218 | }, 219 | -- The color bar 220 | { 221 | widget = wibox.container.place, 222 | halign = 'center', 223 | { 224 | layout = wibox.layout.fixed.horizontal, 225 | { 226 | widget = wibox.container.background, 227 | bg = colors.red, 228 | forced_width = dpi(24), 229 | forced_height = dpi(3) 230 | }, 231 | { 232 | widget = wibox.container.background, 233 | bg = colors.yellow, 234 | forced_width = dpi(24), 235 | forced_height = dpi(3) 236 | }, 237 | { 238 | widget = wibox.container.background, 239 | bg = colors.green, 240 | forced_width = dpi(24), 241 | forced_height = dpi(3) 242 | } 243 | } 244 | }, 245 | -- Minute 246 | { 247 | widget = wibox.widget.textclock, 248 | format = '%M', 249 | font = beautiful.font_mono .. dpi(48) 250 | } 251 | } 252 | }, 253 | { 254 | widget = wibox.container.background, 255 | fg = colors.fg_dark, 256 | month 257 | } 258 | } 259 | 260 | local widget = wibox.widget { 261 | widget = wibox.container.margin, 262 | margins = { 263 | left = dpi(8), right = dpi(8) 264 | }, 265 | { 266 | layout = wibox.layout.align.horizontal, 267 | ret.days, 268 | nil, 269 | clock 270 | } 271 | } 272 | 273 | ret:set_date(os.date('*t')) 274 | gears.table.crush(widget, calendar, true) 275 | return widget 276 | end 277 | 278 | function calendar.mt:__call() 279 | return new() 280 | end 281 | 282 | return setmetatable(calendar, calendar.mt) 283 | -------------------------------------------------------------------------------- /widgets/timepanel/module/colors.lua: -------------------------------------------------------------------------------- 1 | local colorscheme = require('theme.colorscheme') 2 | 3 | local colors = {} 4 | if require('config.user').dark then 5 | colors = { 6 | bg_normal = colorscheme.black, 7 | bg_light = colorscheme.gray100, 8 | mid_dark = colorscheme.gray90, 9 | mid_light = colorscheme.gray70, 10 | fg_dark = colorscheme.gray30, 11 | fg_normal = colorscheme.gray10, 12 | red = colorscheme.red, 13 | green = colorscheme.green, 14 | yellow = colorscheme.yellow 15 | } 16 | else 17 | colors = { 18 | bg_normal = colorscheme.gray100, 19 | bg_light = colorscheme.gray80, 20 | mid_dark = colorscheme.gray60, 21 | mid_light = colorscheme.gray50, 22 | fg_dark = colorscheme.gray20, 23 | fg_normal = colorscheme.white, 24 | red = colorscheme.red_dark, 25 | green = colorscheme.green_dark, 26 | yellow = colorscheme.yellow_dark 27 | } 28 | end 29 | 30 | return colors 31 | -------------------------------------------------------------------------------- /widgets/timepanel/module/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | colors = require(... .. '.colors'), 3 | calendar = require(... .. '.calendar'), 4 | weather = require(... .. '.weather') 5 | } 6 | -------------------------------------------------------------------------------- /widgets/timepanel/module/weather.lua: -------------------------------------------------------------------------------- 1 | local beautiful = require('beautiful') 2 | local wibox = require('wibox') 3 | local gears = require('gears') 4 | 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local overflow = require('modules.overflow') 8 | local colors = require('widgets.timepanel.module.colors') 9 | 10 | -- The OpenWeather API outputs icon codes, we need to actually 11 | -- do something with those if we intend to use icons. 12 | local icon_dir = require('gears.filesystem').get_configuration_dir() .. 'theme/assets/weather/' 13 | local icons = { 14 | -- Daytime 15 | ['01d'] = 'clear', 16 | ['02d'] = 'few-clouds', 17 | ['04d'] = 'few-clouds', 18 | ['03d'] = 'clouds', 19 | ['09d'] = 'rain-light', 20 | ['10d'] = 'rain', 21 | ['11d'] = 'storm', 22 | ['13d'] = 'snow', 23 | ['50d'] = 'fog', 24 | -- Nighttime 25 | ['01n'] = 'clear-n', 26 | ['02n'] = 'few-clouds-n', 27 | ['03n'] = 'few-clouds-n', 28 | ['04n'] = 'clouds', 29 | ['09n'] = 'rain-light', 30 | ['10n'] = 'rain', 31 | ['11n'] = 'storm', 32 | ['13n'] = 'snow', 33 | ['50n'] = 'fog' 34 | } 35 | 36 | -- Template to create a 'climate by hour' widget. 37 | local function hour_weather(index) 38 | return wibox.widget { 39 | widget = wibox.container.constraint, 40 | { 41 | layout = wibox.layout.fixed.vertical, 42 | spacing = dpi(6), 43 | { 44 | widget = wibox.container.background, 45 | fg = colors.fg_dark, 46 | { 47 | widget = wibox.widget.textbox, 48 | text = '+' .. index .. ':00', 49 | halign = 'center', 50 | id = 'time_role' 51 | } 52 | }, 53 | { 54 | widget = wibox.widget.imagebox, 55 | forced_width = dpi(40), 56 | forced_height = dpi(40), 57 | halign = 'center', 58 | image = icon_dir .. 'clouds.svg', 59 | stylesheet = string.format('*{ fill: %s }', colors.fg_normal), 60 | id = 'icon_role' 61 | }, 62 | { 63 | layout = wibox.layout.fixed.vertical, 64 | { 65 | widget = wibox.widget.textbox, 66 | markup = 'N/A', 67 | halign = 'center', 68 | id = 'temp_role' 69 | }, 70 | { 71 | widget = wibox.container.background, 72 | fg = colors.fg_dark, 73 | { 74 | widget = wibox.widget.textbox, 75 | markup = 'N/A', 76 | halign = 'center', 77 | id = 'hum_role' 78 | } 79 | } 80 | } 81 | }, 82 | set_temp = function(self, text) 83 | self:get_children_by_id('temp_role')[1].markup = text .. '°C' 84 | end, 85 | set_image = function(self, icon) 86 | self:get_children_by_id('icon_role')[1].image = icon 87 | end, 88 | set_hum = function(self, text) 89 | self:get_children_by_id('hum_role')[1].markup = text .. '%' 90 | end, 91 | set_time = function(self, time) 92 | -- hours beyond 20:00 are scary 93 | if time + index - 24 >= 0 then 94 | time = time + index - 24 95 | else time = time + index 96 | end 97 | self:get_children_by_id('time_role')[1].markup = math.floor(time) .. ':00' 98 | end 99 | } 100 | end 101 | 102 | -- I feel like more than 16 hours is pretty overkill. 103 | -- Why not just add them directly? I don't know why but it doesn't work. I'm 104 | -- guessing there is something about Lua object creation I don't understand. 105 | local hourly = {} 106 | for i = 1, 16, 1 do 107 | table.insert(hourly, hour_weather(i)) 108 | end 109 | 110 | local hours = wibox.widget { 111 | layout = overflow.horizontal, 112 | spacing = dpi(20), 113 | scrollbar_enabled = false 114 | } 115 | for _, v in ipairs(hourly) do 116 | hours:add(v) 117 | end 118 | 119 | -- Today's forecast 120 | local today = wibox.widget { 121 | widget = wibox.container.background, 122 | bg = colors.bg_light, 123 | shape = function(c, w, h) 124 | gears.shape.rounded_rect(c, w, h, dpi(6)) 125 | end, 126 | { 127 | layout = wibox.layout.stack, 128 | { 129 | widget = wibox.widget.imagebox, 130 | forced_width = dpi(0), 131 | forced_height = dpi(0), 132 | image = icon_dir .. 'clouds.svg', 133 | stylesheet = string.format('*{ fill: %s; }', colors.mid_light .. '9f'), 134 | id = 'icon_box' 135 | }, 136 | { 137 | widget = wibox.container.margin, 138 | margins = dpi(24), 139 | { 140 | layout = wibox.layout.fixed.vertical, 141 | spacing = dpi(8), 142 | { 143 | widget = wibox.widget.textbox, 144 | markup = [[Today's forecast]], 145 | font = beautiful.font_sans .. dpi(13) 146 | }, 147 | { 148 | widget = wibox.container.constraint, 149 | strategy = 'exact', 150 | height = dpi(90), 151 | { 152 | layout = wibox.layout.align.horizontal, 153 | { 154 | layout = wibox.layout.fixed.vertical, 155 | spacing = dpi(4), 156 | { 157 | widget = wibox.widget.textbox, 158 | markup = 'Weather unknown', 159 | font = beautiful.font_sans .. dpi(16), 160 | id = 'description_box' 161 | }, 162 | { 163 | layout = wibox.layout.fixed.vertical, 164 | { 165 | widget = wibox.container.background, 166 | fg = colors.fg_dark, 167 | { 168 | widget = wibox.widget.textbox, 169 | markup = 'Max/Min: N/A', 170 | id = 'min_max_box' 171 | } 172 | }, 173 | { 174 | widget = wibox.container.background, 175 | fg = colors.fg_dark, 176 | { 177 | widget = wibox.widget.textbox, 178 | markup = 'Humidity: N/A', 179 | id = 'humidity_box' 180 | } 181 | } 182 | } 183 | }, 184 | nil, 185 | { 186 | layout = wibox.layout.fixed.vertical, 187 | { 188 | widget = wibox.widget.textbox, 189 | markup = 'N/A', 190 | font = beautiful.font_sans .. dpi(24), 191 | id = 'temperature_box' 192 | }, 193 | { 194 | widget = wibox.container.background, 195 | fg = colors.fg_dark, 196 | { 197 | widget = wibox.widget.textbox, 198 | markup = '(N/A)', 199 | font = beautiful.font_sans .. dpi(16), 200 | halign = 'right', 201 | id = 'feels_like_box' 202 | } 203 | } 204 | } 205 | } 206 | }, 207 | { 208 | widget = wibox.container.place, 209 | halign = 'center', 210 | hours 211 | } 212 | } 213 | } 214 | }, 215 | set_description = function(self, desc) 216 | self:get_children_by_id('description_box')[1].markup = '' .. desc .. '' 217 | end, 218 | set_temperature = function(self, temp) 219 | self:get_children_by_id('temperature_box')[1].markup = '' .. temp .. '°C' 220 | end, 221 | set_feels_like = function(self, feels_like) 222 | self:get_children_by_id('feels_like_box')[1].markup = '(' .. feels_like .. '°C)' 223 | end, 224 | set_icon = function(self, icon) 225 | self:get_children_by_id('icon_box')[1].image = icon 226 | end, 227 | set_humidity = function(self, hum) 228 | self:get_children_by_id('humidity_box')[1].markup = 'Humidity: ' .. hum .. '%' 229 | end, 230 | set_min_max = function(self, string) 231 | self:get_children_by_id('min_max_box')[1].markup = 'Max/Min: ' .. string .. '°C' 232 | end 233 | } 234 | 235 | -- Template for a daily forecast 236 | local weekdays = { 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' } 237 | local function day_weather(index) 238 | return wibox.widget { 239 | widget = wibox.container.constraint, 240 | strategy = 'min', 241 | width = dpi(60), 242 | { 243 | layout = wibox.layout.fixed.vertical, 244 | spacing = dpi(6), 245 | { 246 | widget = wibox.container.background, 247 | fg = colors.fg_dark, 248 | { 249 | widget = wibox.widget.textbox, 250 | markup = '+' .. index .. ' days', 251 | halign = 'center', 252 | id = 'day_role' 253 | } 254 | }, 255 | { 256 | widget = wibox.widget.imagebox, 257 | forced_width = dpi(40), 258 | forced_height = dpi(40), 259 | halign = 'center', 260 | image = icon_dir .. 'clouds.svg', 261 | stylesheet = string.format('*{ fill: %s }', colors.fg_normal), 262 | id = 'icon_role' 263 | }, 264 | { 265 | layout = wibox.layout.fixed.vertical, 266 | { 267 | widget = wibox.container.place, 268 | halign = 'center', 269 | { 270 | layout = wibox.layout.fixed.horizontal, 271 | { 272 | widget = wibox.widget.textbox, 273 | markup = 'N', 274 | id = 'max_temp_role' 275 | }, 276 | { 277 | widget = wibox.widget.textbox, 278 | markup = '/' 279 | }, 280 | { 281 | widget = wibox.widget.textbox, 282 | markup = 'A', 283 | id = 'min_temp_role' 284 | } 285 | } 286 | }, 287 | { 288 | widget = wibox.container.background, 289 | fg = colors.fg_dark, 290 | { 291 | widget = wibox.widget.textbox, 292 | markup = 'N/A', 293 | halign = 'center', 294 | id = 'humidity_role' 295 | } 296 | } 297 | } 298 | }, 299 | set_day = function(self, day) 300 | if day + index > 7 then 301 | day = day + index - 7 302 | else day = day + index 303 | end 304 | self:get_children_by_id('day_role')[1].markup = weekdays[day] 305 | end, 306 | set_image = function(self, icon) 307 | self:get_children_by_id('icon_role')[1].image = icon 308 | end, 309 | set_max = function(self, temp) 310 | self:get_children_by_id('max_temp_role')[1].markup = temp 311 | end, 312 | set_min = function(self, temp) 313 | self:get_children_by_id('min_temp_role')[1].markup = temp .. '°C' 314 | end, 315 | set_humidity = function(self, hum) 316 | self:get_children_by_id('humidity_role')[1].markup = hum .. '%' 317 | end 318 | } 319 | end 320 | 321 | local daily = {} 322 | for i = 1, 7, 1 do 323 | table.insert(daily, day_weather(i)) 324 | end 325 | 326 | local days = wibox.widget { 327 | layout = overflow.horizontal, 328 | spacing = dpi(16), 329 | scrollbar_enabled = false 330 | } 331 | for _, v in ipairs(daily) do 332 | days:add(v) 333 | end 334 | 335 | local week = wibox.widget { 336 | widget = wibox.container.background, 337 | bg = colors.bg_light, 338 | shape = function(c, w, h) 339 | gears.shape.rounded_rect(c, w, h, dpi(4)) 340 | end, 341 | { 342 | widget = wibox.container.margin, 343 | margins = dpi(24), 344 | { 345 | layout = wibox.layout.fixed.vertical, 346 | spacing = dpi(8), 347 | { 348 | widget = wibox.widget.textbox, 349 | markup = [[Week's forecast]], 350 | font = beautiful.font_sans .. dpi(13), 351 | halign = 'right' 352 | }, 353 | days 354 | } 355 | } 356 | } 357 | 358 | awesome.connect_signal('connect::weather', function(out) 359 | --- Today's forecast 360 | -- Current info 361 | today.description = out.description 362 | today.temperature = out.temperature 363 | today.feels_like = out.feels_like 364 | today.icon = out.icon 365 | today.humidity = out.humidity 366 | -- Find today's (remainder) min and max temp 367 | local time = tonumber(os.date('%H')) 368 | if time < 23 then 369 | local max = -math.huge 370 | local min = math.huge 371 | for i = 1, #out.by_hour - time, 1 do 372 | if out.by_hour[i].temp > max then 373 | max = math.ceil(out.by_hour[i].temp) 374 | end 375 | if out.by_hour[i].temp < min then 376 | min = math.floor(out.by_hour[i].temp) 377 | end 378 | end 379 | today.min_max = max .. '/' .. min 380 | end 381 | 382 | -- Hourly info 383 | for i, w in ipairs(hourly) do 384 | w.time = os.date('%H') 385 | w.image = icon_dir .. icons[out.by_hour[i].weather[1].icon] .. '.svg' 386 | w.temp = math.floor(out.by_hour[i].temp) 387 | w.hum = math.floor(out.by_hour[i].humidity) 388 | end 389 | 390 | --- Week's forecast 391 | for i, w in ipairs(daily) do 392 | w.day = os.date('*t').wday 393 | w.image = icon_dir .. icons[out.by_day[i].weather[1].icon] .. '.svg' 394 | w.max = math.floor(out.by_day[i].temp.day) 395 | w.min = math.floor(out.by_day[i].temp.night) 396 | w.humidity = out.by_day[i].humidity 397 | end 398 | end) 399 | 400 | return function() 401 | return wibox.widget { 402 | layout = wibox.layout.fixed.vertical, 403 | spacing = dpi(12), 404 | today, 405 | week 406 | } 407 | end 408 | -------------------------------------------------------------------------------- /widgets/titlebar/colors.lua: -------------------------------------------------------------------------------- 1 | local colorscheme = require('theme.colorscheme') 2 | 3 | local colors = {} 4 | if require('config.user').dark then 5 | colors = { 6 | bg_normal = colorscheme.gray100, 7 | unfocused = colorscheme.gray90, 8 | focused = colorscheme.gray80, 9 | fg_dark = colorscheme.gray30, 10 | fg_normal = colorscheme.gray10, 11 | red = colorscheme.red, 12 | green = colorscheme.green 13 | } 14 | else 15 | colors = { 16 | bg_normal = colorscheme.gray10, 17 | unfocused = colorscheme.gray30, 18 | focused = colorscheme.gray40, 19 | fg_dark = colorscheme.gray80, 20 | fg_normal = colorscheme.black, 21 | red = colorscheme.red_dark, 22 | green = colorscheme.green_dark 23 | } 24 | end 25 | 26 | return colors 27 | -------------------------------------------------------------------------------- /widgets/titlebar/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | normal = require('widgets.titlebar.normal') 3 | } 4 | -------------------------------------------------------------------------------- /widgets/titlebar/normal.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local wibox = require('wibox') 3 | local beautiful = require('beautiful') 4 | 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local C_num_borders = 2 8 | local border_size = require('config.user').borders or 0 9 | local colors = require('widgets.titlebar.colors') 10 | 11 | local function make_border(w, h) 12 | return wibox.widget { 13 | widget = wibox.container.background, 14 | bg = colors.bg_normal, 15 | forced_width = w and 4 * w, 16 | forced_height = h and 4 * h 17 | } 18 | end 19 | 20 | local function make_focus(c, w, h) 21 | local border = wibox.widget { 22 | widget = wibox.container.background, 23 | bg = colors.unfocused, 24 | forced_width = w, 25 | forced_height = h 26 | } 27 | 28 | client.connect_signal('property::active', function() 29 | if c.active then 30 | border.bg = colors.focused 31 | else 32 | border.bg = colors.unfocused 33 | end 34 | end) 35 | 36 | return border 37 | end 38 | 39 | -- A recursive function that fills table `t` with n border 40 | -- widgets, alternating focus indicating and normal borders. 41 | -- Yes, seriously. 42 | local function make_double_border(c, w, h, n, t) 43 | if n == 0 then return end 44 | 45 | if n % 2 == 0 then 46 | table.insert(t, make_border(w, h)) 47 | else 48 | table.insert(t, make_focus(c, w, h)) 49 | end 50 | make_double_border(c, w, h, n - 1, t) 51 | end 52 | 53 | -- Titlebars 54 | local function top_title(c, buttons) 55 | local borders = { layout = wibox.layout.fixed.vertical } 56 | make_double_border(c, nil, dpi(border_size), C_num_borders, borders) 57 | 58 | local close_button = wibox.widget { 59 | widget = wibox.container.background, 60 | bg = colors.red, 61 | shape = require('gears').shape.circle, 62 | valign = 'center', 63 | forced_width = dpi(13), 64 | forced_height= dpi(13), 65 | buttons = { awful.button({}, 1, function() c:kill() end) } 66 | } 67 | local max_button = wibox.widget { 68 | widget = wibox.container.background, 69 | bg = colors.fg_normal, 70 | valign = 'center', 71 | forced_width = dpi(10), 72 | forced_height= dpi(10), 73 | { 74 | widget = wibox.container.margin, 75 | margins = dpi(1), 76 | { 77 | widget = wibox.container.background, 78 | bg = colors.bg_normal, 79 | } 80 | }, 81 | buttons = { 82 | awful.button({}, 1, function() 83 | c.maximized = not c.maximized 84 | c:raise() 85 | end) 86 | } 87 | } 88 | local min_button = wibox.widget { 89 | widget = wibox.container.background, 90 | bg = colors.fg_normal, 91 | valign = 'center', 92 | forced_width = dpi(10), 93 | forced_height= dpi(10), 94 | { 95 | widget = wibox.container.margin, 96 | margins = { bottom = dpi(1) }, 97 | { 98 | widget = wibox.container.background, 99 | bg = colors.bg_normal, 100 | } 101 | }, 102 | buttons = { 103 | awful.button({}, 1, function() 104 | require('gears').timer.delayed_call(function() 105 | c.minimized = not c.minimized 106 | end) 107 | end) 108 | } 109 | } 110 | 111 | client.connect_signal('property::active', function() 112 | if c.active then 113 | close_button.bg = colors.red 114 | max_button.bg = colors.fg_dark 115 | min_button.bg = colors.fg_dark 116 | else 117 | close_button.bg = colors.red .. '8c' 118 | max_button.bg = colors.fg_dark .. '8c' 119 | min_button.bg = colors.fg_dark .. '8c' 120 | end 121 | end) 122 | 123 | return wibox.widget { 124 | widget = wibox.container.background, 125 | bg = colors.bg_normal, 126 | { 127 | layout = wibox.layout.stack, 128 | { 129 | layout = wibox.layout.align.vertical, 130 | nil, 131 | nil, 132 | { 133 | widget = wibox.container.margin, 134 | margins = { 135 | left = dpi(4 * border_size), 136 | right = dpi(4 * border_size) 137 | }, 138 | borders 139 | } 140 | }, 141 | { 142 | layout = wibox.layout.align.horizontal, 143 | nil, 144 | nil, 145 | { 146 | widget = wibox.container.margin, 147 | margins = { right = dpi(9) }, 148 | { 149 | layout = wibox.layout.fixed.horizontal, 150 | spacing = dpi(8), 151 | { 152 | widget = wibox.container.margin, 153 | margins = { top = dpi(12), bottom = dpi(12) }, 154 | { 155 | layout = wibox.layout.fixed.horizontal, 156 | spacing = dpi(8), 157 | min_button, 158 | max_button 159 | } 160 | }, 161 | close_button 162 | } 163 | } 164 | } 165 | }, 166 | buttons = buttons 167 | } 168 | end 169 | 170 | local function bottom_title(c, buttons) 171 | local borders = { layout = wibox.layout.fixed.vertical } 172 | make_double_border(c, nil, dpi(border_size), C_num_borders, borders) 173 | 174 | return wibox.widget { 175 | widget = wibox.container.background, 176 | bg = colors.bg_normal, 177 | { 178 | widget = wibox.container.margin, 179 | margins = { 180 | left = dpi(4 * border_size), 181 | right = dpi(4 * border_size) 182 | }, 183 | { 184 | layout = wibox.layout.align.vertical, 185 | { 186 | widget = wibox.container.rotate, 187 | direction = 'south', 188 | borders 189 | }, 190 | nil, 191 | nil 192 | } 193 | }, 194 | buttons = buttons 195 | } 196 | end 197 | 198 | local function right_title(c, buttons) 199 | local borders = { layout = wibox.layout.fixed.horizontal } 200 | make_double_border(c, dpi(border_size), nil, C_num_borders, borders) 201 | 202 | return wibox.widget { 203 | widget = wibox.container.background, 204 | bg = colors.bg_normal, 205 | { 206 | layout = wibox.layout.align.horizontal, 207 | { 208 | widget = wibox.container.rotate, 209 | direction = 'south', 210 | borders 211 | }, 212 | nil, 213 | nil 214 | }, 215 | buttons = buttons 216 | } 217 | end 218 | 219 | local function left_title(c, buttons) 220 | local borders = { layout = wibox.layout.fixed.horizontal } 221 | make_double_border(c, dpi(border_size), nil, C_num_borders, borders) 222 | 223 | return wibox.widget { 224 | widget = wibox.container.background, 225 | bg = colors.bg_normal, 226 | { 227 | layout = wibox.layout.align.horizontal, 228 | nil, 229 | nil, 230 | borders 231 | }, 232 | buttons = buttons 233 | } 234 | end 235 | 236 | -- The total size of the borders is given by: 237 | -- - Most borders are non-focusable, and 4 times the size of a focusable border. 238 | -- - The focusable borders are always either half or lower the amount of borders. 239 | local t_size = dpi(border_size * (4 * math.ceil(C_num_borders / 2) + math.floor(C_num_borders / 2))) 240 | 241 | return function(c) 242 | -- buttons for the titlebar 243 | local buttons = { 244 | awful.button(nil, 1, function() 245 | c:activate({ context = 'titlebar', action = 'mouse_move' }) 246 | end), 247 | awful.button(nil, 3, function() 248 | c:activate({ context = 'titlebar', action = 'mouse_resize' }) 249 | end) 250 | } 251 | 252 | -- draw all titlebars 253 | awful.titlebar(c, { position = 'left', size = t_size }).widget = left_title(c, buttons) 254 | awful.titlebar(c, { position = 'right', size = t_size }).widget = right_title(c, buttons) 255 | awful.titlebar(c, { position = 'top', size = t_size + dpi(19) }).widget = top_title(c, buttons) 256 | awful.titlebar(c, { position = 'bottom', size = t_size }).widget = bottom_title(c, buttons) 257 | end 258 | -------------------------------------------------------------------------------- /widgets/wibar/init.lua: -------------------------------------------------------------------------------- 1 | -- wii bar meta 2 | 3 | local awful = require('awful') 4 | local wibox = require('wibox') 5 | local beautiful = require('beautiful') 6 | local dpi = beautiful.xresources.apply_dpi 7 | 8 | local modules = require('widgets.wibar.module') 9 | 10 | return function(s) 11 | return awful.wibar { 12 | screen = s, 13 | position = 'top', 14 | height = dpi(48), 15 | widget = { 16 | widget = wibox.container.background, 17 | bg = modules.colors.bg_normal, 18 | { 19 | widget = wibox.container.margin, 20 | margins = { 21 | left = dpi(24), right = dpi(24), 22 | top = dpi(8), bottom = dpi(8) 23 | }, 24 | { 25 | layout = wibox.layout.align.horizontal, 26 | expand = 'none', 27 | -- left widgets 28 | { 29 | layout = wibox.layout.fixed.horizontal, 30 | spacing = dpi(16), 31 | modules.clock() 32 | }, 33 | -- middle widgets 34 | modules.taglist(s), 35 | -- right widgets 36 | { 37 | layout = wibox.layout.fixed.horizontal, 38 | spacing = dpi(16), 39 | modules.systray(), 40 | modules.cfg(), 41 | modules.layoutbox(s) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | end 48 | -------------------------------------------------------------------------------- /widgets/wibar/module/cfg.lua: -------------------------------------------------------------------------------- 1 | local wibox = require('wibox') 2 | local awful = require('awful') 3 | local beautiful = require('beautiful') 4 | 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local colors = require('widgets.wibar.module.colors') 8 | local cfg = require('widgets.config') 9 | local icon = require('gears.filesystem').get_configuration_dir() .. 'theme/assets/settings.svg' 10 | 11 | return function() 12 | return wibox.widget { 13 | widget = wibox.container.background, 14 | shape = function(c, w, h) 15 | require('gears').shape.rounded_rect(c, w, h, dpi(4)) 16 | end, 17 | bg = colors.bg_light, 18 | { 19 | widget = wibox.container.margin, 20 | margins = dpi(8), 21 | { 22 | widget = wibox.widget.imagebox, 23 | stylesheet = string.format('*{ fill: %s }', colors.fg_normal), 24 | image = icon 25 | } 26 | }, 27 | buttons = { 28 | awful.button(nil, 1, function() cfg:show() end) 29 | } 30 | } 31 | end 32 | -------------------------------------------------------------------------------- /widgets/wibar/module/clock.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local wibox = require('wibox') 3 | local beautiful = require('beautiful') 4 | local gears = require('gears') 5 | 6 | local dpi = beautiful.xresources.apply_dpi 7 | 8 | local panel = require('widgets.timepanel') 9 | local colors = require('widgets.wibar.module.colors') 10 | 11 | -- The OpenWeather API outputs icon codes, we need to actually 12 | -- do something with those if we intend to use icons. 13 | local icon_dir = require('gears.filesystem').get_configuration_dir() .. 'theme/assets/weather/' 14 | local icons = { 15 | -- Daytime 16 | ['01d'] = 'clear', 17 | ['02d'] = 'few-clouds', 18 | ['04d'] = 'few-clouds', 19 | ['03d'] = 'clouds', 20 | ['09d'] = 'rain-light', 21 | ['10d'] = 'rain', 22 | ['11d'] = 'storm', 23 | ['13d'] = 'snow', 24 | ['50d'] = 'fog', 25 | -- Nighttime 26 | ['01n'] = 'clear-n', 27 | ['02n'] = 'few-clouds-n', 28 | ['03n'] = 'few-clouds-n', 29 | ['04n'] = 'clouds', 30 | ['09n'] = 'rain-light', 31 | ['10n'] = 'rain', 32 | ['11n'] = 'storm', 33 | ['13n'] = 'snow', 34 | ['50n'] = 'fog' 35 | } 36 | 37 | local weather = wibox.widget { 38 | layout = wibox.layout.fixed.horizontal, 39 | spacing = dpi(8), 40 | { 41 | widget = wibox.widget.imagebox, 42 | stylesheet = string.format([[*{ fill = %s; }]], colors.fg_normal), 43 | image = icon_dir .. 'clouds.svg', 44 | id = 'image_role' 45 | }, 46 | { 47 | widget = wibox.widget.textbox, 48 | markup = 'N/A', 49 | id = 'text_role' 50 | }, 51 | set_image = function(self, image) 52 | local widget = self:get_children_by_id('image_role')[1] 53 | widget.image = gears.color.recolor_image(image, colors.fg_normal) 54 | end, 55 | set_text = function(self, text) 56 | self:get_children_by_id('text_role')[1].markup = '' .. text .. '°C' 57 | end 58 | } 59 | 60 | awesome.connect_signal('connect::weather', function(out) 61 | weather.image = out.icon 62 | weather.text = out.temperature 63 | end) 64 | 65 | local clock = wibox.widget { 66 | widget = wibox.container.background, 67 | shape = function(c, w ,h) 68 | gears.shape.rounded_rect(c, w, h, dpi(4)) 69 | end, 70 | bg = colors.transparent, 71 | { 72 | widget = wibox.container.margin, 73 | margins = { 74 | left = dpi(12), right = dpi(12), 75 | top = dpi(6), bottom = dpi(6) 76 | }, 77 | { 78 | layout = wibox.layout.fixed.horizontal, 79 | spacing = dpi(12), 80 | { 81 | layout = wibox.layout.fixed.horizontal, 82 | spacing = dpi(8), 83 | { 84 | widget = wibox.widget.textclock, 85 | format = '%B %d' 86 | }, 87 | { 88 | widget = wibox.widget.textclock, 89 | format = '%H:%M', 90 | font = beautiful.font_mono .. 'Bold ' .. dpi(10) 91 | } 92 | }, 93 | { 94 | widget = wibox.widget.separator, 95 | color = colors.mid_light, 96 | forced_width = dpi(1) 97 | }, 98 | weather 99 | } 100 | } 101 | } 102 | 103 | clock:connect_signal('mouse::enter', function() 104 | clock.bg = colors.bg_light 105 | end) 106 | clock:connect_signal('mouse::leave', function() 107 | clock.bg = colors.transparent 108 | end) 109 | 110 | clock.buttons = { 111 | awful.button({}, 1, function() 112 | panel:show() 113 | end) 114 | } 115 | 116 | return function() return clock end 117 | -------------------------------------------------------------------------------- /widgets/wibar/module/colors.lua: -------------------------------------------------------------------------------- 1 | local colorscheme = require('theme.colorscheme') 2 | 3 | local colors = {} 4 | if require('config.user').dark then 5 | colors = { 6 | bg_normal = colorscheme.black, 7 | bg_light = colorscheme.gray100, 8 | mid_normal = colorscheme.gray90, 9 | mid_light = colorscheme.gray80, 10 | unfocused = colorscheme.gray60, 11 | fg_dark = colorscheme.gray30, 12 | fg_normal = colorscheme.gray10, 13 | red = colorscheme.red, 14 | green = colorscheme.green 15 | } 16 | else 17 | colors = { 18 | bg_normal = colorscheme.gray100, 19 | bg_light = colorscheme.gray90, 20 | mid_normal = colorscheme.gray80, 21 | mid_light = colorscheme.gray70, 22 | unfocused = colorscheme.gray50, 23 | fg_dark = colorscheme.gray100, 24 | fg_normal = colorscheme.white, 25 | red = colorscheme.red_dark, 26 | green = colorscheme.green_dark 27 | } 28 | end 29 | 30 | return colors 31 | -------------------------------------------------------------------------------- /widgets/wibar/module/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | colors = require(... .. '.colors'), 3 | layoutbox = require(... .. '.layoutbox'), 4 | taglist = require(... .. '.taglist'), 5 | tasklist = require(... .. '.tasklist'), 6 | systray = require(... .. '.systray'), 7 | clock = require(... .. '.clock'), 8 | cfg = require(... .. '.cfg') 9 | } 10 | -------------------------------------------------------------------------------- /widgets/wibar/module/layoutbox.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local wibox = require('wibox') 3 | local beautiful = require('beautiful') 4 | 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local buttons = require('bindings.widgets.layoutbox').buttons 8 | 9 | return function(s) 10 | return wibox.widget { 11 | widget = wibox.container.margin, 12 | margins = { 13 | top = dpi(7), bottom = dpi(7) 14 | }, 15 | { 16 | widget = awful.widget.layoutbox, 17 | screen = s 18 | }, 19 | buttons = buttons 20 | } 21 | end 22 | -------------------------------------------------------------------------------- /widgets/wibar/module/systray.lua: -------------------------------------------------------------------------------- 1 | local wibox = require('wibox') 2 | local beautiful = require('beautiful') 3 | local gears = require('gears') 4 | 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local colors = require('widgets.wibar.module.colors') 8 | 9 | local icons = gears.filesystem.get_configuration_dir() .. 'theme/assets/' 10 | local network = icons .. 'network/on.svg' 11 | local bluetooth = icons .. 'bluetooth/on.svg' 12 | local audio = icons .. 'audio/on.svg' 13 | 14 | -- Sadly, systray does not have `style` and so the background 15 | -- color and icon spacing must be declared through `beautiful`. 16 | return function() 17 | local function status_icon(image) 18 | return wibox.widget { 19 | widget = wibox.widget.imagebox, 20 | stylesheet = string.format('*{ fill: %s; }', colors.fg_normal), 21 | image = image 22 | } 23 | end 24 | 25 | local status = wibox.widget { 26 | widget = wibox.container.background, 27 | bg = colors.bg_light, 28 | shape = function(c, w, h) 29 | gears.shape.rounded_rect(c, w, h, dpi(4)) 30 | end, 31 | { 32 | widget = wibox.container.margin, 33 | margins = { 34 | left = dpi(12), right = dpi(12), 35 | top = dpi(6), bottom = dpi(6) 36 | }, 37 | { 38 | layout = wibox.layout.fixed.horizontal, 39 | spacing = dpi(6), 40 | status_icon(network), 41 | status_icon(bluetooth), 42 | status_icon(audio) 43 | } 44 | } 45 | } 46 | status:connect_signal('mouse::enter', function() 47 | status.bg = colors.mid_normal 48 | end) 49 | status:connect_signal('mouse::leave', function() 50 | status.bg = colors.bg_light 51 | end) 52 | 53 | return wibox.widget { 54 | widget = wibox.container.background, 55 | shape = function(c, w, h) 56 | gears.shape.rounded_rect(c, w, h, dpi(4)) 57 | end, 58 | bg = colors.bg_light, 59 | { 60 | layout = wibox.layout.fixed.horizontal, 61 | { 62 | widget = wibox.container.margin, 63 | margins = { 64 | left = dpi(12), right = dpi(12), 65 | top = dpi(6), bottom = dpi(6) 66 | }, 67 | { 68 | widget = wibox.widget.systray 69 | } 70 | }, 71 | { 72 | widget = wibox.container.margin, 73 | margins = { 74 | top = dpi(6), bottom = dpi(6) 75 | }, 76 | { 77 | widget = wibox.container.background, 78 | bg = colors.mid_light, 79 | forced_width = dpi(1) 80 | } 81 | }, 82 | status 83 | } 84 | } 85 | end 86 | -------------------------------------------------------------------------------- /widgets/wibar/module/taglist.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local wibox = require('wibox') 3 | local beautiful = require('beautiful') 4 | 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local buttons = require('bindings.widgets.taglist').buttons 8 | local rubato = require('modules.rubato') 9 | local colors = require('widgets.wibar.module.colors') 10 | 11 | return function(s) 12 | local taglist = awful.widget.taglist { 13 | screen = s, 14 | filter = awful.widget.taglist.filter.all, 15 | buttons = buttons, 16 | layout = { 17 | layout = wibox.layout.fixed.horizontal, 18 | spacing = dpi(8) 19 | }, 20 | style = { 21 | bg_focus = colors.green, 22 | bg_occupied = colors.unfocused, 23 | bg_empty = colors.mid_light, 24 | bg_urgent = colors.red 25 | }, 26 | -- The fun stuff. 27 | widget_template = { 28 | -- Create the tag icon as an empty textbox. 29 | widget = wibox.container.margin, 30 | margins = { 31 | top = dpi(8), bottom = dpi(8) 32 | }, 33 | { 34 | widget = wibox.container.background, 35 | id = 'background_role', 36 | { 37 | widget = wibox.widget.textbox, 38 | text = '' 39 | } 40 | }, 41 | -- Create a callback to change its size with an animation depending 42 | -- on focus and occupation. 43 | create_callback = function(self, tag) 44 | self.animate = rubato.timed { 45 | duration = 0.15, 46 | subscribed = function(h) 47 | self:get_children_by_id('background_role')[1].forced_width = h 48 | end 49 | } 50 | self.update = function() 51 | if tag.selected then 52 | -- If the tag is focused: 53 | self.animate.target = dpi(64) 54 | elseif #tag:clients() > 0 then 55 | -- If the tag is occupied: 56 | self.animate.target = dpi(48) 57 | else 58 | -- If the tag is unoccupied and unfocused: 59 | self.animate.target = dpi(24) 60 | end 61 | end 62 | -- Generate the bar sizes once. 63 | self.update() 64 | end, 65 | -- Then update on callback. 66 | update_callback = function(self) 67 | self.update() 68 | end 69 | } 70 | } 71 | 72 | local widget = wibox.widget { 73 | widget = wibox.container.background, 74 | bg = beautiful.transparent, 75 | shape = function(c, w, h) 76 | require('gears').shape.rounded_rect(c, w, h, dpi(4)) 77 | end, 78 | { 79 | widget = wibox.container.margin, 80 | margins = { 81 | left = dpi(18), right = dpi(18), 82 | top = dpi(7), bottom = dpi(7) 83 | }, 84 | taglist 85 | } 86 | } 87 | widget:connect_signal('mouse::enter', function() 88 | widget.bg = colors.bg_light 89 | end) 90 | widget:connect_signal('mouse::leave', function() 91 | widget.bg = beautiful.transparent 92 | end) 93 | 94 | return widget 95 | end 96 | -------------------------------------------------------------------------------- /widgets/wibar/module/tasklist.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local beautiful = require('beautiful') 3 | local wibox = require('wibox') 4 | local gears = require('gears') 5 | 6 | local dpi = beautiful.xresources.apply_dpi 7 | 8 | local buttons = require('bindings.widgets.tasklist').buttons 9 | local colors = require('widgets.wibar.module.colors') 10 | 11 | return function(s) 12 | return awful.widget.tasklist { 13 | screen = s, 14 | filter = awful.widget.tasklist.filter.currenttags, 15 | buttons = buttons, 16 | source = function() 17 | local ret = {} 18 | for _, t in ipairs(s.tags) do 19 | gears.table.merge(ret, t:clients()) 20 | end 21 | return ret 22 | end, 23 | style = { 24 | disable_task_name = true, 25 | bg_normal = colors.bg_normal, 26 | bg_focus = colors.bg_light, 27 | bg_urgent = colors.red .. '40', 28 | bg_minimize = colors.bg_normal, 29 | shape = function(c, w, h) 30 | gears.shape.rounded_rect(c, w, h, dpi(4)) 31 | end 32 | }, 33 | layout = { 34 | layout = wibox.layout.fixed.horizontal 35 | }, 36 | widget_template = { 37 | widget = wibox.container.background, 38 | id = 'background_role', 39 | { 40 | widget = wibox.container.margin, 41 | margins = dpi(6), 42 | { 43 | widget = awful.widget.clienticon 44 | } 45 | } 46 | } 47 | } 48 | end 49 | --------------------------------------------------------------------------------