├── .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 |
43 |
--------------------------------------------------------------------------------
/theme/assets/arrow/up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
43 |
--------------------------------------------------------------------------------
/theme/assets/audio/off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/theme/assets/audio/on.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/theme/assets/awesome.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
44 |
--------------------------------------------------------------------------------
/theme/assets/bluetooth/off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/theme/assets/bluetooth/on.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/theme/assets/dot.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
42 |
--------------------------------------------------------------------------------
/theme/assets/image.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/theme/assets/layout/float.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
26 |
--------------------------------------------------------------------------------
/theme/assets/layout/tile_bottom.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
51 |
--------------------------------------------------------------------------------
/theme/assets/layout/tile_left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
51 |
--------------------------------------------------------------------------------
/theme/assets/layout/tile_right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
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 |
--------------------------------------------------------------------------------