├── .github ├── README.md └── assets │ ├── banner.png │ └── showcase.png ├── .gitmodules ├── binds ├── client │ ├── init.lua │ ├── keys.lua │ └── mouse.lua ├── global │ ├── init.lua │ ├── keys.lua │ └── mouse.lua ├── init.lua └── mod.lua ├── config ├── apps.lua ├── auto.lua ├── rules.lua └── user.lua ├── helpers.lua ├── module └── json.lua ├── rc.lua ├── script ├── shooter.lua ├── tym-themer.lua └── xresources.lua ├── signal ├── client.lua ├── init.lua ├── naughty.lua ├── screen.lua ├── system │ ├── audio.lua │ ├── battery.lua │ ├── network.lua │ ├── playerctl.lua │ └── weather.lua └── tag.lua ├── theme ├── assets │ ├── default │ │ └── pfp.png │ ├── fonts │ │ └── gwnce.ttf │ ├── notif │ │ ├── cancel.png │ │ └── default.png │ └── util │ │ └── awesome.png ├── color │ ├── init.lua │ ├── lite-xl │ │ ├── dark │ │ │ └── init.lua │ │ └── light │ │ │ └── init.lua │ ├── mardel │ │ ├── dark │ │ │ └── init.lua │ │ └── light │ │ │ └── init.lua │ ├── oxocarbon │ │ ├── dark │ │ │ └── init.lua │ │ └── light │ │ │ └── init.lua │ └── rose-pine │ │ ├── dark │ │ └── init.lua │ │ └── light │ │ └── init.lua ├── icons.lua ├── init.lua └── theme.lua ├── ui ├── dash │ ├── init.lua │ └── module │ │ ├── grid.lua │ │ ├── init.lua │ │ ├── player.lua │ │ ├── random.lua │ │ ├── slider.lua │ │ ├── title.lua │ │ └── user.lua ├── launcher │ └── init.lua ├── menu │ └── init.lua ├── notification │ └── init.lua ├── osd │ ├── init.lua │ ├── player.lua │ └── volume.lua ├── scratch │ ├── init.lua │ └── music.lua ├── time │ ├── init.lua │ └── module │ │ ├── calendar.lua │ │ ├── clock.lua │ │ ├── init.lua │ │ └── weather.lua ├── titlebar │ ├── init.lua │ └── normal.lua └── wibar │ ├── init.lua │ └── module │ ├── clock.lua │ ├── dash.lua │ ├── init.lua │ ├── launcher.lua │ ├── layoutbox.lua │ ├── status.lua │ ├── systray.lua │ ├── taglist.lua │ └── tasklist.lua └── widget ├── init.lua ├── layoutbox.lua └── textbox ├── colored.lua ├── init.lua └── scrolling.lua /.github/README.md: -------------------------------------------------------------------------------- 1 | ![Welcome!](assets/banner.png) 2 | Welcome to this humble [AwesomeWM](https://awesomewm.org/) configuration that I made off 3 | my [modularized default rc.lua](https://github.com/Gwynsav/modular-awm-default), focused 4 | on being clean, simple and fast to use. 5 | 6 | > [!WARNING] 7 | > I have very bizarre ideas and am shameless enough to actually implement them here. 8 | Sometimes these ideas make the WM very slow or unstable, so please beware my incompetence. 9 | 10 | ## Installation 11 | 12 | ### Dependencies 13 | - `pactl` (usually provided by `pulseaudio-utils`) for audio widgets and keybinds. This 14 | does NOT mean that this setup only works with `pulseaudio`, you can also use `pipewire` 15 | by using `pipewire-pulse`. 16 | - `playerctl` (also usually `playerctl-{dev/devel}`) for music playback widgets and 17 | keybinds. 18 | 19 | 20 | 21 | As of right now, this is only a custom icon font and the AwesomeWM configuration, so to 22 | install it, just run: 23 | ``` 24 | # Assuming ~/.config/ exists. 25 | git clone https://github.com/Gwynsav/gwileful.git ~/.config/awesome --recursive 26 | # Assuming ~/.local/share/fonts exists. 27 | cp ~/.config/awesome/theme/assets/fonts/* ~/.local/share/fonts 28 | fc-cache -f 29 | ``` 30 | There are also some variables in the `config` directory, so make sure everything is 31 | defined correctly. 32 | 33 | ## Gallery 34 | 35 | ![How it looks as of 29/08/2024](assets/showcase.png) 36 | 37 | ## References and Acknowledgements 38 | 39 | All instances of me using others' code have a link to the original at the top of the file. 40 | 41 | Groups/projects: 42 | - Again, my [modularized default rc.lua](https://github.com/Gwynsav/modular-awm-default). 43 | - All projects used as submodules of this one, see `module/`. 44 | - [Feather Icons](https://feathericons.com/). Actually, I've moved away from these and 45 | made my own icons for everything here. But still, I used them in the past and as 46 | reference making my own icons, so I think they're still worth a mention. 47 | - [Fairfax](https://www.kreativekorp.com/software/fonts/fairfaxhd/), the beautiful font 48 | from KreativeKorp used for UI as well as terminal, in the past, that inspired the look of 49 | my own fonts currently in use in this rice. 50 | 51 | Individuals: 52 | - [sakuya](https://codeberg.org/moseni/bitmap-fonts). Creator of the `koishi` and `satori` 53 | fonts used in this rice previously, as well as help making my own icon font, used here. 54 | These fonts are no longer being distributed and so all current and future versions of this 55 | rice will not provide them nor display them. All fonts used from now on are created by 56 | myself and may become publically available in the future. Their rice also influenced the look 57 | of mine. 58 | - [Stardust-kyun](https://github.com/Stardust-kyun/dotfiles), references and some widgets. 59 | - [Kasper](https://github.com/Kasper24/KwesomeDE), used some of their daemons. 60 | - [Myagko](https://github.com/myagko/dotfiles), used their calendar. 61 | - [Crylia](https://github.com/Crylia/crylia-theme/), used some of their code. 62 | - [rxyhn](https://github.com/rxyhn/yoru), used calendar and some ideas. 63 | - The beautiful artwork I often display in the screenshots is by 64 | [みすたーおさる](https://www.pixiv.net/en/users/10770935) ("Mister Monkey" in english). 65 | -------------------------------------------------------------------------------- /.github/assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gwynsav/gwileful/a9bde5b98b9055fa432f74283a1ca9a33ae11b71/.github/assets/banner.png -------------------------------------------------------------------------------- /.github/assets/showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gwynsav/gwileful/a9bde5b98b9055fa432f74283a1ca9a33ae11b71/.github/assets/showcase.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "module/bling"] 2 | path = module/bling 3 | url = https://github.com/BlingCorp/bling.git 4 | [submodule "module/awesome-battery_widget"] 5 | path = module/awesome-battery_widget 6 | url = https://github.com/Aire-One/awesome-battery_widget 7 | [submodule "module/rubato"] 8 | path = module/rubato 9 | url = https://github.com/andorlando/rubato 10 | [submodule "module/dbus_proxy"] 11 | path = module/dbus_proxy 12 | url = https://github.com/Gwynsav/dbus_proxy 13 | -------------------------------------------------------------------------------- /binds/client/init.lua: -------------------------------------------------------------------------------- 1 | -- Returns all client mouse and keybinds. 2 | local require = require 3 | return { 4 | keys = require(... .. '.keys'), 5 | mouse = require(... .. '.mouse') 6 | } 7 | -------------------------------------------------------------------------------- /binds/client/keys.lua: -------------------------------------------------------------------------------- 1 | local require, client = require, client 2 | 3 | local awful = require('awful') 4 | 5 | local mod = require('binds.mod') 6 | local modkey = mod.modkey 7 | local tabbed = require('module.bling').module.tabbed 8 | 9 | --- Client keybindings. 10 | client.connect_signal('request::default_keybindings', function() 11 | awful.keyboard.append_client_keybindings({ 12 | -- Client state management. 13 | awful.key({ modkey, mod.shift }, 'q', function(c) c:kill() end, 14 | { description = 'close', group = 'client' }), 15 | 16 | awful.key({ modkey, }, 'space', awful.client.floating.toggle, 17 | { description = 'toggle floating', group = 'client' }), 18 | awful.key({ modkey, }, 't', function(c) c.ontop = not c.ontop end, 19 | { description = 'toggle keep on top', group = 'client' }), 20 | awful.key({ modkey, mod.ctrl }, 'space', function(c) c.sticky = not c.sticky end, 21 | { description = 'toggle sticky', group = 'client' }), 22 | 23 | awful.key({ modkey, }, 'n', 24 | function(c) 25 | -- The client currently has the input focus, so it cannot be 26 | -- minimized, since minimized clients can't have the focus. 27 | c.minimized = true 28 | end, { description = 'minimize', group = 'client' }), 29 | awful.key({ modkey, }, 'm', 30 | function(c) 31 | c.maximized = not c.maximized 32 | c:raise() 33 | end, { description = '(un)maximize', group = 'client' }), 34 | awful.key({ modkey, }, 'f', function(c) 35 | c.fullscreen = not c.fullscreen 36 | c:raise() 37 | end, { description = 'toggle fullscreen', group = 'client' }), 38 | 39 | -- Bling Tabbed management. 40 | awful.key({ modkey, mod.shift }, 'h', function() 41 | tabbed.pick_by_direction('up') 42 | end, { description = 'add client above focused to group', group = 'tabbing' }), 43 | awful.key({ modkey, mod.shift }, 'l', function() 44 | tabbed.pick_by_direction('down') 45 | end, { description = 'add client below focused to group', group = 'tabbing' }), 46 | awful.key({ modkey }, 'Escape', function() tabbed.pop() end, 47 | { description = 'remove client from tabbed group', group = 'tabbing' }), 48 | awful.key({ modkey }, 'Tab', function() tabbed.iter() end, 49 | { description = 'cycle tabbed client focus', group = 'tabbing' }) 50 | }) 51 | end) 52 | -------------------------------------------------------------------------------- /binds/client/mouse.lua: -------------------------------------------------------------------------------- 1 | local require, client = require, client 2 | 3 | local awful = require('awful') 4 | 5 | local mod = require('binds.mod') 6 | local modkey = mod.modkey 7 | 8 | --- Client mouse bindings. 9 | client.connect_signal('request::default_mousebindings', function() 10 | awful.mouse.append_client_mousebindings({ 11 | awful.button(nil, 1, function(c) 12 | c:activate({ context = 'mouse_click' }) 13 | end), 14 | awful.button({ modkey }, 1, function(c) 15 | c:activate({ context = 'mouse_click', action = 'mouse_move' }) 16 | end), 17 | awful.button({ modkey }, 3, function(c) 18 | c:activate({ context = 'mouse_click', action = 'mouse_resize' }) 19 | end) 20 | }) 21 | end) 22 | -------------------------------------------------------------------------------- /binds/global/init.lua: -------------------------------------------------------------------------------- 1 | -- Returns all global WM mouse and keybinds. 2 | local require = require 3 | return { 4 | keys = require(... .. '.keys'), 5 | mouse = require(... .. '.mouse') 6 | } 7 | -------------------------------------------------------------------------------- /binds/global/keys.lua: -------------------------------------------------------------------------------- 1 | local require, awesome, client = require, awesome, client 2 | 3 | local awful = require('awful') 4 | 5 | local mod = require('binds.mod') 6 | local modkey = mod.modkey 7 | 8 | local apps = require('config.apps') 9 | local audio = require('signal.system.audio') 10 | local pctl = require('signal.system.playerctl') 11 | local shooter = require('script.shooter') 12 | local scratch = require('ui.scratch') 13 | 14 | --- Global key bindings 15 | awful.keyboard.append_global_keybindings({ 16 | -- AwesomeWM 17 | ------------ 18 | -- General Awesome keys. 19 | awful.key({ modkey, }, 's', require('awful.hotkeys_popup').show_help, 20 | { description = 'show help', group = 'awesome' }), 21 | awful.key({ modkey, mod.ctrl }, 'r', awesome.restart, 22 | { description = 'reload awesome', group = 'awesome' }), 23 | awful.key({ modkey, }, 'Return', function() awful.spawn(apps.terminal) end, 24 | { description = 'open a terminal', group = 'launcher' }), 25 | awful.key({ modkey, }, 'p', function() awful.screen.focused().launcher:open() end, 26 | { description = 'show the launcher', group = 'launcher' }), 27 | 28 | -- Focus related keybindings. 29 | awful.key({ modkey, }, 'j', function() awful.client.focus.byidx( 1) end, 30 | { description = 'focus next by index', group = 'client' }), 31 | awful.key({ modkey, }, 'k', function() awful.client.focus.byidx(-1) end, 32 | { description = 'focus previous by index', group = 'client'}), 33 | awful.key({ modkey, mod.ctrl }, 'Left', function() awful.screen.focus_relative( 1) end, 34 | { description = 'focus the next screen', group = 'screen' }), 35 | awful.key({ modkey, mod.ctrl }, 'Right', function() awful.screen.focus_relative(-1) end, 36 | { description = 'focus the previous screen', group = 'screen' }), 37 | 38 | -- Layout related keybindings. 39 | awful.key({ modkey, mod.shift }, 'j', function() awful.client.swap.byidx( 1) end, 40 | { description = 'swap with next client by index', group = 'client' }), 41 | awful.key({ modkey, mod.shift }, 'k', function() awful.client.swap.byidx(-1) end, 42 | { description = 'swap with previous client by index', group = 'client' }), 43 | awful.key({ modkey, }, 'l', function() awful.tag.incmwfact( 0.05) end, 44 | { description = 'increase master width factor', group = 'layout' }), 45 | awful.key({ modkey, }, 'h', function() awful.tag.incmwfact(-0.05) end, 46 | { description = 'decrease master width factor', group = 'layout' }), 47 | awful.key({ modkey, }, 'equal', function() awful.tag.incnmaster( 1, nil, true) end, 48 | { description = 'increase the number of master clients', group = 'layout' }), 49 | awful.key({ modkey, }, 'minus', function() awful.tag.incnmaster(-1, nil, true) end, 50 | { description = 'decrease the number of master clients', group = 'layout' }), 51 | awful.key({ modkey, mod.alt }, 'k', function() awful.client.incwfact( 0.05) end, 52 | { description = 'increase client width factor', group = 'layout' }), 53 | awful.key({ modkey, mod.alt }, 'j', function() awful.client.incwfact(-0.05) end, 54 | { description = 'decrease client width factor', group = 'layout' }), 55 | awful.key({ modkey, mod.ctrl }, 'equal', function() awful.tag.incncol( 1, nil, true) 56 | end,{ description = 'increase the number of columns', group = 'layout' }), 57 | awful.key({ modkey, mod.ctrl }, 'minus', function() awful.tag.incncol(-1, nil, true) 58 | end, { description = 'decrease the number of columns', group = 'layout' }), 59 | awful.key({ 60 | modifiers = { modkey }, 61 | keygroup = 'numrow', 62 | description = 'only view tag', 63 | group = 'tag', 64 | on_press = function(index) 65 | local tag = awful.screen.focused().tags[index] 66 | if tag then tag:view_only() end 67 | end 68 | }), 69 | awful.key({ 70 | modifiers = { modkey, mod.ctrl }, 71 | keygroup = 'numrow', 72 | description = 'toggle tag', 73 | group = 'tag', 74 | on_press = function(index) 75 | local tag = awful.screen.focused().tags[index] 76 | if tag then awful.tag.viewtoggle(tag) end 77 | end 78 | }), 79 | awful.key({ 80 | modifiers = { modkey, mod.shift }, 81 | keygroup = 'numrow', 82 | description = 'move focused client to tag', 83 | group = 'tag', 84 | on_press = function(index) 85 | if client.focus then 86 | local tag = client.focus.screen.tags[index] 87 | if tag then client.focus:move_to_tag(tag) end 88 | end 89 | end 90 | }), 91 | awful.key({ 92 | modifiers = { modkey, mod.ctrl, mod.shift }, 93 | keygroup = 'numrow', 94 | description = 'toggle focused client on tag', 95 | group = 'tag', 96 | on_press = function(index) 97 | if client.focus then 98 | local tag = client.focus.screen.tags[index] 99 | if tag then client.focus:toggle_tag(tag) end 100 | end 101 | end 102 | }), 103 | awful.key({ 104 | modifiers = { modkey, mod.alt }, 105 | keygroup = 'numrow', 106 | description = 'select layout directly', 107 | group = 'layout', 108 | on_press = function(index) 109 | local t = awful.screen.focused().selected_tag 110 | if t then 111 | t.layout = t.layouts[index] or t.layout 112 | end 113 | end 114 | }), 115 | 116 | -- Miscelaneous 117 | --------------- 118 | -- Screenshot. 119 | awful.key({ }, 'Print', function() shooter.selection() end, 120 | { description = 'select a region to screenshot', group = 'screenshot' }), 121 | awful.key({ modkey }, 'Print', function() shooter.screen() end, 122 | { description = 'select the whole screen to screenshot', group = 'screenshot' }), 123 | awful.key({ mod.ctrl }, 'Print', function() shooter.delayed(5) end, 124 | { description = 'take a delayed fullscreen screenshot', group = 'screenshot' }), 125 | 126 | -- Audio. 127 | awful.key({ }, 'XF86AudioRaiseVolume', function() audio:default_sink_volume_up(2) end, 128 | { description = 'raises default audio device volume', group = 'audio' }), 129 | awful.key({ }, 'XF86AudioLowerVolume', function() audio:default_sink_volume_down(2) end, 130 | { description = 'lowers default audio device volume', group = 'audio' }), 131 | awful.key({ }, 'XF86AudioMute', function() audio:default_sink_toggle_mute() end, 132 | { description = 'toggles default audio device mute', group = 'audio' }), 133 | 134 | -- Music. 135 | awful.key({ }, 'XF86AudioPlay', function() pctl:play_pause() end, 136 | { description = 'toggles music playback', group = 'music' }), 137 | awful.key({ }, 'XF86AudioPrev', function() pctl:previous() end, 138 | { description = 'rewinds to previous song', group = 'music' }), 139 | awful.key({ }, 'XF86AudioNext', function() pctl:next() end, 140 | { description = 'skips to next song', group = 'music' }), 141 | awful.key({ modkey }, 'o', function() scratch.music:toggle() end, 142 | { description = 'toggles music scratchpad', group = 'music' }) 143 | }) 144 | -------------------------------------------------------------------------------- /binds/global/mouse.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local awful = require('awful') 4 | 5 | local menu = require('ui.menu') 6 | 7 | --- Global mouse bindings 8 | awful.mouse.append_global_mousebindings({ 9 | awful.button(nil, 3, function() menu.main:toggle() end), 10 | -- Single most annoying pair of keybinds ever to be seen. 11 | -- awful.button(nil, 4, awful.tag.viewprev), 12 | -- awful.button(nil, 5, awful.tag.viewnext) 13 | }) 14 | -------------------------------------------------------------------------------- /binds/init.lua: -------------------------------------------------------------------------------- 1 | -- Returns all mouse and keybinds for both clients and the WM. 2 | local require = require 3 | return { 4 | global = require(... .. '.global'), 5 | client = require(... .. '.client') 6 | } 7 | 8 | -------------------------------------------------------------------------------- /binds/mod.lua: -------------------------------------------------------------------------------- 1 | return { 2 | alt = 'Mod1', 3 | super = 'Mod4', 4 | shift = 'Shift', 5 | ctrl = 'Control', 6 | 7 | -- Set Super as default modkey if none is present. 8 | modkey = require('config.user').mod or 'Mod4' 9 | } 10 | -------------------------------------------------------------------------------- /config/apps.lua: -------------------------------------------------------------------------------- 1 | -- This is used later as the default terminal and editor to run. 2 | local apps = {} 3 | 4 | apps.terminal = 'tym' 5 | apps.terminal_cmd = apps.terminal .. ' -e ' 6 | 7 | apps.editor = os.getenv('EDITOR') or 'vim' 8 | apps.editor_cmd = apps.terminal_cmd .. apps.editor 9 | 10 | apps.browser = 'firefox' 11 | 12 | -- Set the terminal for the menubar. 13 | require('menubar').utils.terminal = apps.terminal 14 | 15 | return apps 16 | -------------------------------------------------------------------------------- /config/auto.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | 3 | -- Checks for currently running programs with the same `Command`. If found does nothing, 4 | -- else runs `program`. 5 | local function spawn_if_not_running(program) 6 | awful.spawn.easy_async_with_shell('pgrep ' .. program, function(output) 7 | if output == nil or output == '' then 8 | print('"' .. program .. '" started with PID: ' .. awful.spawn(program)) 9 | else 10 | -- Some programs run several processes, with different PIDs each. 11 | if output:match('\n.+') then 12 | print('"' .. program .. '" already running with PID: ' 13 | .. output:gsub('\n', ','):sub(1, -2)) 14 | else 15 | print('"' .. program .. '" already running with PID: ' .. output:gsub('\n', '')) 16 | end 17 | end 18 | end) 19 | end 20 | 21 | -- "Daemons", but not really daemonized, just programs running in the bg. 22 | -- spawn_if_not_running('mpd') 23 | -- spawn_if_not_running('mpDris2') 24 | -- spawn_if_not_running('playerctld') 25 | -- spawn_if_not_running('nm-applet') 26 | -- X stuff. 27 | -- awful.spawn.once('setxkbmap -option caps:super us') 28 | -------------------------------------------------------------------------------- /config/rules.lua: -------------------------------------------------------------------------------- 1 | local require, screen = require, screen 2 | 3 | local awful = require('awful') 4 | local ruled = require('ruled') 5 | 6 | local user = require('config.user') 7 | 8 | --- Rules. 9 | -- Rules to apply to new clients. 10 | ruled.client.connect_signal('request::rules', function() 11 | -- All clients will match this rule. 12 | ruled.client.append_rule({ 13 | id = 'global', 14 | rule = nil, 15 | properties = { 16 | focus = awful.client.focus.filter, 17 | raise = true, 18 | screen = awful.screen.preferred, 19 | placement = awful.placement.centered + awful.placement.no_offscreen, 20 | callback = awful.client.setslave, 21 | size_hints_honor = false 22 | } 23 | }) 24 | 25 | -- Floating clients. 26 | ruled.client.append_rule({ 27 | id = 'floating', 28 | rule_any = { 29 | instance = { 'copyq', 'pinentry' }, 30 | class = { 31 | 'Arandr', 'Blueman-manager', 'Gpick', 'Kruler', 'Sxiv', 32 | 'Tor Browser', 'Wpa_gui', 'veromix', 'xtightvncviewer', 33 | 'Nsxiv', 'mpv' 34 | }, 35 | -- Note that the name property shown in xprop might be set slightly after 36 | -- creation of the client and the name shown there might not match defined rules 37 | -- here. 38 | name = { 39 | 'Event Tester' -- xev. 40 | }, 41 | role = { 42 | 'AlarmWindow', -- Thunderbird's calendar. 43 | 'ConfigManager', -- Thunderbird's about:config. 44 | 'pop-up' -- e.g. Google Chrome's (detached) Developer Tools. 45 | } 46 | }, 47 | properties = { floating = true } 48 | }) 49 | 50 | -- Add titlebars to normal clients and dialogs. 51 | ruled.client.append_rule({ 52 | id = 'titlebars', 53 | rule_any = { type = { 'normal', 'dialog' } }, 54 | properties = { titlebars_enabled = true } 55 | }) 56 | 57 | -- Prevent certain clients from forcibly claiming focus. 58 | ruled.client.append_rule({ 59 | rule_any = { class = { 'firefox', 'steam', 'discord' } }, 60 | properties = { focus = false } 61 | }) 62 | 63 | -- Map certain clients to certain workspaces. 64 | ruled.client.append_rule({ 65 | rule_any = { 66 | class = { 'steamwebhelper', 'steam', 'Heroic' } 67 | }, 68 | properties = { 69 | tag = screen[1].tags[user.tags], 70 | floating = true 71 | } 72 | }) 73 | ruled.client.append_rule({ 74 | rule_any = { 75 | class = { 'discord', 'vesktop' } 76 | }, 77 | properties = { tag = screen[1].tags[user.tags - 1] } 78 | }) 79 | end) 80 | 81 | -- Floating windows are `always on top` by default. Breaks fullscreen for some reason?? 82 | -- client.connect_signal("property::floating", function(c) c.ontop = c.floating end) 83 | -------------------------------------------------------------------------------- /config/user.lua: -------------------------------------------------------------------------------- 1 | local awful = require('awful') 2 | local dpi = require('beautiful.xresources').apply_dpi 3 | 4 | local HOME = os.getenv('HOME') .. '/' 5 | 6 | -- Specify user preferences for Awesome's behavior. 7 | return { 8 | -- Basics 9 | --------- 10 | -- Default modkey. 11 | -- Usually, Mod4 is the key with a logo between Control and Alt. If you do not like 12 | -- this or do not have such a key, I suggest you to remap Mod4 to another key using 13 | -- xmodmap or other tools. However, you can use another modifier like Mod1, but it 14 | -- may interact with others. 15 | mod = 'Mod4', 16 | -- Each screen has its own tag table. You can just define one and append it to all 17 | -- screens (default behavior). 18 | tags = 7, 19 | -- Table of layouts to cover with awful.layout.inc, ORDER MATTERS, the first layout 20 | -- in the table is your DEFAULT LAYOUT. 21 | layouts = { 22 | awful.layout.suit.tile, 23 | awful.layout.suit.tile.left, 24 | awful.layout.suit.tile.bottom, 25 | -- awful.layout.suit.tile.top, 26 | -- awful.layout.suit.fair, 27 | -- awful.layout.suit.fair.horizontal, 28 | -- awful.layout.suit.spiral, 29 | -- awful.layout.suit.spiral.dwindle, 30 | -- awful.layout.suit.max, 31 | -- awful.layout.suit.max.fullscreen, 32 | -- awful.layout.suit.magnifier, 33 | -- awful.layout.suit.corner.nw, 34 | awful.layout.suit.floating 35 | }, 36 | 37 | -- Bling 38 | -------- 39 | -- Sizes. 40 | gaps = dpi(2), 41 | tag_padding = dpi(6), 42 | 43 | -- Colors. Available options: 44 | -- lite-xl, mardel, oxocarbon, rose-pine. 45 | colorscheme = 'mardel', 46 | style = 'dark', 47 | 48 | -- Profile Picture. 49 | -- pfp = HOME .. 'Pictures/avatars/misuta-o-saru/gundamZOOM.jpg', 50 | -- pfp = HOME .. 'Pictures/avatars/misuta-o-saru/foxZOOM.jpg', 51 | 52 | -- Wallpaper. 53 | -- wallpaper = HOME .. 'Pictures/walls/flowers/YellowMacro.jpg', 54 | -- wallpaper = HOME .. 'Pictures/walls/abstract/GeometricalShape.jpg', 55 | 56 | -- Screenshots, they're only saved to this path when you select SAVE on the screenshot 57 | -- notification. 58 | screenshot_path = HOME .. 'Pictures/screenshots/', 59 | 60 | -- Power 61 | -------- 62 | -- Most systemd distros need not prepend anything to the {poweroff,reboot,suspend} 63 | -- commands, but distros not using systemd must use a polkit or other methods to access 64 | -- these. Some systemd distros also require prepending `systemctl` as user. 65 | shutdown_cmd = 'loginctl poweroff', 66 | reboot_cmd = 'loginctl reboot', 67 | suspend_cmd = 'loginctl suspend', 68 | -- The battery name, actually refers to its path in `/org/freedesktop/UPower/devices/`. 69 | -- Most devices will just have 'BAT0' for the battery. 70 | -- battery_name = 'BAT0', 71 | 72 | -- Avoid loading non-vital assets to save resources. 73 | lite = true, 74 | 75 | -- Weather 76 | ---------- 77 | -- area = "New+York", 78 | -- imperial = false 79 | } 80 | -------------------------------------------------------------------------------- /helpers.lua: -------------------------------------------------------------------------------- 1 | local _H = {} 2 | 3 | -- I feel like YanDev saying "I wish there was a better way to do this"... 4 | -- Gets the suffix for any given day of the month. 5 | function _H.get_suffix(day) 6 | if day > 3 and day < 21 then 7 | return 'th' 8 | end 9 | 10 | if day % 10 == 1 then 11 | return 'st' 12 | elseif day % 10 == 2 then 13 | return 'nd' 14 | elseif day % 10 == 3 then 15 | return 'rd' 16 | else 17 | return 'th' 18 | end 19 | end 20 | 21 | -- Returns true when `e` is an entry in `table`. 22 | function _H.in_table(e, table) 23 | for _, v in table do 24 | if v == e then 25 | return true 26 | end 27 | end 28 | return false 29 | end 30 | 31 | function _H.exists(path) 32 | if path == nil or type(path) ~= 'string' then 33 | return false 34 | end 35 | 36 | return os.rename(path, path) 37 | end 38 | 39 | -- Check whether a file exists. 40 | function _H.file_exists(path) 41 | if not _H.exists(path) then return false end 42 | 43 | local file = io.open(path) 44 | if file then 45 | io.close(file) 46 | return true 47 | end 48 | 49 | return false 50 | end 51 | 52 | -- Check whether a directory exists. 53 | function _H.dir_exists(path) 54 | if path == nil or type(path) ~= 'string' then 55 | return false 56 | end 57 | 58 | if path:sub(-1, -1) ~= '/' then 59 | path = path .. '/' 60 | end 61 | return (_H.exists(path) and not _H.file_exists(path)) 62 | end 63 | 64 | return _H 65 | -------------------------------------------------------------------------------- /module/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 | -------------------------------------------------------------------------------- /rc.lua: -------------------------------------------------------------------------------- 1 | -- awesome_mode: api-level=4:screen=on 2 | -- If LuaRocks is installed, make sure that packages installed through it are 3 | -- found (e.g. lgi). If LuaRocks is not installed, do nothing. 4 | pcall(require, 'luarocks.loader') 5 | 6 | -- Allow Awesome to automatically focus a client upon changing tags or loading. 7 | require('awful.autofocus') 8 | -- Enable hotkeys help widget for VIM and other apps when client with a matching 9 | -- name is opened. 10 | require('awful.hotkeys_popup.keys') 11 | 12 | --- Error handling. 13 | -- Notification library. 14 | local naughty = require('naughty') 15 | -- Check if awesome encountered an error during startup and fell back to another config 16 | -- (This code will only ever execute for the fallback config). 17 | naughty.connect_signal('request::display_error', function(message, startup) 18 | naughty.notification({ 19 | urgency = 'critical', 20 | title = 'Oops, an error happened' .. (startup and ' during startup!' or '!'), 21 | message = message 22 | }) 23 | end) 24 | 25 | -- Create auxiliary directories for user state. 26 | local awful = require('awful') 27 | awful.spawn('mkdir -p ' .. os.getenv('HOME') .. '/.local/data/awesome') 28 | 29 | -- Load the theme. In other words, defines the variables within the `beautiful` table. 30 | require('theme') 31 | 32 | -- Treat all signals. Bear in mind this implies creating all tags, attaching their 33 | -- layouts, setting client behavior and loading UI. 34 | require('signal') 35 | 36 | -- Set all keybinds. 37 | require('binds') 38 | 39 | -- Load all client rules. 40 | require('config.rules') 41 | 42 | -- Run startup commands. 43 | require('config.auto') 44 | -------------------------------------------------------------------------------- /script/shooter.lua: -------------------------------------------------------------------------------- 1 | -- Screenshot script using maim and xclip. `awful.screenshot` has yet to be 2 | -- reliable enough to be usable. 3 | local require, io, os = require, io, os 4 | 5 | local awful = require('awful') 6 | local beautiful = require('beautiful') 7 | local naughty = require('naughty') 8 | 9 | local user = require('config.user') 10 | local helpers = require('helpers') 11 | 12 | -- The directory where PERMANENT files would be stored. 13 | local perm_dir = user.screenshot_path or os.getenv('HOME') 14 | 15 | local function send_notif(path) 16 | local ok = naughty.action({ name = 'Ok' }) 17 | local save = naughty.action({ name = 'Save' }) 18 | local discard = naughty.action({ name = 'Discard' }) 19 | 20 | save:connect_signal('invoked', function() 21 | if not helpers.dir_exists(perm_dir) then 22 | awful.spawn('mkdir -p ' .. perm_dir) 23 | end 24 | awful.spawn.easy_async_with_shell('cp ' .. path .. ' ' .. perm_dir, function() 25 | naughty.notification({ 26 | icon = beautiful.notification_default, 27 | title = 'Screenshot', 28 | message = 'Saved to ' .. perm_dir, 29 | actions = { ok } 30 | }) 31 | end) 32 | end) 33 | 34 | discard:connect_signal('invoked', function() 35 | awful.spawn.easy_async_with_shell('rm ' .. path, function() 36 | naughty.notification({ 37 | icon = beautiful.notification_cancel, 38 | title = 'Screenshot', 39 | message = 'Temporary file removed!', 40 | actions = { ok } 41 | }) 42 | end) 43 | end) 44 | 45 | -- Check whether the screenshot was taken or not. 46 | local file = io.open(path) 47 | if file ~= nil then 48 | -- If it exists: 49 | io.close(file) 50 | naughty.notification({ 51 | icon = path, 52 | title = 'Screenshot', 53 | message = 'Copied to clipboard!', 54 | actions = { save, discard } 55 | }) 56 | else 57 | -- If it doesn't: 58 | naughty.notification({ 59 | icon = beautiful.notification_cancel, 60 | title = 'Screenshot', 61 | message = 'Cancelled!', 62 | actions = { ok } 63 | }) 64 | end 65 | end 66 | 67 | -- Takes a screenshot and puts it in `/tmp`, then copies it to system clipboard 68 | -- and notifies about the result. 69 | local function take_screenshot(cmd) 70 | local tmp = '/tmp/ss-' .. os.date('%Y%m%d-%H%M%S') .. '.png' 71 | awful.spawn.easy_async_with_shell(cmd .. ' ' .. tmp, function() 72 | awful.spawn.with_shell('xclip -selection clip -t image/png -i ' .. tmp) 73 | send_notif(tmp) 74 | end) 75 | end 76 | 77 | return { 78 | screen = function() take_screenshot('maim') end, 79 | selection = function() take_screenshot('maim -s') end, 80 | delayed = function(s) take_screenshot('sleep ' .. s .. '; maim') end 81 | } 82 | -------------------------------------------------------------------------------- /script/tym-themer.lua: -------------------------------------------------------------------------------- 1 | -- Writes a tym colorcheme using current colorscheme. 2 | 3 | local c = require(require('beautiful').colorscheme) 4 | 5 | local theme = string.format([[ 6 | return { 7 | color_window_background = "%s", 8 | color_cursor_foreground = "%s", 9 | color_background = "%s", 10 | color_0 = "%s", 11 | color_foreground = "%s", 12 | color_7 = "%s", 13 | color_bold = "%s", 14 | color_8 = "%s", 15 | color_15 = "%s", 16 | color_cursor = "%s", 17 | color_9 = "%s", 18 | color_1 = "%s", 19 | color_10 = "%s", 20 | color_2 = "%s", 21 | color_11 = "%s", 22 | color_3 = "%s", 23 | color_12 = "%s", 24 | color_4 = "%s", 25 | color_13 = "%s", 26 | color_5 = "%s", 27 | color_14 = "%s", 28 | color_6 = "%s" 29 | } 30 | ]], 31 | c.bg0, c.bg0, c.bg0, c.bg0, c.fg0, c.fg2, c.fg0, c.bg2, c.fg0, c.fg0, 32 | c.red, c.red, c.green, c.green, c.blue, c.blue, c.yellow, c.yellow, 33 | c.magenta, c.magenta, c.cyan, c.cyan 34 | ) 35 | 36 | return function() 37 | local file = io.open(os.getenv('HOME') .. '/.config/tym/theme.lua', 'w+') 38 | if file == nil then return end 39 | file:write(theme) 40 | file:close() 41 | end 42 | -------------------------------------------------------------------------------- /script/xresources.lua: -------------------------------------------------------------------------------- 1 | -- Writes Xresources using current colorscheme and a few other things, does not override 2 | -- your `~/.Xresources` file, instead, it creates a `~/.local/share/gwileful/xresources` 3 | -- file which is merged with your present configuration on AwesomeWM startup. 4 | local require, io = require, io 5 | 6 | local awful = require('awful') 7 | local beautiful = require('beautiful') 8 | 9 | local color = require(beautiful.colorscheme) 10 | local path = beautiful.data_dir .. 'xresources' 11 | 12 | local theme = string.format([[ 13 | Nsxiv.window.background: %s 14 | Nsxiv.window.foreground: %s 15 | Nsxiv.bar.background: %s 16 | Nsxiv.bar.foreground: %s 17 | Nsxiv.mark.foreground: %s 18 | Nsxiv.bar.font: %s 19 | ]], 20 | color.bg0, color.accent, color.bg1, color.fg0, color.bccent, 21 | (beautiful.font_bitm .. beautiful.bitm_size):gsub(' ', '-') 22 | ) 23 | 24 | return function() 25 | local file = io.open(path, 'w') 26 | -- Create file if it doesn't exist. 27 | if file == nil then 28 | awful.spawn.with_shell('mkdir -p ' .. beautiful.data_dir .. '; touch ' .. path) 29 | file = io.open(path, 'w') 30 | end 31 | -- If it was still somehow impossible to access the file, stop. 32 | if file == nil then return end 33 | 34 | -- Update Xresources. 35 | file:write(theme) 36 | file:close() 37 | awful.spawn('xrdb -override ' .. path) 38 | end 39 | -------------------------------------------------------------------------------- /signal/client.lua: -------------------------------------------------------------------------------- 1 | local client = client 2 | 3 | -- Add a titlebar if titlebars_enabled is set to true for the client in `config/rules.lua`. 4 | client.connect_signal('request::titlebars', function(c) 5 | -- Some clients don't actually want to have a titlebar. 6 | if c.requests_no_titlebar then return end 7 | 8 | require('ui.titlebar').normal(c) 9 | end) 10 | 11 | -- Enable sloppy focus, so that focus follows mouse. 12 | -- client.connect_signal('mouse::enter', function(c) 13 | -- c:activate({ context = 'mouse_enter', raise = false }) 14 | -- end) 15 | -------------------------------------------------------------------------------- /signal/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | -- Allows all signals to be connected and/or emitted. 4 | return { 5 | client = require(... .. '.client'), 6 | -- NOTE: The `tag` file must be loaded before the `screen` one so that 7 | -- the correct layouts defined in `config.user` are appended to the tags 8 | -- upon creation. 9 | tag = require(... .. '.tag'), 10 | screen = require(... .. '.screen'), 11 | naughty = require(... .. '.naughty') 12 | } 13 | -------------------------------------------------------------------------------- /signal/naughty.lua: -------------------------------------------------------------------------------- 1 | --- Notifications 2 | local require = require 3 | 4 | -- Something particularly odd about naughty is that you must set position and screen 5 | -- setting through ruled, since trying to do that on the `naughty.notification.layout_box` 6 | -- directly will result in some really buggy behavior. Even more weirdly, notification 7 | -- margins must be set through a `beautiful` variable (in `theme/theme.lua`). 8 | local ruled = require('ruled') 9 | ruled.notification.connect_signal('request::rules', function() 10 | ruled.notification.append_rule({ 11 | rule = {}, 12 | properties = { 13 | position = 'bottom_right' 14 | } 15 | }) 16 | end) 17 | 18 | -- Defines the default notification layout. Given I have OSDs now, special notifications 19 | -- won't be a necessity. 20 | require('naughty').connect_signal('request::display', function(n) 21 | require('ui.notification')(n) 22 | end) 23 | -------------------------------------------------------------------------------- /signal/screen.lua: -------------------------------------------------------------------------------- 1 | local require, screen, table = require, screen, table 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local user = require('config.user') 10 | 11 | --- Attach tags and require('ui') to all screens. 12 | screen.connect_signal('request::desktop_decoration', function(s) 13 | -- Create all tags and attach the layouts to each of them. 14 | local tags = {} 15 | for i = 1, user.tags - 1, 1 do 16 | table.insert(tags, i) 17 | end 18 | awful.tag(tags, s, awful.layout.layouts[1]) 19 | awful.tag.add(user.tags, { screen = s, layout = awful.layout.suit.floating }) 20 | 21 | -- Add padding to the screens themselves. 22 | s.padding = dpi(user.tag_padding) 23 | 24 | -- Attach a bar to each screen. 25 | s.bar = require('ui.wibar')(s) 26 | s.launcher = require('ui.launcher')(s) 27 | 28 | -- And some other widgets only to the `main` screen. 29 | if s == awful.screen.focused() then 30 | s.dash = require('ui.dash')(s) 31 | s.time = require('ui.time')(s) 32 | s.osds = require('ui.osd')(s) 33 | end 34 | end) 35 | 36 | --- Wallpaper. 37 | if not beautiful.solid_wallpaper then 38 | -- https://awesomewm.org/apidoc/utility_libraries/gears.wallpaper.html 39 | require('gears').wallpaper.maximized(beautiful.wallpaper) 40 | else 41 | screen.connect_signal('request::wallpaper', function(s) 42 | awful.wallpaper({ 43 | screen = s, 44 | widget = { 45 | widget = wibox.container.background, 46 | bg = beautiful.wallpaper 47 | } 48 | }) 49 | end) 50 | end 51 | -------------------------------------------------------------------------------- /signal/system/battery.lua: -------------------------------------------------------------------------------- 1 | -- See: https://lazka.github.io/pgi-docs/UPowerGlib-1.0/classes/Device.html 2 | local require, string, ipairs = require, string, ipairs 3 | 4 | local gears = require('gears') 5 | 6 | local upower = require('lgi').require('UPowerGlib') 7 | local client = upower.Client() 8 | local user = require('config.user') 9 | 10 | local instance = nil 11 | 12 | local battery = {} 13 | 14 | -- Default to BAT0 if the user doesn't set it, in which case the user may not actually 15 | -- have a battery, which won't get past the for loop later on, which checks that 16 | -- `device_path` matches a present battery before attempting to get data from it. 17 | battery.device_path = '/org/freedesktop/UPower/devices/battery_' .. (user.battery_name or 'BAT0') 18 | -- The only relevant ones for this use case are the first five. 19 | battery.device_state = { 20 | 'UNKNOWN', 'CHARGING', 'DISCHARGING', 'EMPTY', 'FULLY_CHARGED', 21 | 'PENDING_CHARGE', 'PENDING_DISCHARGE', 'LAST' 22 | } 23 | -- For some reason, this enum contains some unused values, they were set to nil here. 24 | battery.device_level = { 25 | 'UNKNOWN', 'NONE', nil, 'LOW', 'CRITICAL', nil, 'NORMAL', 'HIGH', 'FULL', nil 26 | } 27 | 28 | local function new() 29 | local self = gears.object({}) 30 | gears.table.crush(self, battery, true) 31 | 32 | local devices = client:get_devices() 33 | if devices ~= nil then 34 | -- Check the device the user wants tracked is actually present. 35 | local dev_found = false 36 | for _, dev in ipairs(devices) do 37 | if dev:get_object_path() == self.device_path then 38 | dev_found = true 39 | break 40 | end 41 | end 42 | if dev_found then 43 | -- Given the signal is only emitted if a valid device is found, widgets can be made 44 | -- to simply start off with `visible = false` and have that changed to `true` upon 45 | -- connecting this signal. 46 | require('module.awesome-battery_widget')({ 47 | device_path = self.device_path, 48 | instant_update = true 49 | }):connect_signal('upower::update', function(_, device) 50 | self:emit_signal('update', 51 | tonumber(string.format('%.0f', device.percentage)), 52 | self.device_state[device.state + 1], 53 | self.device_level[device.battery_level + 1], 54 | tonumber(string.format('%.0f', device.time_to_empty / 60)), 55 | tonumber(string.format('%.0f', device.time_to_full / 60)) 56 | ) 57 | end) 58 | end 59 | end 60 | 61 | return self 62 | end 63 | 64 | if not instance then 65 | instance = new() 66 | end 67 | 68 | return instance 69 | -------------------------------------------------------------------------------- /signal/system/network.lua: -------------------------------------------------------------------------------- 1 | local require, string, table, ipairs, print = require, string, table, ipairs, print 2 | 3 | local gears = require('gears') 4 | 5 | local lgi = require('lgi') 6 | local dbus = require('module.dbus_proxy') 7 | 8 | local instance = nil 9 | local net = {} 10 | 11 | local _NM_status, NM = pcall(function() 12 | return lgi.NM 13 | end) 14 | 15 | if not _NM_status or not NM then 16 | gears.debug.print_warning('Failed to connect to NM interface.') 17 | return 18 | end 19 | 20 | local DEV_TYPE = { WIRED = 1, WIRELESS = 2 } 21 | 22 | local function flags_to_string(flags, wpa_flags, rsn_flags) 23 | local str = '' 24 | 25 | if flags == 1 and wpa_flags == 0 and rsn_flags == 0 then 26 | str = str .. ' WEP' 27 | end 28 | if wpa_flags ~= 0 then 29 | str = str .. ' WPA1' 30 | end 31 | if rsn_flags == 0 then 32 | str = str .. ' WPA2' 33 | end 34 | if wpa_flags == 512 or rsn_flags == 512 then 35 | str = str .. ' 802.1X' 36 | end 37 | 38 | return str 39 | end 40 | 41 | local function get_ap_conns(self, ssid) 42 | local conns = {} 43 | 44 | for _, conn_path in ipairs(self._private.settings:ListConnections()) do 45 | local conn = dbus.Proxy:new({ 46 | bus = dbus.Bus.SYSTEM, 47 | name = 'org.freedesktop.NetworkManager', 48 | interface = 'org.freedesktop.NetworkManager.Settings.Connection', 49 | path = conn_path 50 | }) 51 | 52 | if string.find(conn.Filename, ssid) then 53 | table.insert(conns, conn) 54 | end 55 | end 56 | 57 | return conns 58 | end 59 | 60 | local function get_dev_proxy(self) 61 | local devs = self._private.client:GetDevices() 62 | for _, dev_path in ipairs(devs) do 63 | local dev = dbus.Proxy:new({ 64 | bus = dbus.Bus.SYSTEM, 65 | name = 'org.freedesktop.NetworkManager', 66 | interface = 'org.freedesktop.NetworkManager.Device', 67 | path = dev_path 68 | }) 69 | 70 | if dev.DeviceType == DEV_TYPE.WIRELESS then 71 | self._private.dev = dev 72 | self._private.wifi = dbus.Proxy:new({ 73 | bus = dbus.Bus.SYSTEM, 74 | name = 'org.freedesktop.NetworkManager', 75 | interface = 'org.freedesktop.NetworkManager.Device.Wireless', 76 | path = dev_path 77 | }) 78 | self._private.dev:connect_signal('StateChanged', function(proxy, new, old, reason) 79 | -- idk 80 | end) 81 | elseif dev.DeviceType == DEV_TYPE.WIRED then 82 | self._private.dev = dev 83 | self._private.wired = dbus.Proxy:new({ 84 | bus = dbus.Bus.SYSTEM, 85 | name = 'org.freedesktop.NetworkManager', 86 | interface = 'org.freedesktop.NetworkManager.Device.Ethernet', 87 | path = dev_path 88 | }) 89 | self._private.dev:connect_signal('StateChanged', function(proxy, new, old, reason) 90 | -- idk 91 | end) 92 | end 93 | end 94 | end 95 | 96 | -- O(n*log(n)) 97 | -- **Considering the amount of connections existing to an access point negligible in 98 | -- comparison to the amount of present access points. 99 | -- ***Lua `table.sort` is a C programmed quicksort implementation. 100 | function net:scan_aps() 101 | if self._private.wifi == nil then return end 102 | 103 | -- Reset access point list. 104 | self._private.aps = {} 105 | self._private.wifi:RequestScanAsync(function(_, _, _, failure) 106 | if failure ~= nil then 107 | gears.debug.print_warning('Failed to scan for access points.') 108 | self:emit_signal('wireless::scan::failed', tostring(failure.code)) 109 | return 110 | end 111 | 112 | -- Scan all access points. 113 | local aps = self._private.wifi:GetAccessPoints() 114 | for _, ap_path in ipairs(aps) do 115 | -- Create a dbus proxy for the access point. 116 | local ap = dbus.Proxy:new({ 117 | bus = dbus.Bus.SYSTEM, 118 | name = 'org.freedesktop.NetworkManager', 119 | interface = 'org.freedesktop.NetworkManager.AccessPoint', 120 | path = ap_path 121 | }) 122 | 123 | if ap.Ssid ~= nil then 124 | -- Obtain its info. 125 | local ssid = NM.utils_ssid_to_utf8(ap.Ssid) 126 | local security = flags_to_string(ap.Flags, ap.WpaFlags, ap.RsnFlags) 127 | local password = '' 128 | local conns = get_ap_conns(self, ssid) 129 | 130 | -- If it has a saved connection, get its password. 131 | for _, conn in ipairs(conns) do 132 | if string.find(conn.Filename, ssid) then 133 | local secrets = conn:GetSecrets('802-11-wireless-security') 134 | if secrets ~= nil then 135 | password = secrets['802-11-wireless-security'].psk 136 | break 137 | end 138 | end 139 | end 140 | 141 | -- Add the access point info to the access point table. 142 | local ret = { 143 | raw_ssid = ap.Ssid, 144 | ssid = ssid, 145 | security = security, 146 | password = password, 147 | strength = ap.Strength, 148 | path = ap_path, 149 | dev_if = self._private.dev.Interface, 150 | dev_path = self._private.dev.object_path, 151 | hw_address = ap.HwAddress 152 | } 153 | gears.table.crush(ret, ap, true) 154 | 155 | print('Adding AP: ' .. ssid) 156 | table.insert(self._private.aps, ret) 157 | end 158 | end 159 | 160 | -- Sort the access point based on their signal strength. 161 | table.sort(self._private.aps, function(a, b) 162 | return a.strength > b.strength 163 | end) 164 | 165 | -- Return table of access points. 166 | print('Access point scan successful.') 167 | self:emit_signal('wireless::scan::success', self._private.aps) 168 | end) 169 | end 170 | 171 | function net:set_networking(state) 172 | self._private.client:Enable(state) 173 | end 174 | 175 | function net:get_aps() 176 | return self._private.aps 177 | end 178 | 179 | 180 | local function new() 181 | local self = gears.object({}) 182 | gears.table.crush(self, net, true) 183 | 184 | self._private = {} 185 | self._private.aps = {} 186 | 187 | self._private.client = dbus.Proxy:new({ 188 | bus = dbus.Bus.SYSTEM, 189 | name = 'org.freedesktop.NetworkManager', 190 | interface = 'org.freedesktop.NetworkManager', 191 | path = '/org/freedesktop/NetworkManager' 192 | }) 193 | 194 | self._private.settings = dbus.Proxy:new({ 195 | bus = dbus.Bus.SYSTEM, 196 | name = 'org.freedesktop.NetworkManager', 197 | interface = 'org.freedesktop.NetworkManager.Settings', 198 | path = '/org/freedesktop/NetworkManager/Settings' 199 | }) 200 | 201 | local client_props = dbus.Proxy:new({ 202 | bus = dbus.Bus.SYSTEM, 203 | name = 'org.freedesktop.NetworkManager', 204 | interface = 'org.freedesktop.Dbus.Properties', 205 | path = '/org/freedesktop/NetworkManager' 206 | }) 207 | 208 | local ap_scan_timer = gears.timer({ 209 | timeout = 5, 210 | callback = function() 211 | print('Attempting AP scan.') 212 | self:scan_aps() 213 | return false 214 | end 215 | }) 216 | 217 | client_props:connect_signal('PropertiesChanged', function(_, _, data) 218 | if not data.NetworkingEnabled then return end 219 | 220 | if (data.WirelessEnabled ~= nil and 221 | data.WirelessEnabled ~= self._private.wireless_enabled) then 222 | self._private.wireless_enabled = data.WirelessEnabled 223 | self:emit_signal('wireless::state', data.WirelessEnabled) 224 | 225 | if data.WirelessEnabled then 226 | ap_scan_timer:start() 227 | else 228 | ap_scan_timer:stop() 229 | end 230 | end 231 | end) 232 | 233 | get_dev_proxy() 234 | 235 | return self 236 | end 237 | 238 | if not instance then 239 | instance = new() 240 | end 241 | 242 | return instance 243 | -------------------------------------------------------------------------------- /signal/system/playerctl.lua: -------------------------------------------------------------------------------- 1 | local instance = nil 2 | 3 | local function new() 4 | return require('module.bling').signal.playerctl.lib({ 5 | player = { 'mpd', '%any' }, 6 | ignore = { 'firefox' }, 7 | update_on_activity = true, 8 | interval = 1, 9 | debounce_delay = 0.35 10 | }) 11 | end 12 | 13 | if not instance then 14 | instance = new() 15 | end 16 | 17 | return instance 18 | -------------------------------------------------------------------------------- /signal/system/weather.lua: -------------------------------------------------------------------------------- 1 | -- The original version of this script was inspired by ChadCat's, see: 2 | -- https://github.com/chadcat7/crystal/blob/the-awesome-config/signals/stat/weather.lua 3 | 4 | -- This version improves upon it by adding timeouts and other measures of control, to 5 | -- ensure that weather information is obtained or fails cleanly. 6 | local require, string, math, table = require, string, math, table 7 | 8 | local awful = require('awful') 9 | local gears = require('gears') 10 | 11 | local json = require('module.json') 12 | local user = require('config.user') 13 | 14 | local instance = nil 15 | 16 | -- Allow custom locations and units. If not set, use defaults! 17 | local args = { area = user.area, imperial = user.imperial } 18 | local conf = gears.table.crush({ 19 | area = '', 20 | imperial = false 21 | }, args, true) 22 | 23 | if conf.area == nil then 24 | gears.debug.print_warning('No location provided, using wttr.in default (IP-based)!') 25 | end 26 | 27 | -- The information source. 28 | local command = [[bash -c "curl -s --show-error -X GET '%s'"]] 29 | local url = 30 | 'wttr.in/' .. conf.area .. '?format=j1' 31 | local shell_cmd = string.format(command, url) 32 | 33 | -- Customizable values. 34 | local weather = { 35 | poll_wait = 720, 36 | timeout = 15, 37 | max_retries = 7 38 | } 39 | 40 | -- O our Father who art in heaven... 41 | local icon_map = { 42 | -- Daytime. 43 | ['113d'] = 'day_clear', 44 | ['116d'] = 'day_partly_cloudy', 45 | ['119d'] = 'day_cloudy', 46 | ['122d'] = 'day_cloudy', 47 | ['143d'] = 'day_fog', 48 | ['176d'] = 'day_light_rain', 49 | ['179d'] = 'day_light_rain', 50 | ['182d'] = 'day_light_rain', 51 | ['185d'] = 'day_light_rain', 52 | ['200d'] = 'day_storm', 53 | ['227d'] = 'day_snow', 54 | ['230d'] = 'day_snow', 55 | ['248d'] = 'day_fog', 56 | ['260d'] = 'day_fog', 57 | ['263d'] = 'day_light_rain', 58 | ['266d'] = 'day_light_rain', 59 | ['281d'] = 'day_light_rain', 60 | ['284d'] = 'day_light_rain', 61 | ['293d'] = 'day_light_rain', 62 | ['296d'] = 'day_light_rain', 63 | ['299d'] = 'day_rain', 64 | ['302d'] = 'day_rain', 65 | ['305d'] = 'day_rain', 66 | ['308d'] = 'day_rain', 67 | ['311d'] = 'day_light_rain', 68 | ['314d'] = 'day_light_rain', 69 | ['317d'] = 'day_light_rain', 70 | ['320d'] = 'day_snow', 71 | ['323d'] = 'day_snow', 72 | ['326d'] = 'day_snow', 73 | ['329d'] = 'day_snow', 74 | ['332d'] = 'day_snow', 75 | ['335d'] = 'day_snow', 76 | ['338d'] = 'day_snow', 77 | ['350d'] = 'day_light_rain', 78 | ['353d'] = 'day_light_rain', 79 | ['356d'] = 'day_rain', 80 | ['359d'] = 'day_rain', 81 | ['362d'] = 'day_light_rain', 82 | ['365d'] = 'day_light_rain', 83 | ['368d'] = 'day_snow', 84 | ['371d'] = 'day_snow', 85 | ['374d'] = 'day_light_rain', 86 | ['377d'] = 'day_light_rain', 87 | ['386d'] = 'day_storm', 88 | ['389d'] = 'day_storm', 89 | ['392d'] = 'day_snow', 90 | ['395d'] = 'day_snow', 91 | -- Nighttime. 92 | ['113n'] = 'night_clear', 93 | ['116n'] = 'night_partly_cloudy', 94 | ['119n'] = 'night_cloudy', 95 | ['122n'] = 'night_cloudy', 96 | ['143n'] = 'night_fog', 97 | ['176n'] = 'night_light_rain', 98 | ['179n'] = 'night_light_rain', 99 | ['182n'] = 'night_light_rain', 100 | ['185n'] = 'night_light_rain', 101 | ['200n'] = 'night_storm', 102 | ['227n'] = 'night_snow', 103 | ['230n'] = 'night_snow', 104 | ['248n'] = 'night_fog', 105 | ['260n'] = 'night_fog', 106 | ['263n'] = 'night_light_rain', 107 | ['266n'] = 'night_light_rain', 108 | ['281n'] = 'night_light_rain', 109 | ['284n'] = 'night_light_rain', 110 | ['293n'] = 'night_light_rain', 111 | ['296n'] = 'night_light_rain', 112 | ['299n'] = 'night_rain', 113 | ['302n'] = 'night_rain', 114 | ['305n'] = 'night_rain', 115 | ['308n'] = 'night_rain', 116 | ['311n'] = 'night_light_rain', 117 | ['314n'] = 'night_light_rain', 118 | ['317n'] = 'night_light_rain', 119 | ['320n'] = 'night_snow', 120 | ['323n'] = 'night_snow', 121 | ['326n'] = 'night_snow', 122 | ['329n'] = 'night_snow', 123 | ['332n'] = 'night_snow', 124 | ['335n'] = 'night_snow', 125 | ['338n'] = 'night_snow', 126 | ['350n'] = 'night_light_rain', 127 | ['353n'] = 'night_light_rain', 128 | ['356n'] = 'night_rain', 129 | ['359n'] = 'night_rain', 130 | ['362n'] = 'night_light_rain', 131 | ['365n'] = 'night_light_rain', 132 | ['368n'] = 'night_snow', 133 | ['371n'] = 'night_snow', 134 | ['374n'] = 'night_light_rain', 135 | ['377n'] = 'night_light_rain', 136 | ['386n'] = 'night_storm', 137 | ['389n'] = 'night_storm', 138 | ['392n'] = 'night_snow', 139 | ['395n'] = 'night_snow', 140 | } 141 | -- Amen. 142 | 143 | local function suffix(time) 144 | time = tonumber(time) 145 | return (time > 19 or time < 6) and 'n' or 'd' 146 | end 147 | 148 | local function hhmm_to_hh(time) 149 | return string.format('%02d', math.floor(tonumber(time) / 100)) 150 | end 151 | 152 | -- snow > storm > rain > fog > light_rain > cloudy > partly_cloudy > clear 153 | local weight_map = { 154 | ['day_snow'] = 7, 155 | ['night_snow'] = 7, 156 | ['day_storm'] = 6, 157 | ['night_storm'] = 6, 158 | ['day_rain'] = 5, 159 | ['night_rain'] = 5, 160 | ['day_fog'] = 4, 161 | ['night_fog'] = 4, 162 | ['day_light_rain'] = 3, 163 | ['night_light_rain'] = 3, 164 | ['day_cloudy'] = 2, 165 | ['night_cloudy'] = 2, 166 | ['day_partly_cloudy'] = 1, 167 | ['night_partly_cloudy'] = 1, 168 | ['day_clear'] = 0, 169 | ['night_clear'] = 0 170 | } 171 | 172 | local function new() 173 | local self = gears.object({}) 174 | gears.table.crush(self, weather, true) 175 | local retries = 0 176 | local data = {} 177 | 178 | -- This timer is a fetch attempt timeout. If it fails to get the weather info, it will 179 | -- wait for `weather.timeout` seconds and try again, up to `weather.max_retries` 180 | -- retries. After that it will give up and wait for the next emision of the `retry` 181 | -- signal. If information is obtained successfully, it's emitted through an object 182 | -- signal. 183 | self.timer = gears.timer({ 184 | timeout = self.timeout, 185 | call_now = true, 186 | single_shot = true, 187 | callback = function() 188 | awful.spawn.easy_async_with_shell(shell_cmd, function(out) 189 | if out == nil or out == '' then 190 | if retries < self.max_retries then 191 | print("Weather fetching failed, retrying...") 192 | retries = retries + 1 193 | self.timer:start() 194 | else 195 | retries = 0 196 | end 197 | else 198 | self:emit_signal('get', json.decode(out)) 199 | end 200 | end) 201 | end 202 | }) 203 | 204 | -- In charge of starting an attempt to fetch weather info. 205 | self:connect_signal('retry', function() 206 | self.timer:start() 207 | end) 208 | 209 | -- Emits weather info outward. 210 | self:connect_signal('get', function(_, res) 211 | if res.current_condition[1] == nil then 212 | gears.debug.print_warning('Failed to fetch weather info!') 213 | return 214 | end 215 | 216 | -- Right now! 217 | local current = res.current_condition[1] 218 | local today = res.weather[1] 219 | data.description = current.weatherDesc[1].value 220 | data.humidity = current.humidity .. '%' 221 | data.temperature = conf.imperial and current.temp_F .. '°F' 222 | or current.temp_C .. '°C' 223 | data.feels_like = conf.imperial and current.FeelsLikeF .. '°F' 224 | or current.FeelsLikeC .. '°C' 225 | data.icon = icon_map[current.weatherCode .. suffix(os.date('%H'))] 226 | data.max_temp = conf.imperial and today.maxtempF .. '°F' 227 | or today.maxtempC .. '°C' 228 | data.min_temp = conf.imperial and today.mintempF .. '°F' 229 | or today.mintempC .. '°C' 230 | 231 | -- The next 8 hours. 232 | data.by_hour = {} 233 | for i = 1, 8, 1 do 234 | local fc = today.hourly[i] 235 | local hour = { 236 | time = hhmm_to_hh(fc.time) .. ':00', 237 | description = fc.weatherDesc[1].value, 238 | humidity = fc.humidity .. '%', 239 | temperature = conf.imperial and fc.tempF .. '°F' 240 | or fc.tempC .. '°C', 241 | rain_chance = fc.chanceofrain .. '%', 242 | icon = icon_map[fc.weatherCode .. suffix(hhmm_to_hh(fc.time))] 243 | } 244 | table.insert(data.by_hour, hour) 245 | end 246 | 247 | -- The next 2 days. 248 | data.by_day = {} 249 | for i = 2, 3, 1 do 250 | local fc = res.weather[i] 251 | local day = {} 252 | day.max_temp = conf.imperial and fc.maxtempF .. '°F' 253 | or fc.maxtempC .. '°C' 254 | day.min_temp = conf.imperial and fc.mintempF .. '°F' 255 | or fc.mintempC .. '°C' 256 | 257 | -- NOTE: none of these are actually given. Must be calculated from hourly 258 | -- forecast. 259 | local worst = -1 260 | local icon = icon_map['113d'] 261 | local rain = 0 262 | local hum = 0 263 | for j = 1, #fc.hourly, 1 do 264 | -- Get icon. wttr.in does not provide an icon for a day's general weather, so 265 | -- we get that from the "worst" weather in the day. 266 | local hour = fc.hourly[j] 267 | local status = icon_map[hour.weatherCode .. suffix(hhmm_to_hh(hour.time))] 268 | if weight_map[status] > worst then 269 | worst = weight_map[status] 270 | icon = status 271 | end 272 | -- Get chance of rain as the worst chance of rain in the day. 273 | local chance = tonumber(hour.chanceofrain) 274 | if chance > rain then 275 | rain = chance 276 | end 277 | -- Get humidity as the mean humidity of the day. 278 | hum = hum + tonumber(hour.humidity) 279 | end 280 | day.icon = icon 281 | day.rain_chance = rain .. '%' 282 | day.humidity = math.floor(hum / #fc.hourly) .. '%' 283 | 284 | table.insert(data.by_day, day) 285 | end 286 | 287 | self:emit_signal('weather::data', data) 288 | retries = 0 289 | end) 290 | 291 | -- This timer runs `try` every `weather.poll_wait` seconds. It's running at all times 292 | -- and ensures the weather info gets updated. 293 | gears.timer({ 294 | timeout = self.poll_wait, 295 | autostart = true, 296 | callback = function() self:emit_signal('retry') end 297 | }) 298 | 299 | -- Same code as ran in the retry timer, meant for widgets to call directly. 300 | function self:request_data() 301 | if data.description ~= nil then 302 | self:emit_signal('weather::data', data) 303 | return 304 | end 305 | if not self.timer.started then 306 | awful.spawn.easy_async_with_shell(shell_cmd, function(out) 307 | -- Test and test and try... 308 | if not self.timer.started then 309 | if out == nil or out == '' then 310 | if retries < self.max_retries then 311 | retries = retries + 1 312 | self.timer:start() 313 | else 314 | retries = 0 315 | end 316 | else 317 | self:emit_signal('get', json.decode(out)) 318 | end 319 | end 320 | end) 321 | end 322 | end 323 | 324 | return self 325 | end 326 | 327 | if not instance then 328 | instance = new() 329 | end 330 | return instance 331 | -------------------------------------------------------------------------------- /signal/tag.lua: -------------------------------------------------------------------------------- 1 | --- Tag layouts. 2 | local tag = tag 3 | 4 | -- Appends all layouts defined in `config/user.lua` to all tags. 5 | tag.connect_signal('request::default_layouts', function() 6 | require('awful').layout.append_default_layouts(require('config.user').layouts) 7 | end) 8 | -------------------------------------------------------------------------------- /theme/assets/default/pfp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gwynsav/gwileful/a9bde5b98b9055fa432f74283a1ca9a33ae11b71/theme/assets/default/pfp.png -------------------------------------------------------------------------------- /theme/assets/fonts/gwnce.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gwynsav/gwileful/a9bde5b98b9055fa432f74283a1ca9a33ae11b71/theme/assets/fonts/gwnce.ttf -------------------------------------------------------------------------------- /theme/assets/notif/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gwynsav/gwileful/a9bde5b98b9055fa432f74283a1ca9a33ae11b71/theme/assets/notif/cancel.png -------------------------------------------------------------------------------- /theme/assets/notif/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gwynsav/gwileful/a9bde5b98b9055fa432f74283a1ca9a33ae11b71/theme/assets/notif/default.png -------------------------------------------------------------------------------- /theme/assets/util/awesome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gwynsav/gwileful/a9bde5b98b9055fa432f74283a1ca9a33ae11b71/theme/assets/util/awesome.png -------------------------------------------------------------------------------- /theme/color/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local user = require('config.user') 4 | 5 | local colorscheme = user.colorscheme or 'rose-pine' 6 | local style = user.style or 'dark' 7 | 8 | local path = 'theme.color.' .. colorscheme .. '.' .. style 9 | local palette = require(path) 10 | palette.transparent = '#00000000' 11 | 12 | return { 13 | palette = palette, 14 | path = path 15 | } 16 | -------------------------------------------------------------------------------- /theme/color/lite-xl/dark/init.lua: -------------------------------------------------------------------------------- 1 | local _C = {} 2 | 3 | _C.bg0 = '#252529' 4 | _C.bg1 = '#2e2e32' 5 | _C.bg2 = '#343434' 6 | _C.bg3 = '#414146' 7 | _C.bg4 = '#525259' 8 | _C.fg2 = '#83838f' 9 | _C.fg1 = '#97979c' 10 | _C.fg0 = '#e1e1e6' 11 | 12 | _C.red = '#f77483' 13 | _C.green = '#72b886' 14 | _C.yellow = '#f7c95c' 15 | _C.blue = '#1c7c9c' 16 | _C.magenta = '#e58ac9' 17 | _C.cyan = '#93ddfa' 18 | 19 | _C.accent = _C.yellow 20 | _C.bccent = _C.cyan 21 | 22 | return _C 23 | -------------------------------------------------------------------------------- /theme/color/lite-xl/light/init.lua: -------------------------------------------------------------------------------- 1 | local _C = {} 2 | 3 | _C.bg0 = '#fbfbfb' 4 | _C.bg1 = '#e8e8e8' 5 | _C.bg2 = '#e0e0e0' 6 | _C.bg3 = '#d0d0d0' 7 | _C.bg4 = '#b0b0b0' 8 | _C.fg2 = '#808080' 9 | _C.fg1 = '#404040' 10 | _C.fg0 = '#181818' 11 | 12 | _C.red = '#fc1785' 13 | _C.green = '#22a21f' 14 | _C.yellow = '#fb6620' 15 | _C.blue = '#1586d2' 16 | _C.magenta = '#aa2782' 17 | _C.cyan = '#1c7c9c' 18 | 19 | _C.accent = _C.blue 20 | _C.bccent = _C.magenta 21 | 22 | return _C 23 | 24 | -------------------------------------------------------------------------------- /theme/color/mardel/dark/init.lua: -------------------------------------------------------------------------------- 1 | local _C = {} 2 | 3 | _C.bg0 = '#1b1b1b' 4 | _C.bg1 = '#222222' 5 | _C.bg2 = '#2e2e2e' 6 | _C.bg3 = '#3c3c3c' 7 | _C.bg4 = '#545454' 8 | _C.fg2 = '#ababab' 9 | _C.fg1 = '#d4d4d4' 10 | _C.fg0 = '#eeeeee' 11 | 12 | _C.red = '#e45c79' 13 | _C.green = '#92bc55' 14 | _C.yellow = '#e0b05c' 15 | _C.blue = '#589ede' 16 | _C.magenta = '#ea93a9' 17 | _C.cyan = '#5ab1de' 18 | 19 | _C.accent = _C.green 20 | _C.bccent = _C.blue 21 | 22 | return _C 23 | -------------------------------------------------------------------------------- /theme/color/mardel/light/init.lua: -------------------------------------------------------------------------------- 1 | local _C = {} 2 | 3 | _C.bg0 = '#f5f5f5' 4 | _C.bg1 = '#edecea' 5 | _C.bg2 = '#dddcd8' 6 | _C.bg3 = '#c2c1bd' 7 | _C.bg4 = '#9b9a97' 8 | _C.fg2 = '#848483' 9 | _C.fg1 = '#666666' 10 | _C.fg0 = '#404040' 11 | 12 | _C.red = '#ac3251' 13 | _C.green = '#465e21' 14 | _C.yellow = '#865329' 15 | _C.blue = '#254f88' 16 | _C.magenta = '#97356f' 17 | _C.cyan = '#226277' 18 | 19 | _C.accent = _C.green 20 | _C.bccent = _C.blue 21 | 22 | return _C 23 | -------------------------------------------------------------------------------- /theme/color/oxocarbon/dark/init.lua: -------------------------------------------------------------------------------- 1 | local _C = {} 2 | 3 | _C.bg0 = '#161616' 4 | _C.bg1 = '#1e1e1e' 5 | _C.bg2 = '#262626' 6 | _C.bg3 = '#393939' 7 | _C.bg4 = '#525252' 8 | _C.fg2 = '#a4a4a4' 9 | _C.fg1 = '#d0d0d0' 10 | _C.fg0 = '#f2f2f2' 11 | 12 | _C.red = '#ee5396' 13 | _C.green = '#42be56' 14 | _C.yellow = '#ff7eb6' 15 | _C.blue = '#33b1ff' 16 | _C.magenta = '#be95ff' 17 | _C.cyan = '#3ddbd9' 18 | 19 | _C.accent = _C.cyan 20 | _C.bccent = _C.magenta 21 | 22 | return _C 23 | -------------------------------------------------------------------------------- /theme/color/oxocarbon/light/init.lua: -------------------------------------------------------------------------------- 1 | local _C = {} 2 | 3 | _C.bg0 = '#ffffff' 4 | _C.bg1 = '#f2f2f2' 5 | _C.bg2 = '#d0d0d0' 6 | _C.bg3 = '#bcbcbc' 7 | _C.bg4 = '#a4a4a4' 8 | _C.fg2 = '#525252' 9 | _C.fg1 = '#393939' 10 | _C.fg0 = '#161616' 11 | 12 | _C.red = '#ee5396' 13 | _C.green = '#42be56' 14 | _C.yellow = '#ff6f00' 15 | _C.blue = '#0f62fe' 16 | _C.magenta = '#673ab7' 17 | _C.cyan = '#08bdba' 18 | 19 | _C.accent = _C.yellow 20 | _C.bccent = _C.blue 21 | 22 | return _C 23 | -------------------------------------------------------------------------------- /theme/color/rose-pine/dark/init.lua: -------------------------------------------------------------------------------- 1 | local _C = {} 2 | 3 | _C.bg0 = '#191724' 4 | _C.bg1 = '#1f1d2e' 5 | _C.bg2 = '#21202e' 6 | _C.bg3 = '#26233a' 7 | _C.bg4 = '#524f67' 8 | _C.fg2 = '#6e6a86' 9 | _C.fg1 = '#908caa' 10 | _C.fg0 = '#e0def4' 11 | 12 | _C.red = '#eb6f92' 13 | _C.green = '#31748f' 14 | _C.yellow = '#f6c177' 15 | _C.blue = '#9ccfd8' 16 | _C.magenta = '#c4a7e7' 17 | _C.cyan = '#ebbcba' 18 | 19 | _C.accent = _C.yellow 20 | _C.bccent = _C.magenta 21 | 22 | return _C 23 | -------------------------------------------------------------------------------- /theme/color/rose-pine/light/init.lua: -------------------------------------------------------------------------------- 1 | local _C = {} 2 | 3 | _C.bg0 = '#fffaf3' 4 | _C.bg1 = '#faf4ed' 5 | _C.bg2 = '#f2e9e1' 6 | _C.bg3 = '#dad6d9' 7 | _C.bg4 = '#cecacd' 8 | _C.fg2 = '#9893a5' 9 | _C.fg1 = '#797593' 10 | _C.fg0 = '#575279' 11 | 12 | _C.red = '#b4637a' 13 | _C.green = '#286983' 14 | _C.yellow = '#ea9d34' 15 | _C.blue = '#56949f' 16 | _C.magenta = '#907aa9' 17 | _C.cyan = '#d7827e' 18 | 19 | _C.accent = _C.green 20 | _C.bccent = _C.magenta 21 | 22 | return _C 23 | -------------------------------------------------------------------------------- /theme/icons.lua: -------------------------------------------------------------------------------- 1 | local _I = { 2 | battery = {}, 3 | weather = {}, 4 | layout = {} 5 | } 6 | 7 | _I.font = 'gwnce ' 8 | -- The glyphs are actually 9px tall. 9 | _I.size = require('beautiful').xresources.apply_dpi(9) 10 | 11 | -- Power. 12 | _I['power_shutdown'] = '' 13 | _I['power_reboot'] = '' 14 | _I['power_suspend'] = '' 15 | _I['power_logoff'] = '' 16 | -- Battery. 17 | _I.battery['UNKNOWN'] = '' 18 | _I.battery['NONE'] = '' 19 | _I.battery['CRITICAL'] = '' 20 | _I.battery['LOW'] = '' 21 | _I.battery['NORMAL'] = '' 22 | _I.battery['HIGH'] = '' 23 | _I.battery['FULL'] = '' 24 | _I.battery['CHARGING'] = '' 25 | _I.battery['CHARGED'] = '' 26 | 27 | -- Weather. 28 | _I.weather['day_clear'] = '' 29 | _I.weather['day_partly_cloudy'] = '' 30 | _I.weather['day_cloudy'] = '' 31 | _I.weather['day_light_rain'] = '' 32 | _I.weather['day_rain'] = '' 33 | _I.weather['day_storm'] = '' 34 | _I.weather['day_snow'] = '' 35 | _I.weather['day_fog'] = '' 36 | _I.weather['night_clear'] = '' 37 | _I.weather['night_partly_cloudy'] = '' 38 | _I.weather['night_cloudy'] = '' 39 | _I.weather['night_light_rain'] = '' 40 | _I.weather['night_rain'] = '' 41 | _I.weather['night_storm'] = '' 42 | _I.weather['night_snow'] = '' 43 | _I.weather['night_fog'] = '' 44 | 45 | -- Network. 46 | _I['net_wifi_high'] = '' 47 | _I['net_wifi_normal'] = '' 48 | _I['net_wifi_low'] = '' 49 | _I['net_wifi_none'] = '' 50 | _I['net_wired_normal'] = '' 51 | _I['net_wired_none'] = '' 52 | _I['net_none'] = '' 53 | -- Bluetooth. 54 | _I['bluez_off'] = '' 55 | _I['bluez_scanning'] = '' 56 | _I['bluez_on'] = '' 57 | 58 | -- Media. 59 | _I['music'] = '' 60 | _I['music_previous'] = '' 61 | _I['music_next'] = '' 62 | _I['music_pause'] = '' 63 | _I['music_play'] = '' 64 | _I['music_loop'] = _I['power_reboot'] 65 | _I['music_shuffle'] = '' 66 | -- Audio. 67 | _I['audio_muted'] = '' 68 | _I['audio_decrease'] = '' 69 | _I['audio_increase'] = '' 70 | -- Microphone. 71 | _I['mic_muted'] = '' 72 | _I['mic_decrease'] = '' 73 | _I['mic_increase'] = '' 74 | 75 | -- Titlebar. 76 | _I['title_pin'] = '' 77 | _I['title_minimize'] = '' 78 | _I['title_maximize'] = '' 79 | _I['title_close'] = '' 80 | -- Layout. 81 | _I.layout['floating'] = '' 82 | _I.layout['tile'] = '' 83 | _I.layout['tileleft'] = '' 84 | _I.layout['tilebottom'] = '' 85 | 86 | -- Arrows. 87 | _I['arrow_up'] = '' 88 | _I['arrow_right'] = '' 89 | _I['arrow_down'] = '' 90 | _I['arrow_left'] = '' 91 | -- Miscelaneous. 92 | _I['util_magnifier'] = '' 93 | _I['util_hamburger'] = '' 94 | 95 | return _I 96 | -------------------------------------------------------------------------------- /theme/init.lua: -------------------------------------------------------------------------------- 1 | local require, io = require, io 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | 7 | -- Themes define colors, icons, font and wallpapers. 8 | beautiful.init(gears.filesystem.get_configuration_dir() .. 'theme/theme.lua') 9 | 10 | -- Check that the colorscheme to be set is actually different than current. 11 | -- If not, do nothing. 12 | local last_path = beautiful.state_dir .. 'last_colorscheme' 13 | 14 | local last = io.open(last_path, 'rb') 15 | local colors = nil 16 | if last == nil then 17 | awful.spawn.with_shell('mkdir -p ' .. beautiful.state_dir .. '; touch ' .. last_path) 18 | else 19 | last:close() 20 | -- It only really has one line. 21 | local lines = {} 22 | for line in io.lines(last_path) do 23 | lines[#lines + 1] = line 24 | end 25 | colors = lines[1] 26 | end 27 | if colors == beautiful.colorscheme then return end 28 | 29 | -- Set the tym colorscheme. 30 | require('script.tym-themer')() 31 | -- Merge the Xresources settings. 32 | require('script.xresources')() 33 | 34 | -- Update history file. 35 | last = io.open(last_path, 'w') 36 | if last == nil then return end 37 | last:write(beautiful.colorscheme) 38 | last:close() 39 | -------------------------------------------------------------------------------- /theme/theme.lua: -------------------------------------------------------------------------------- 1 | local require, os = require, os 2 | 3 | local gears = require('gears') 4 | 5 | local gc = gears.color 6 | local gfs = gears.filesystem 7 | local dpi = require('beautiful.xresources').apply_dpi 8 | 9 | local home = os.getenv('HOME') .. '/' 10 | local user = require('config.user') 11 | local color = require('theme.color') 12 | local asset = gfs.get_configuration_dir() .. 'theme/assets/' 13 | local colorscheme = color.palette 14 | 15 | local helpers = require('helpers') 16 | 17 | -- NOTE: try to keep all usages of `gears.color.recolor_image` within this file to prevent 18 | -- it from being executed multiple times. It will burn through your RAM at alarming speed. 19 | local _T = {} 20 | 21 | -- Return the path to the file to avoid having to: 22 | -- - 1: clone the entire colors table into beautiful. 23 | -- - 2: having to run the `theme.color` logic every time. 24 | -- This may have been a very smart or ridiculously stupid choice. We'll see. 25 | _T.colorscheme = color.path 26 | 27 | _T.solid_wallpaper = false 28 | if not helpers.file_exists(user.wallpaper) then 29 | if user.wallpaper == nil or not user.wallpaper:match('#%x+') then 30 | gears.debug.print_warning("Wallpaper: inexistent file or incorrect color format") 31 | _T.wallpaper = colorscheme.bg2 32 | else 33 | _T.wallpaper = user.wallpaper 34 | end 35 | _T.solid_wallpaper = true 36 | end 37 | 38 | if not user.lite or user.lite == nil then 39 | _T.pfp = gears.surface.load_uncached(helpers.file_exists(user.pfp) and user.pfp 40 | or asset .. 'default/pfp.png') 41 | if not _T.solid_wallpaper then 42 | _T.wallpaper = gears.surface.load_uncached(user.wallpaper) 43 | end 44 | else 45 | if not _T.solid_wallpaper then 46 | _T.wallpaper = user.wallpaper 47 | end 48 | end 49 | 50 | -- Fonts 51 | _T.font_bitm = 'angwish sans ' 52 | _T.font_mono = 'angwish ' 53 | _T.bitm_size = dpi(9) 54 | 55 | -- A few defaults. 56 | _T.font = _T.font_bitm .. _T.bitm_size 57 | _T.bg_normal = colorscheme.bg0 58 | _T.fg_normal = colorscheme.fg0 59 | 60 | -- Awesome Icon. 61 | _T.awesome_icon = gc.recolor_image(asset .. 'util/awesome.png', colorscheme.fg0) 62 | 63 | -- WM 64 | ----- 65 | _T.useless_gap = user.gaps or dpi(6) 66 | _T.master_width_factor = 0.58 67 | 68 | -- Borders. 69 | _T.border_width = 0 70 | 71 | -- Widgets 72 | ---------- 73 | -- Notifications. 74 | _T.notification_spacing = _T.useless_gap * 2 75 | _T.notification_default = gc.recolor_image(asset .. 'notif/default.png', colorscheme.fg0) 76 | _T.notification_cancel = gc.recolor_image(asset .. 'notif/cancel.png', colorscheme.red) 77 | 78 | -- Tooltips. 79 | _T.tooltip_border_color = colorscheme.bg3 80 | _T.tooltip_border_width = dpi(1) 81 | _T.tooltip_bg = colorscheme.bg0 82 | _T.tooltip_fg = colorscheme.fg0 83 | 84 | -- Systray. 85 | _T.bg_systray = colorscheme.bg1 86 | _T.systray_icon_spacing = dpi(2) 87 | 88 | -- Bling 89 | -------- 90 | -- Tabbar. 91 | _T.tabbar_disable = true 92 | 93 | -- Tag Preview. 94 | _T.tag_preview_widget_border_width = dpi(1) 95 | _T.tag_preview_widget_margin = _T.useless_gap 96 | _T.tag_preview_widget_bg = colorscheme.bg1 97 | _T.tag_preview_widget_border_color = colorscheme.bg3 98 | --- Clients. 99 | _T.tag_preview_client_opacity = 1 100 | _T.tag_preview_client_border_width = dpi(1) 101 | _T.tag_preview_client_bg = colorscheme.bg0 102 | _T.tag_preview_client_border_color = colorscheme.bg3 103 | 104 | -- Files 105 | -------- 106 | _T.data_dir = home .. '.local/share/gwileful/' 107 | _T.state_dir = home .. '.local/state/gwileful/' 108 | 109 | return _T 110 | -------------------------------------------------------------------------------- /ui/dash/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local beautiful = require('beautiful') 4 | local wibox = require('wibox') 5 | 6 | local dpi = beautiful.xresources.apply_dpi 7 | 8 | local color = require(beautiful.colorscheme) 9 | local mods = require('ui.dash.module') 10 | 11 | local width, height, margin = 352, 510, 6 12 | 13 | return function(s) 14 | local panel = wibox({ 15 | ontop = true, 16 | visible = false, 17 | width = dpi(width), 18 | height = dpi(height), 19 | bg = color.bg0, 20 | x = dpi(s.geometry.width - width - margin), 21 | y = dpi(margin + s.bar.height), 22 | border_width = dpi(1), 23 | border_color = color.bg3, 24 | widget = { 25 | layout = wibox.layout.align.vertical, 26 | { 27 | layout = wibox.layout.fixed.vertical, 28 | mods.user(), 29 | { 30 | widget = wibox.container.background, 31 | forced_height = dpi(1), 32 | bg = color.bg3 33 | } 34 | }, 35 | { 36 | widget = wibox.container.margin, 37 | margins = dpi(16), 38 | { 39 | layout = wibox.layout.fixed.vertical, 40 | spacing = dpi(16), 41 | mods.grid(), 42 | { 43 | widget = wibox.container.background, 44 | bg = color.bg3, 45 | forced_height = dpi(1) 46 | }, 47 | mods.player(), 48 | mods.slider(), 49 | { 50 | widget = wibox.container.background, 51 | bg = color.bg3, 52 | forced_height = dpi(1) 53 | }, 54 | { 55 | layout = wibox.layout.align.vertical, 56 | expand = 'none', 57 | nil, mods.random(), nil 58 | } 59 | } 60 | }, 61 | mods.title() 62 | } 63 | }) 64 | 65 | function panel:hide() 66 | self.visible = false 67 | end 68 | 69 | function panel:show() 70 | s.time:hide() 71 | self.visible = true 72 | end 73 | 74 | function panel:toggle() 75 | if self.visible then 76 | self:hide() 77 | else 78 | self:show() 79 | end 80 | end 81 | 82 | return panel 83 | end 84 | -------------------------------------------------------------------------------- /ui/dash/module/grid.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local color = require(beautiful.colorscheme) 10 | local widget = require('widget') 11 | local icons = require('theme.icons') 12 | 13 | -- local net = require('signal.system.network') 14 | 15 | return function() 16 | -- Returns a grid entry, with an icon, title, and body. 17 | -- @param args: 18 | -- - title: the entry title. 19 | -- - body: the entry body. 20 | -- - icon: the icon in normal state. 21 | -- - on_click: the action to execute on icon click. 22 | local function entry(args) 23 | local title = widget.textbox.colored({ 24 | text = args.title 25 | }) 26 | local body = widget.textbox.colored({ 27 | text = args.body, 28 | color = color.fg1 29 | }) 30 | local icon = widget.textbox.colored({ 31 | text = args.icon, 32 | font = icons.font .. icons.size * 2, 33 | align = 'center' 34 | }) 35 | 36 | local w = wibox.widget({ 37 | widget = wibox.container.background, 38 | bg = color.bg1, 39 | border_width = dpi(1), 40 | border_color = color.bg3, 41 | { 42 | widget = wibox.container.margin, 43 | margins = dpi(8), 44 | { 45 | layout = wibox.layout.fixed.horizontal, 46 | spacing = dpi(6), 47 | { 48 | widget = wibox.container.margin, 49 | margins = dpi(4), 50 | icon 51 | }, 52 | { 53 | widget = wibox.container.place, 54 | valign = 'center', 55 | halign = 'left', 56 | { 57 | layout = wibox.layout.fixed.vertical, 58 | title, 59 | body 60 | } 61 | } 62 | } 63 | }, 64 | buttons = { 65 | awful.button(nil, 1, args.on_click) 66 | } 67 | }) 68 | w:connect_signal('mouse::enter', function(self) 69 | self.border_color = color.accent 70 | icon.color = color.accent 71 | end) 72 | w:connect_signal('mouse::leave', function(self) 73 | self.border_color = color.bg3 74 | icon.color = color.fg0 75 | end) 76 | 77 | return w 78 | end 79 | 80 | local network = entry({ 81 | title = 'Network', 82 | body = 'No connection available', 83 | icon = icons['net_none'], 84 | -- on_click = function() net:toggle_networking() end 85 | on_click = function() end 86 | }) 87 | local bluetooth = entry({ 88 | title = 'Bluetooth', 89 | body = 'Powered on', 90 | icon = icons['bluez_on'], 91 | on_click = function() end 92 | }) 93 | 94 | return wibox.widget({ 95 | layout = wibox.layout.grid, 96 | orientation = 'horizontal', 97 | expand = true, 98 | spacing = dpi(8), 99 | network, 100 | bluetooth 101 | }) 102 | end 103 | -------------------------------------------------------------------------------- /ui/dash/module/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | local path = ... .. '.' 3 | return setmetatable({}, { 4 | __index = function(_, key) 5 | local module, _ = require(path .. key) 6 | return module 7 | end 8 | }) 9 | -------------------------------------------------------------------------------- /ui/dash/module/player.lua: -------------------------------------------------------------------------------- 1 | local require, math, string, collectgarbage = require, math, string, collectgarbage 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | local wibox = require('wibox') 7 | 8 | local dpi = beautiful.xresources.apply_dpi 9 | local pctl = require('signal.system.playerctl') 10 | 11 | local widget = require('widget') 12 | local color = require(beautiful.colorscheme) 13 | local icons = require('theme.icons') 14 | local player = require('ui.scratch').music 15 | local user = require('config.user') 16 | 17 | return function() 18 | -- The art is a bit special. 19 | local song_art = wibox.widget.imagebox() 20 | if not user.lite or user.lite == nil then 21 | song_art.image = gears.surface.crop_surface({ 22 | ratio = 3, 23 | surface = beautiful.wallpaper 24 | }) 25 | end 26 | 27 | local function button(icon, action) 28 | local w = widget.textbox.colored({ 29 | text = icon, 30 | font = icons.font .. icons.size 31 | }) 32 | w.buttons = { awful.button({}, 1, action) } 33 | w:connect_signal('mouse::enter', function(self) 34 | self.color = color.bccent 35 | end) 36 | w:connect_signal('mouse::leave', function(self) 37 | self.color = color.fg0 38 | end) 39 | return w 40 | end 41 | 42 | local song_icon = button(icons['music'], function() player:turn_on() end) 43 | local song_status = widget.textbox.colored({ 44 | text = 'Paused', 45 | color = color.fg1 46 | }) 47 | local song_player = widget.textbox.colored({ 48 | text = 'Source Unknown', 49 | color = color.fg1, 50 | align = 'right' 51 | }) 52 | local song_title = widget.textbox.scrolling({ 53 | text = 'Nothing Playing', 54 | font = beautiful.font_mono .. beautiful.bitm_size 55 | }) 56 | local song_artist = widget.textbox.scrolling({ 57 | text = 'by Unknown', 58 | font = beautiful.font_mono .. beautiful.bitm_size, 59 | color = color.fg1 60 | }) 61 | local song_album = widget.textbox.scrolling({ 62 | text = 'No album', 63 | font = beautiful.font_mono .. beautiful.bitm_size, 64 | color = color.fg2 65 | }) 66 | 67 | local prog_text = widget.textbox.colored({ 68 | text = '00:00 / 00:00', 69 | font = beautiful.font, 70 | color = color.fg1 71 | }) 72 | local prog_slider = wibox.widget({ 73 | widget = wibox.widget.slider, 74 | minimum = 0, 75 | maximum = 100, 76 | value = 0, 77 | bar_color = color.bg1, 78 | bar_active_color = color.bg3 79 | }) 80 | 81 | local play_pause = button(icons['music_play'], function() pctl:play_pause() end) 82 | local back = button(icons['music_previous'], function() pctl:previous() end) 83 | local forward = button(icons['music_next'], function() pctl:next() end) 84 | local loop = button(icons['music_loop'], function() pctl:cycle_loop_status() end) 85 | local loop_text = widget.textbox.colored({ text = 'None' }) 86 | local shuffle = button(icons['music_shuffle'], function() pctl:cycle_shuffle() end) 87 | local last_shuffle = false 88 | shuffle:connect_signal('mouse::leave', function(self) 89 | self.color = last_shuffle and color.accent or color.fg0 90 | end) 91 | 92 | local w = wibox.widget({ 93 | layout = wibox.layout.stack, 94 | song_art, 95 | { 96 | widget = wibox.container.background, 97 | bg = { 98 | type = 'linear', 99 | from = { 0, 0 }, 100 | to = { dpi(360), 0 }, 101 | stops = { 102 | { 0, color.bg0 .. 'EF' }, { 0.45, color.bg0 .. 'EF' }, 103 | { 0.73, color.bg0 .. 'CC' }, { 1, color.bg0 .. 'A0' } 104 | } 105 | }, 106 | border_width = dpi(1), 107 | border_color = color.bg3, 108 | { 109 | layout = wibox.layout.align.vertical, 110 | { 111 | widget = wibox.container.margin, 112 | margins = { 113 | top = dpi(16), bottom = dpi(14), 114 | left = dpi(16), right = dpi(16) 115 | }, 116 | { 117 | layout = wibox.layout.fixed.vertical, 118 | spacing = dpi(8), 119 | { 120 | layout = wibox.layout.align.horizontal, 121 | { 122 | layout = wibox.layout.fixed.horizontal, 123 | spacing = dpi(6), 124 | song_icon, 125 | song_status 126 | }, 127 | nil, 128 | song_player 129 | }, 130 | { 131 | layout = wibox.layout.fixed.vertical, 132 | song_title, 133 | song_artist, 134 | song_album 135 | }, 136 | { 137 | layout = wibox.layout.align.horizontal, 138 | prog_text, 139 | nil, 140 | { 141 | layout = wibox.layout.fixed.horizontal, 142 | spacing = dpi(4), 143 | back, 144 | play_pause, 145 | forward, 146 | { 147 | widget = wibox.container.margin, 148 | margins = { left = dpi(4), right = dpi(4) }, 149 | { 150 | layout = wibox.layout.fixed.horizontal, 151 | spacing = dpi(6), 152 | loop, 153 | loop_text 154 | } 155 | }, 156 | shuffle 157 | } 158 | } 159 | } 160 | }, 161 | nil, 162 | { 163 | layout = wibox.layout.fixed.vertical, 164 | { 165 | widget = wibox.container.background, 166 | bg = color.bg3, 167 | forced_height = dpi(1) 168 | }, 169 | { 170 | widget = wibox.container.constraint, 171 | strategy = 'exact', 172 | height = dpi(4), 173 | prog_slider 174 | } 175 | } 176 | } 177 | } 178 | }) 179 | 180 | -- Why not just use the `new` variable that the signal exposes? Because the first song 181 | -- that comes never counts as new, and so using the `new` variable results in the first 182 | -- song upon AwesomeWM reload not updating the widget. 183 | local last_poll = { 184 | title = nil, 185 | artist = nil, 186 | prog = 0 187 | } 188 | pctl:connect_signal('metadata', function(_, title, artist, cover, album, _, source) 189 | -- Whenever a new song comes through: 190 | if title ~= last_poll.title or artist ~= last_poll.artist then 191 | -- Update widget info. 192 | song_title.text = gears.string.xml_unescape(title or 'Unknown') 193 | song_artist.text = 'by ' .. (gears.string.xml_unescape(artist or 'Unknown')) 194 | song_album.text = 'on ' .. (gears.string.xml_unescape(album or 'Unknown')) 195 | song_player.text = 'via ' .. source 196 | -- Update image only if desired. 197 | if not user.lite or user.lite == nil then 198 | song_art.image = gears.surface.crop_surface({ 199 | ratio = 3, 200 | surface = gears.surface.load_uncached(cover or beautiful.wallpaper) 201 | }) 202 | end 203 | 204 | -- Update last poll info. 205 | last_poll.title = title 206 | last_poll.artist = artist 207 | last_poll.player = source 208 | 209 | -- Manually call Lua's gc to get rid of the old album art and prevent memory 210 | -- usage from stacking up. 211 | collectgarbage('collect') 212 | end 213 | end) 214 | 215 | pctl:connect_signal('position', function(_, prog, len, _) 216 | prog_slider.maximum = len 217 | prog_slider.value = prog 218 | prog_text.text = string.format('%02d:%02d', math.floor(prog / 60), prog % 60) 219 | .. ' / ' .. string.format('%02d:%02d', math.floor(len / 60), len % 60) 220 | end) 221 | 222 | pctl:connect_signal('playback_status', function(_, playing, _) 223 | if playing then 224 | prog_slider.bar_active_color = color.bg4 225 | song_status.text = 'Playing' 226 | play_pause.text = icons['music_pause'] 227 | else 228 | prog_slider.bar_active_color = color.bg3 229 | song_status.text = 'Paused' 230 | play_pause.text = icons['music_play'] 231 | end 232 | end) 233 | 234 | pctl:connect_signal('loop_status', function(_, loop_status, _) 235 | loop_text.text = loop_status:gsub('^%l', string.upper) 236 | end) 237 | 238 | pctl:connect_signal('shuffle', function(_, shuff, _) 239 | shuffle.color = shuff and color.accent or color.fg0 240 | last_shuffle = shuff 241 | end) 242 | 243 | -- Prevent overwriting of song progress when not using the bar to seek. 244 | -- Expecting a seek to differ by 4 seconds from the previous time seems to do the 245 | -- trick. 246 | local prog_hover = false 247 | prog_slider:connect_signal('mouse::enter', function() 248 | prog_hover = true 249 | end) 250 | prog_slider:connect_signal('mouse::leave', function() 251 | prog_hover = false 252 | end) 253 | prog_slider:connect_signal('property::value', function(_, new) 254 | if prog_hover and (new > last_poll.prog + 4 or new < last_poll.prog - 4) then 255 | pctl:set_position(new) 256 | end 257 | last_poll.prog = new 258 | end) 259 | 260 | return w 261 | end 262 | -------------------------------------------------------------------------------- /ui/dash/module/random.lua: -------------------------------------------------------------------------------- 1 | local wibox = require('wibox') 2 | 3 | local messages = { 4 | 'This is the world of the recycled vessel, created to avoid the destruction of all.', 5 | 'The Black Scrawl. A lost destiny. A white book. A false truth.', 6 | 'Soldiers of salt calling forth white death. They are Legion, those who plunged the' .. 7 | ' world into darkness.', 8 | 'The dragon\'s corpse brought death to the world, delivering unto it the power of ' .. 9 | 'magic.', 10 | 'The black sickness stains the future. They journey to return to soulless vessels.', 11 | 'The apocalypse divided the world in two: one that knows not day, and one which has' .. 12 | ' never seen night.', 13 | 'Black and White. Thirteen pacts. The vessels\' forms waver as they cross time and ' .. 14 | 'space.', 15 | 'The song of man has been drowned out. In its place, the scream of something inhuman.', 16 | 'The sky falls with the dragon. The world ends this day.', 17 | 'The puppet priest collects the accursed prayers and polishes the vessel.', 18 | 'So long as this memory exists -so long as mankind has hope- a bloody battle will ' .. 19 | 'be waged over the holy domain of the body.', 20 | 'Foolish human. Foolish human. Foolish human.\nFoolish vessel.', 21 | 'All is paid. All is sacrifice.', 22 | 'Do not bring back the light. Do not bring back the vessel. Do not bring back the ' .. 23 | 'future. Do not bring it back.', 24 | 'Every beam of light is an invitation to death.' 25 | } 26 | 27 | return function() 28 | return wibox.widget({ 29 | widget = wibox.widget.textbox, 30 | text = messages[math.random(#messages)], 31 | halign = 'center' 32 | }) 33 | end 34 | -------------------------------------------------------------------------------- /ui/dash/module/slider.lua: -------------------------------------------------------------------------------- 1 | local require, math = require, math 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local widget = require('widget') 10 | local pctl = require('module.bling').signal.playerctl.lib() 11 | local audio = require('signal.system.audio') 12 | local color = require(beautiful.colorscheme) 13 | local icons = require('theme.icons') 14 | 15 | -- Creates a slider, with an icon, label and percentage values attached. 16 | -- @param args: 17 | -- - label, the text shown next to the icon. 18 | -- - icon, the icon used in normal state. 19 | -- - icon_click, the action to perform when clicking the icon. 20 | -- - bar_action, what to do with new values of the slider. 21 | local function slider(args) 22 | -- Icon. 23 | local icon = widget.textbox.colored({ 24 | text = args.icon, 25 | font = icons.font .. icons.size 26 | }) 27 | local iconbox = wibox.widget({ 28 | widget = wibox.container.background, 29 | bg = color.bg2, 30 | { 31 | widget = wibox.container.margin, 32 | margins = { 33 | top = dpi(5), bottom = dpi(3), 34 | left = dpi(7), right = dpi(7) 35 | }, 36 | icon 37 | } 38 | }) 39 | iconbox.buttons = { awful.button(nil, 1, args.icon_click) } 40 | iconbox:connect_signal('mouse::enter', function() 41 | icon.color = color.accent 42 | end) 43 | iconbox:connect_signal('mouse::leave', function() 44 | icon.color = color.fg0 45 | end) 46 | 47 | local label = widget.textbox.colored({ text = args.label }) 48 | 49 | -- Slider. 50 | local bar = wibox.widget({ 51 | widget = wibox.widget.slider, 52 | bar_height = dpi(21), 53 | bar_color = color.bg1, 54 | handle_width = dpi(4), 55 | handle_border_width = 0, 56 | bar_active_color = color.bg2, 57 | handle_color = color.bg3, 58 | minimum = 0, 59 | maximum = 100, 60 | value = 0, 61 | id = 'slider_role' 62 | }) 63 | local is_hovered = false 64 | bar:connect_signal('mouse::enter', function(self) 65 | is_hovered = true 66 | self.handle_color = color.accent 67 | self.bar_border_color = color.accent 68 | end) 69 | bar:connect_signal('mouse::leave', function(self) 70 | is_hovered = false 71 | self.handle_color = color.bg3 72 | self.bar_border_color = color.bg3 73 | end) 74 | bar:connect_signal('property::value', function(_, val) 75 | if is_hovered then 76 | args.bar_action(_, val) 77 | end 78 | end) 79 | 80 | -- Non-interactable level. 81 | local level = widget.textbox.colored({ text = 'N/A' }) 82 | 83 | local border = wibox.widget({ 84 | widget = wibox.container.background, 85 | border_width = dpi(1), 86 | border_color = color.bg3 87 | }) 88 | border:connect_signal('mouse::enter', function(self) 89 | self.border_color = color.accent 90 | end) 91 | border:connect_signal('mouse::leave', function(self) 92 | self.border_color = color.bg3 93 | end) 94 | 95 | return wibox.widget({ 96 | widget = wibox.container.constraint, 97 | strategy = 'exact', 98 | height = dpi(21), 99 | { 100 | widget = border, 101 | { 102 | layout = wibox.layout.fixed.horizontal, 103 | iconbox, 104 | { 105 | layout = wibox.layout.stack, 106 | bar, 107 | { 108 | widget = wibox.container.margin, 109 | margins = { 110 | top = dpi(5), bottom = dpi(3), 111 | right = dpi(7) 112 | }, 113 | { 114 | layout = wibox.layout.align.horizontal, 115 | { 116 | widget = wibox.container.constraint, 117 | strategy = 'max', 118 | width = dpi(200), 119 | label 120 | }, 121 | nil, 122 | level 123 | } 124 | } 125 | } 126 | } 127 | }, 128 | set_value = function(_, value) 129 | bar.value = value 130 | end, 131 | set_level = function(_, value) 132 | level.text = value .. '%' 133 | end, 134 | set_label = function(_, value) 135 | label.text = value 136 | end, 137 | set_icon = function(_, value) 138 | icon.text = value 139 | end 140 | }) 141 | end 142 | 143 | local volume_slider = slider({ 144 | label = 'Audio Device Unknown', 145 | icon = icons['audio_muted'], 146 | icon_click = function() audio:default_sink_toggle_mute() end, 147 | bar_action = function(_, new) audio:default_sink_set_volume(new) end 148 | }) 149 | audio:connect_signal('sinks::default', function(_, default_sink) 150 | volume_slider.value = default_sink.volume 151 | volume_slider.level = default_sink.volume 152 | if default_sink.mute or default_sink.volume == 0 then 153 | volume_slider.icon = icons['audio_muted'] 154 | elseif default_sink.volume >= 50 then 155 | volume_slider.icon = icons['audio_increase'] 156 | else 157 | volume_slider.icon = icons['audio_decrease'] 158 | end 159 | volume_slider.label = default_sink.description 160 | end) 161 | 162 | local mic_slider = slider({ 163 | label = 'Microphone Unknown', 164 | icon = icons['mic_muted'], 165 | icon_click = function() audio:default_source_toggle_mute() end, 166 | bar_action = function(_, new) audio:default_source_set_volume(new) end 167 | }) 168 | audio:connect_signal('sources::default', function(_, default_source) 169 | mic_slider.value = default_source.volume 170 | mic_slider.level = default_source.volume 171 | if default_source.mute or default_source.volume == 0 then 172 | mic_slider.icon = icons['mic_muted'] 173 | elseif default_source.volume >= 50 then 174 | mic_slider.icon = icons['mic_increase'] 175 | else 176 | mic_slider.icon = icons['mic_decrease'] 177 | end 178 | mic_slider.label = default_source.description 179 | end) 180 | 181 | local music_slider = slider({ 182 | label = 'Player Unknown', 183 | icon = icons['music'], 184 | icon_click = function() end, 185 | bar_action = function(_, new) pctl:set_volume(new / 100) end 186 | }) 187 | pctl:connect_signal('volume', function(_, volume, player) 188 | music_slider.label = player 189 | local value = math.floor((volume or 0) * 100) 190 | music_slider.value = value 191 | music_slider.level = value 192 | end) 193 | 194 | return function() 195 | return wibox.widget({ 196 | layout = wibox.layout.fixed.vertical, 197 | spacing = dpi(9), 198 | music_slider, 199 | volume_slider, 200 | mic_slider 201 | }) 202 | end 203 | -------------------------------------------------------------------------------- /ui/dash/module/title.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local beautiful = require('beautiful') 4 | local wibox = require('wibox') 5 | 6 | local dpi = beautiful.xresources.apply_dpi 7 | local color = require(beautiful.colorscheme) 8 | local icons = require('theme.icons') 9 | 10 | return function() 11 | return wibox.widget({ 12 | layout = wibox.layout.fixed.vertical, 13 | { 14 | widget = wibox.container.background, 15 | bg = color.bg3, 16 | forced_height = dpi(1) 17 | }, 18 | { 19 | widget = wibox.container.background, 20 | bg = color.bg1, 21 | { 22 | layout = wibox.layout.align.horizontal, 23 | { 24 | widget = wibox.container.margin, 25 | margins = { 26 | top = dpi(6), bottom = dpi(6), 27 | left = dpi(10), right = dpi(10) 28 | }, 29 | { 30 | widget = wibox.widget.textbox, 31 | text = 'Quick Settings Menu', 32 | } 33 | }, 34 | nil, 35 | { 36 | layout = wibox.layout.fixed.horizontal, 37 | { 38 | widget = wibox.container.background, 39 | bg = color.bg3, 40 | forced_width = dpi(1) 41 | }, 42 | { 43 | widget = wibox.container.margin, 44 | margins = { 45 | left = dpi(8), right = dpi(8), 46 | top = dpi(6), bottom = dpi(6) 47 | }, 48 | { 49 | widget = wibox.widget.textbox, 50 | text = icons['util_hamburger'], 51 | font = icons.font .. icons.size 52 | } 53 | } 54 | } 55 | } 56 | } 57 | }) 58 | end 59 | -------------------------------------------------------------------------------- /ui/dash/module/user.lua: -------------------------------------------------------------------------------- 1 | local require, os, awesome = require, os, awesome 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | local wibox = require('wibox') 7 | 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local color = require(beautiful.colorscheme) 11 | local icons = require('theme.icons') 12 | local widget = require('widget') 13 | local conf = require('config.user') 14 | 15 | local pfp_widget 16 | if not conf.lite or conf.lite == nil then 17 | pfp_widget = wibox.widget({ 18 | widget = wibox.widget.imagebox, 19 | image = gears.surface.crop_surface({ 20 | surface = beautiful.pfp, 21 | ratio = 7/3, 22 | left = dpi(48) 23 | }) 24 | }) 25 | end 26 | 27 | local host = widget.textbox.colored({ 28 | text = '@host', 29 | font = beautiful.font_bitm .. beautiful.bitm_size * 2, 30 | align = 'right' 31 | }) 32 | local user_at_host = wibox.widget({ 33 | layout = wibox.layout.fixed.horizontal, 34 | widget.textbox.colored({ 35 | text = os.getenv('USER') or 'user', 36 | font = beautiful.font_bitm .. beautiful.bitm_size * 2, 37 | color = color.accent, 38 | align = 'right' 39 | }), 40 | host 41 | }) 42 | awful.spawn.easy_async_with_shell('uname -n', function(out) 43 | if out == nil or out == '' then return end 44 | host.text = '@' .. out:gsub('\n', '') 45 | end) 46 | 47 | -- Updated every minute. 48 | local uptime_widget = widget.textbox.colored({ 49 | text = 'Uptime Unknown!', 50 | color = color.fg1, 51 | align = 'right' 52 | }) 53 | gears.timer({ 54 | timeout = 60, 55 | call_now = true, 56 | autostart = true, 57 | callback = function() 58 | awful.spawn.easy_async('uptime -p', function(up) 59 | uptime_widget.text = up:gsub('\n', '') 60 | end) 61 | end 62 | }) 63 | 64 | local function button(icon, action) 65 | local w = widget.textbox.colored({ 66 | font = icons.font .. icons.size * 2, 67 | text = icon 68 | }) 69 | w.buttons = { awful.button(nil, 1, action) } 70 | w.set_action = function(self, new_action) 71 | self.buttons = { awful.button(nil, 1, new_action) } 72 | end 73 | w:connect_signal('mouse::enter', function(self) 74 | self.color = color.red 75 | end) 76 | w:connect_signal('mouse::leave', function(self) 77 | self.color = color.fg0 78 | end) 79 | return w 80 | end 81 | 82 | -- Power options revealer. 83 | local show_power = button(icons['arrow_right'], function() end) 84 | local power = wibox.widget({ 85 | layout = wibox.layout.fixed.horizontal, 86 | spacing = dpi(8), 87 | show_power, 88 | { 89 | layout = wibox.layout.fixed.horizontal, 90 | spacing = dpi(8), 91 | visible = false, 92 | id = 'power', 93 | button(icons['power_shutdown'], function() awful.spawn(conf.shutdown_cmd) end), 94 | button(icons['power_reboot'], function() awful.spawn(conf.reboot_cmd) end), 95 | button(icons['power_suspend'], function() awful.spawn(conf.suspend_cmd) end), 96 | button(icons['power_logoff'], function() awesome.quit() end) 97 | }, 98 | toggle_show_power = function(self) 99 | local power = self:get_children_by_id('power')[1] 100 | power.visible = not power.visible 101 | show_power.text = power.visible and icons['arrow_left'] or icons['arrow_right'] 102 | end 103 | }) 104 | show_power.action = function() 105 | power.toggle_show_power(power) 106 | end 107 | 108 | return function() 109 | return wibox.widget({ 110 | layout = wibox.layout.stack, 111 | { 112 | layout = wibox.layout.fixed.horizontal, 113 | { 114 | widget = wibox.container.constraint, 115 | strategy = 'exact', 116 | width = dpi(240), 117 | { 118 | layout = wibox.layout.stack, 119 | pfp_widget, 120 | { 121 | widget = wibox.container.background, 122 | bg = { 123 | type = 'linear', 124 | from = { dpi(240), 0 }, 125 | to = { 0, 0 }, 126 | stops = { 127 | { 0, color.bg1 }, { 0.33, color.bg1 .. 'F0' }, 128 | { 0.66, color.bg1 .. 'DC' }, { 1, color.bg1 .. 'C0' } 129 | } 130 | } 131 | } 132 | } 133 | }, 134 | { 135 | widget = wibox.container.background, 136 | bg = color.bg1, 137 | forced_width = dpi(115) 138 | } 139 | }, 140 | { 141 | widget = wibox.container.margin, 142 | margins = dpi(16), 143 | { 144 | layout = wibox.layout.align.vertical, 145 | { 146 | layout = wibox.layout.fixed.vertical, 147 | spacing = dpi(2), 148 | -- Text alignment sucks. 149 | { 150 | layout = wibox.layout.align.horizontal, 151 | nil, nil, 152 | user_at_host, 153 | }, 154 | uptime_widget 155 | }, 156 | nil, 157 | power 158 | } 159 | } 160 | }) 161 | end 162 | -------------------------------------------------------------------------------- /ui/launcher/init.lua: -------------------------------------------------------------------------------- 1 | -- Logic based largely (carbon copied, for the most part) on Stardust-kyun's launcher. 2 | -- I removed mouse support :P 3 | -- https://github.com/Stardust-kyun/dotfiles/blob/126b2df5edbec8044cbdbf13fb261f935311f6fe/home/.config/awesome/theme/launcher.lua 4 | local require, table, ipairs = require, table, ipairs 5 | 6 | local awful = require('awful') 7 | local beautiful = require('beautiful') 8 | local gears = require('gears') 9 | local wibox = require('wibox') 10 | 11 | local dpi = beautiful.xresources.apply_dpi 12 | local gio = require('lgi').Gio 13 | 14 | local user = require('config.user') 15 | local color = require(beautiful.colorscheme) 16 | local height, width, margin = 220, 540, 6 17 | local entry_max = 12 18 | 19 | return function(s) 20 | local default 21 | if not user.lite or user.lite == nil then 22 | default = gears.surface.crop_surface({ 23 | ratio = width / height, 24 | surface = beautiful.wallpaper 25 | }) 26 | end 27 | 28 | local launcher = wibox({ 29 | visible = false, 30 | ontop = true, 31 | width = dpi(width), 32 | height = dpi(height), 33 | bg = color.bg0, 34 | border_width = dpi(1), 35 | border_color = color.bg3 36 | }) 37 | 38 | --- Widgets 39 | ----------- 40 | local _W = {} 41 | _W.prompt = wibox.widget.textbox('Search') 42 | _W.entries = wibox.widget({ 43 | layout = wibox.layout.grid, 44 | spacing = dpi(8), 45 | expand = true, 46 | column_count = 3, 47 | minimum_row_height = dpi(29), 48 | homogenous = false 49 | }) 50 | 51 | launcher:setup({ 52 | layout = wibox.layout.stack, 53 | { 54 | widget = wibox.widget.imagebox, 55 | image = default 56 | }, 57 | { 58 | widget = wibox.container.background, 59 | bg = { 60 | type = 'linear', 61 | from = { 0, 0 }, 62 | to = { 0, dpi(height) }, 63 | stops = { 64 | { 0, color.bg0 .. 'A0' }, { 0.25, color.bg0 .. 'D0' }, 65 | { 0.4, color.bg0 .. 'F0' }, { 0.55, color.bg0 }, { 1, color.bg0 } 66 | } 67 | }, 68 | { 69 | widget = wibox.container.margin, 70 | margins = dpi(16), 71 | { 72 | layout = wibox.layout.fixed.vertical, 73 | spacing = dpi(16), 74 | { 75 | layout = wibox.layout.align.horizontal, 76 | { 77 | layout = wibox.layout.fixed.vertical, 78 | { 79 | widget = wibox.container.background, 80 | bg = color.bg0, 81 | { 82 | widget = wibox.container.margin, 83 | margins = dpi(8), 84 | forced_height = dpi(28), 85 | _W.prompt 86 | } 87 | }, 88 | { 89 | widget = wibox.container.background, 90 | bg = color.bg2, 91 | forced_height = dpi(1) 92 | } 93 | }, 94 | nil, nil 95 | }, 96 | _W.entries 97 | } 98 | } 99 | } 100 | }) 101 | 102 | --- App related 103 | --------------- 104 | local _A = {} 105 | 106 | -- Gets all entries. 107 | function _A.gen() 108 | local entry_list = {} 109 | for _, entry in ipairs(gio.AppInfo.get_all()) do 110 | if entry:should_show() then 111 | local name = 112 | entry:get_name():gsub("&", "&"):gsub("<", "<"):gsub("'", "'") 113 | table.insert(entry_list, { name = name, appinfo = entry }) 114 | end 115 | end 116 | return entry_list 117 | end 118 | 119 | -- Reduces shown entries to those matching the user's input, sorted and merged into one 120 | -- table. 121 | function _A.filter(cmd) 122 | _A.filtered = {} 123 | _A.reg_filtered = {} 124 | 125 | -- Filter entries matching `cmd` (user's input). 126 | for _, entry in ipairs(_A.unfiltered) do 127 | if entry.name:lower():sub(1, cmd:len()) == cmd:lower() then 128 | table.insert(_A.filtered, entry) 129 | elseif entry.name:lower():match(cmd:lower()) then 130 | table.insert(_A.reg_filtered, entry) 131 | end 132 | end 133 | -- Sort remaining entries. 134 | table.sort(_A.filtered, function(a, b) return a.name:lower() < b.name:lower() end) 135 | table.sort(_A.reg_filtered, function(a, b) return a.name:lower() < b.name:lower() end) 136 | -- Merge entries. 137 | for i = 1, #_A.reg_filtered do 138 | _A.filtered[#_A.filtered + 1] = _A.reg_filtered[i] 139 | end 140 | -- Clear entries. 141 | _W.entries:reset() 142 | _A.entry_index, _A.start_index = 1, 1 143 | 144 | -- Add filtered entries. 145 | for i, entry in ipairs(_A.filtered) do 146 | local widget = wibox.widget({ 147 | widget = wibox.container.background, 148 | bg = color.bg1, 149 | fg = color.fg0, 150 | border_width = dpi(1), 151 | border_color = color.bg1, 152 | { 153 | widget = wibox.container.margin, 154 | margins = { 155 | top = dpi(6), bottom = dpi(6), 156 | left = dpi(12), right = dpi(12) 157 | }, 158 | wibox.widget.textbox(entry.name) 159 | } 160 | }) 161 | 162 | widget.visible = (_A.start_index <= i and i <= _A.start_index + entry_max - 1) 163 | _W.entries:add(widget) 164 | if i == _A.entry_index then 165 | widget.fg = color.accent 166 | widget.border_color = color.bg3 167 | end 168 | end 169 | 170 | collectgarbage('collect') 171 | end 172 | 173 | function _A.open() 174 | -- Reset everything. 175 | _A.start_index, _A.entry_index = 1, 1 176 | -- Gets all entries. 177 | _A.unfiltered = _A.gen() 178 | _A.filter('') 179 | 180 | -- Populates prompt and makes it responsive. 181 | awful.prompt.run({ 182 | prompt = 'Searching: ', 183 | textbox = _W.prompt, 184 | -- Upon modifying the contents of the prompt. 185 | changed_callback = function(cmd) 186 | local input = cmd:gsub("[%[%]%(%)%.%-%+%?%*%%]", "%%%1") 187 | _A.filter(input) 188 | end, 189 | -- Upon pressing enter. 190 | exe_callback = function(cmd) 191 | local entry = _A.filtered[_A.entry_index] 192 | if entry then 193 | entry.appinfo:launch() 194 | else 195 | awful.spawn.with_shell(cmd) 196 | end 197 | end, 198 | -- When all else is done. 199 | done_callback = function() 200 | launcher.visible = false 201 | end 202 | }) 203 | end 204 | 205 | function launcher:open() 206 | launcher.visible = not launcher.visible 207 | 208 | if launcher.visible then 209 | _A.open() 210 | else 211 | awful.keygrabber.stop() 212 | end 213 | 214 | awful.placement.next_to(launcher, { 215 | margins = { top = dpi(margin), left = dpi(margin) }, 216 | geometry = s.bar, 217 | preferred_positions = 'bottom', 218 | preferred_anchors = 'front' 219 | }) 220 | end 221 | 222 | return launcher 223 | end 224 | -------------------------------------------------------------------------------- /ui/menu/init.lua: -------------------------------------------------------------------------------- 1 | local require, awesome, os = require, awesome, os 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local hotkeys = require('awful.hotkeys_popup') 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local apps = require('config.apps') 11 | local user = require('config.user') 12 | local color = require(beautiful.colorscheme) 13 | 14 | --- Menu 15 | local menu = {} 16 | local section = {} 17 | 18 | section.awesome = { 19 | { 'Hotkeys', function() hotkeys.show_help(nil, awful.screen.focused()) end }, 20 | { 'Documentation', apps.browser .. ' https://awesomewm.org/apidoc' }, 21 | { 'Configuration', apps.editor_cmd .. ' ' .. awesome.conffile }, 22 | { 'Reload', awesome.restart } 23 | } 24 | 25 | section.power = { 26 | { 'Log off', function() awesome.quit() end }, 27 | { 'Suspend', function() os.execute(user.suspend_cmd) end }, 28 | { 'Reboot', function() os.execute(user.reboot_cmd) end }, 29 | { 'Shutdown', function() os.execute(user.shutdown_cmd) end } 30 | } 31 | 32 | -- Create a main menu. 33 | menu.main = awful.menu({ 34 | theme = { 35 | font = beautiful.font, 36 | width = dpi(172), 37 | height = dpi(32), 38 | bg_normal = color.bg0, 39 | bg_focus = color.bg1, 40 | border_width = dpi(1), 41 | border_color = color.bg3 42 | }, 43 | items = { 44 | { 'Terminal', apps.terminal }, 45 | { 'Editor', apps.editor }, 46 | { 'Browser', apps.browser }, 47 | { 'Awesome', section.awesome }, 48 | { 'Power', section.power } 49 | } 50 | }) 51 | 52 | -- Add margins. 53 | menu.main.wibox:set_widget(wibox.widget({ 54 | widget = wibox.container.margin, 55 | margins = dpi(12), 56 | { 57 | widget = wibox.container.background, 58 | menu.main.wibox.widget 59 | } 60 | })) 61 | -- Repeat for submenus. 62 | awful.menu.old_new = awful.menu.new 63 | function awful.menu.new(...) 64 | local submenu = awful.menu.old_new(...) 65 | submenu.wibox.bg = color.bg0 66 | submenu.wibox.border_width = dpi(1) 67 | submenu.wibox.border_color = color.bg3 68 | submenu.wibox:set_widget(wibox.widget({ 69 | widget = wibox.container.background, 70 | bg = color.bg0, 71 | { 72 | widget = wibox.container.margin, 73 | margins = dpi(12), 74 | { 75 | widget = wibox.container.background, 76 | submenu.wibox.widget 77 | } 78 | } 79 | })) 80 | return submenu 81 | end 82 | 83 | return menu 84 | -------------------------------------------------------------------------------- /ui/notification/init.lua: -------------------------------------------------------------------------------- 1 | -- Credits to Aproxia for the timeout animation logic. 2 | -- https://github.com/Aproxia-dev 3 | local require, collectgarbage = require, collectgarbage 4 | 5 | local awful = require('awful') 6 | local beautiful = require('beautiful') 7 | local gears = require('gears') 8 | local naughty = require('naughty') 9 | local wibox = require('wibox') 10 | 11 | local dpi = beautiful.xresources.apply_dpi 12 | 13 | local widget = require('widget') 14 | local color = require(beautiful.colorscheme) 15 | 16 | local _N = {} 17 | 18 | function _N.title(n) 19 | return widget.textbox.scrolling({ 20 | text = ''..((n.title == nil or n.title == '') and 'AwesomeWM' or n.title)..'' 21 | }) 22 | end 23 | 24 | function _N.body(n) 25 | return widget.textbox.scrolling({ 26 | text = n.message, 27 | color = color.fg1 .. 'cc', 28 | dir = 'vertical' 29 | }) 30 | end 31 | 32 | function _N.icon(n) 33 | return wibox.widget({ 34 | widget = wibox.widget.imagebox, 35 | image = n.icon and gears.surface.crop_surface({ 36 | ratio = 1, 37 | surface = gears.surface.load_uncached(n.icon) 38 | }) or beautiful.notification_default, 39 | buttons = { awful.button(nil, 1, function() n:destroy() end) }, 40 | scaling_quality = 'nearest' 41 | }) 42 | end 43 | 44 | function _N.timeout() 45 | return wibox.widget({ 46 | widget = wibox.widget.progressbar, 47 | max_value = 100, 48 | value = 0, 49 | background_color = color.bg1, 50 | color = color.accent, 51 | forced_height = dpi(3) 52 | }) 53 | end 54 | 55 | function _N.actions(n) 56 | return wibox.widget({ 57 | widget = naughty.list.actions, 58 | notification = n, 59 | base_layout = wibox.widget({ 60 | layout = wibox.layout.flex.horizontal, 61 | spacing = dpi(2) 62 | }), 63 | style = { 64 | underline_normal = false, 65 | underline_selected = false, 66 | bg_normal = color.bg1 67 | }, 68 | widget_template = { 69 | widget = wibox.container.background, 70 | bg = color.bg4 .. '20', 71 | { 72 | widget = wibox.container.margin, 73 | margins = dpi(3), 74 | { 75 | widget = wibox.container.place, 76 | halign = 'center', 77 | { 78 | widget = wibox.widget.textbox, 79 | font = beautiful.font, 80 | id = 'text_role' 81 | } 82 | } 83 | } 84 | } 85 | }) 86 | end 87 | 88 | return function(n) 89 | -- Store original timeout and set it to an unreachable number. 90 | local timeout = n.timeout 91 | -- Using `math.huge` here breaks naughty :P. 92 | n.timeout = 999999 93 | local timeout_bar = _N.timeout() 94 | 95 | -- Sections, divided into blocks to avoid YandereDev levels of indentation. 96 | local titlebox = wibox.widget({ 97 | widget = wibox.container.background, 98 | bg = color.bg3, 99 | { 100 | widget = wibox.container.margin, 101 | margins = { bottom = dpi(1) }, 102 | { 103 | widget = wibox.container.background, 104 | bg = color.bg1, 105 | { 106 | widget = wibox.container.margin, 107 | margins = { 108 | top = dpi(8), bottom = dpi(8), 109 | left = dpi(12), right = dpi(12) 110 | }, 111 | { 112 | widget = wibox.container.place, 113 | halign = 'center', 114 | _N.title(n) 115 | } 116 | } 117 | } 118 | } 119 | }) 120 | 121 | local contentbox = wibox.widget({ 122 | layout = wibox.layout.align.vertical, 123 | { 124 | widget = wibox.container.margin, 125 | margins = dpi(12), 126 | { 127 | widget = wibox.container.constraint, 128 | strategy = 'max', 129 | width = dpi(280), 130 | height = dpi(250), 131 | { 132 | layout = wibox.layout.fixed.vertical, 133 | _N.body(n), 134 | { 135 | -- Add extra spacing to avoid having it look weird. 136 | widget = wibox.container.margin, 137 | margins = { top = dpi(4) }, 138 | -- This, however, makes you have to hide the spacing itself. 139 | visible = #n.actions > 0, 140 | _N.actions(n) 141 | } 142 | } 143 | } 144 | }, 145 | nil, 146 | { 147 | layout = wibox.layout.fixed.vertical, 148 | { 149 | widget = wibox.container.background, 150 | bg = color.bg3, 151 | forced_height = dpi(1) 152 | }, 153 | { 154 | -- Today I learnt setting a constraint on a progress bar makes it use its minimum 155 | -- required size. The number you input into width doesn't matter. 156 | widget = wibox.container.constraint, 157 | strategy = 'max', 158 | width = 0, 159 | timeout_bar 160 | } 161 | } 162 | }) 163 | 164 | local iconbox = wibox.widget({ 165 | widget = wibox.container.margin, 166 | margins = dpi(8), 167 | { 168 | widget = wibox.container.constraint, 169 | strategy = 'exact', 170 | width = dpi(45), 171 | height = dpi(45), 172 | _N.icon(n) 173 | } 174 | }) 175 | 176 | local layout = naughty.layout.box({ 177 | notification = n, 178 | cursor = 'hand2', 179 | border_width = 0, 180 | widget_template = { 181 | widget = wibox.container.constraint, 182 | strategy = 'max', 183 | height = dpi(320), 184 | width = dpi(360), 185 | { 186 | widget = wibox.container.constraint, 187 | strategy = 'min', 188 | width = dpi(120), 189 | { 190 | widget = wibox.container.background, 191 | bg = color.bg0, 192 | border_width = dpi(1), 193 | border_color = color.bg3, 194 | { 195 | layout = wibox.layout.fixed.horizontal, 196 | iconbox, 197 | { 198 | widget = wibox.container.background, 199 | bg = color.bg3, 200 | forced_width = dpi(1) 201 | }, 202 | { 203 | layout = wibox.layout.fixed.vertical, 204 | titlebox, 205 | contentbox 206 | } 207 | } 208 | } 209 | } 210 | } 211 | }) 212 | -- For some reason, doing this inside the `layout` declaration just doesn't work. You 213 | -- have to do it imperatively or it'll literally just get ignored. 214 | layout.buttons = {} 215 | 216 | -- Create an animation for the timeout. 217 | local anim = require('module.rubato').timed({ 218 | intro = 0, 219 | duration = timeout, 220 | subscribed = function(pos, time) 221 | timeout_bar.value = pos 222 | if time == timeout then 223 | n:destroy() 224 | collectgarbage('collect') 225 | end 226 | end 227 | }) 228 | -- Stop the timeout on notification hover. 229 | layout:connect_signal('mouse::enter', function() anim.pause = true end) 230 | layout:connect_signal('mouse::leave', function() anim.pause = false end) 231 | anim.target = 100 232 | 233 | return layout 234 | end 235 | -------------------------------------------------------------------------------- /ui/osd/init.lua: -------------------------------------------------------------------------------- 1 | local path = ... .. '.' 2 | 3 | return function(s) 4 | return { 5 | volume = require(path .. '.volume')(s), 6 | player = require(path .. '.player')(s) 7 | } 8 | end 9 | -------------------------------------------------------------------------------- /ui/osd/player.lua: -------------------------------------------------------------------------------- 1 | local require, math, string, awesome = require, math, string, awesome 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | local wibox = require('wibox') 7 | 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local color = require(beautiful.colorscheme) 11 | local pctl = require('signal.system.playerctl') 12 | local widget = require('widget') 13 | local icons = require('theme.icons') 14 | local user = require('config.user') 15 | 16 | local width, height, timeout = 270, 120, 3 17 | local ratio = width / (height - 24) 18 | 19 | return function(s) 20 | local _W = {} 21 | 22 | _W.cover = wibox.widget.imagebox() 23 | if not user.lite or user.lite == nil then 24 | _W.cover.image = gears.surface.crop_surface({ 25 | ratio = ratio, 26 | surface = beautiful.wallpaper 27 | }) 28 | end 29 | 30 | _W.icon = widget.textbox.colored({ 31 | text = icons['music'], 32 | font = icons.font .. icons.size, 33 | color = color.fg1 34 | }) 35 | _W.player = widget.textbox.colored({ 36 | text = 'N/A', 37 | color = color.fg1, 38 | align = 'right' 39 | }) 40 | _W.title = widget.textbox.scrolling({ 41 | text = 'Nothing Playing', 42 | font = beautiful.font_mono .. beautiful.bitm_size 43 | }) 44 | _W.artist = widget.textbox.scrolling({ 45 | text = 'by Unknown', 46 | font = beautiful.font_mono .. beautiful.bitm_size, 47 | color = color.fg1 48 | }) 49 | _W.album = widget.textbox.scrolling({ 50 | text = 'on N/A', 51 | font = beautiful.font_mono .. beautiful.bitm_size, 52 | color = color.fg2 53 | }) 54 | _W.progress = widget.textbox.colored({ 55 | text = '00:00 / 00:00', 56 | color = color.fg1 57 | }) 58 | _W.status = wibox.widget({ 59 | widget = wibox.container.constraint, 60 | { 61 | layout = wibox.layout.fixed.horizontal, 62 | spacing = dpi(8), 63 | { 64 | widget = widget.textbox.colored({ 65 | text = icons['music_pause'], 66 | font = icons.font .. icons.size, 67 | align = 'right' 68 | }), 69 | id = 'icon' 70 | }, 71 | { 72 | widget = widget.textbox.colored({ 73 | text = 'Paused', 74 | align = 'right' 75 | }), 76 | id = 'text' 77 | } 78 | }, 79 | set_icon = function(self, icon) 80 | self:get_children_by_id('icon')[1].text = icon 81 | end, 82 | set_status = function(self, text) 83 | self:get_children_by_id('text')[1].text = text 84 | end 85 | }) 86 | _W.volume = wibox.widget({ 87 | widget = wibox.widget.progressbar, 88 | background_color = color.bg1, 89 | color = color.fg0, 90 | margins = { 91 | left = dpi(9), right = dpi(9), 92 | top = dpi(4), bottom = dpi(4) 93 | } 94 | }) 95 | _W.volume_level = widget.textbox.colored({ 96 | text = icons['audio_muted'], 97 | font = icons.font .. icons.size, 98 | align = 'center' 99 | }) 100 | _W.volume_label = widget.textbox.colored({ 101 | text = 'N/A' 102 | }) 103 | 104 | local osd = wibox({ 105 | height = height, 106 | width = width, 107 | screen = s, 108 | bg = color.bg0, 109 | ontop = true, 110 | visible = false, 111 | border_width = dpi(1), 112 | border_color = color.bg3, 113 | widget = { 114 | layout = wibox.layout.fixed.vertical, 115 | { 116 | layout = wibox.layout.stack, 117 | _W.cover, 118 | { 119 | widget = wibox.container.background, 120 | bg = { 121 | type = 'linear', 122 | from = { 0, 0 }, 123 | to = { dpi(width), 0 }, 124 | stops = { 125 | { 0, color.bg0 .. 'EF' }, { 0.45, color.bg0 .. 'EF' }, 126 | { 0.73, color.bg0 .. 'CC' }, { 1, color.bg0 .. 'A0' } 127 | } 128 | }, 129 | { 130 | widget = wibox.container.margin, 131 | margins = dpi(12), 132 | { 133 | layout = wibox.layout.fixed.vertical, 134 | spacing = dpi(6), 135 | { 136 | layout = wibox.layout.align.horizontal, 137 | _W.icon, nil, _W.player 138 | }, 139 | { 140 | layout = wibox.layout.fixed.vertical, 141 | _W.title, 142 | _W.artist, 143 | _W.album 144 | }, 145 | { 146 | layout = wibox.layout.align.horizontal, 147 | _W.progress, nil, _W.status 148 | } 149 | } 150 | } 151 | } 152 | }, 153 | { 154 | widget = wibox.container.background, 155 | bg = color.bg3, 156 | forced_height = dpi(1) 157 | }, 158 | { 159 | widget = wibox.container.margin, 160 | margins = { 161 | top = dpi(6), bottom = dpi(6), 162 | left = dpi(12), right = dpi(12) 163 | }, 164 | { 165 | layout = wibox.layout.align.horizontal, 166 | _W.volume_level, 167 | _W.volume, 168 | _W.volume_label 169 | } 170 | } 171 | } 172 | }) 173 | 174 | local timer = gears.timer({ 175 | timeout = timeout, 176 | single_shot = true, 177 | callback = function() 178 | osd.visible = false 179 | end 180 | }) 181 | 182 | local function show() 183 | -- Hide all other OSDs if visible. 184 | awesome.emit_signal('osd::new', osd) 185 | -- Reset timer. 186 | if timer.started then 187 | timer:again() 188 | else 189 | osd.visible = true 190 | awful.placement.bottom(osd, { margins = { bottom = beautiful.useless_gap } }) 191 | timer:start() 192 | end 193 | end 194 | 195 | pctl:connect_signal('metadata', function(_, title, artist, cover, album, new, source) 196 | -- Update OSD. 197 | _W.player.text = (source or 'Unknown player') 198 | _W.title.text = gears.string.xml_unescape(title) or 'Unknown' 199 | _W.artist.text = 'by ' .. (gears.string.xml_unescape(artist) or 'Unknown') 200 | _W.album.text = 'on ' .. (gears.string.xml_unescape(album) or 'Unknown') 201 | -- Update cover only if desired. 202 | if not user.lite or user.lite == nil then 203 | _W.cover.image = gears.surface.crop_surface({ 204 | surface = gears.surface.load_uncached(cover or beautiful.wallpaper), 205 | ratio = ratio 206 | }) 207 | end 208 | -- GC old album covers. 209 | collectgarbage('collect') 210 | 211 | -- Show the OSD when a new song comes through. 212 | if new and not s.dash.visible then show() end 213 | end) 214 | 215 | pctl:connect_signal('playback_status', function(_, playing, _) 216 | -- Update OSD. 217 | if playing then 218 | _W.status.icon = icons['music_play'] 219 | _W.status.status = 'Playing' 220 | else 221 | _W.status.icon = icons['music_pause'] 222 | _W.status.status = 'Paused' 223 | end 224 | 225 | if not s.dash.visible then show() end 226 | end) 227 | 228 | pctl:connect_signal('volume', function(_, volume, _) 229 | _W.volume.value = volume 230 | volume = volume * 100 231 | _W.volume_label.text = volume .. '%' 232 | if volume == 0 then 233 | _W.volume_level.text = icons['audio_muted'] 234 | elseif volume < 50 then 235 | _W.volume_level.text = icons['audio_decrease'] 236 | else 237 | _W.volume_level.text = icons['audio_increase'] 238 | end 239 | 240 | if not s.dash.visible then show() end 241 | end) 242 | 243 | pctl:connect_signal('position', function(_, prog, len, _) 244 | _W.progress.text = string.format('%02d:%02d', math.floor(prog / 60), prog % 60) 245 | .. ' / ' .. string.format('%02d:%02d', math.floor(len / 60), len % 60) 246 | end) 247 | 248 | awesome.connect_signal('osd::new', function(new_osd) 249 | -- If the new osd is this one, do nothing. 250 | if new_osd == osd then return end 251 | -- Otherwise stop the timer and hide the osd if the timer is running. 252 | if timer.started then 253 | timer:stop() 254 | osd.visible = false 255 | end 256 | end) 257 | 258 | return osd 259 | end 260 | -------------------------------------------------------------------------------- /ui/osd/volume.lua: -------------------------------------------------------------------------------- 1 | local require, awesome = require, awesome 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | local wibox = require('wibox') 7 | 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local color = require(beautiful.colorscheme) 11 | local audio = require('signal.system.audio') 12 | local widget = require('widget') 13 | local icons = require('theme.icons') 14 | 15 | local width, height, timeout = 200, 32, 3 16 | 17 | return function(s) 18 | local icon = widget.textbox.colored({ 19 | text = icons['audio_muted'], 20 | font = icons.font .. icons.size, 21 | align = 'center' 22 | }) 23 | 24 | local progress = wibox.widget({ 25 | widget = wibox.widget.progressbar, 26 | background_color = color.bg1, 27 | color = color.fg0, 28 | max_value = 100, 29 | margins = { 30 | left = dpi(9), right = dpi(9), 31 | top = dpi(6), bottom = dpi(6) 32 | } 33 | }) 34 | 35 | local label = wibox.widget({ 36 | widget = wibox.widget.textbox, 37 | text = 'N/A' 38 | }) 39 | 40 | local osd = wibox({ 41 | x = (s.geometry.width - width) / 2, 42 | y = s.bar.height + beautiful.useless_gap, 43 | height = height, 44 | width = width, 45 | screen = s, 46 | bg = color.bg0, 47 | ontop = true, 48 | visible = false, 49 | border_width = dpi(1), 50 | border_color = color.bg3, 51 | widget = { 52 | widget = wibox.container.margin, 53 | margins = { 54 | left = dpi(12), right = dpi(12), 55 | top = dpi(9), bottom = dpi(9) 56 | }, 57 | { 58 | layout = wibox.layout.align.horizontal, 59 | icon, 60 | progress, 61 | label 62 | } 63 | } 64 | }) 65 | 66 | local timer = gears.timer({ 67 | timeout = timeout, 68 | single_shot = true, 69 | callback = function() 70 | osd.visible = false 71 | end 72 | }) 73 | 74 | local old = { mute = nil, level = nil, fresh = true } 75 | audio:connect_signal('sinks::default', function(_, default_sink) 76 | -- Sometimes, pactl gets pretty confused. 77 | if old.mute == default_sink.mute and old.level == default_sink.volume then 78 | return 79 | end 80 | 81 | -- Update OSD. 82 | if default_sink.mute or default_sink.volume == 0 then 83 | icon.text = icons['audio_muted'] 84 | elseif default_sink.volume >= 50 then 85 | icon.text = icons['audio_increase'] 86 | else 87 | icon.text = icons['audio_decrease'] 88 | end 89 | progress.value = default_sink.volume 90 | label.text = default_sink.volume .. '%' 91 | -- Update reference values. 92 | old.mute = default_sink.mute 93 | old.level = default_sink.volume 94 | 95 | -- Prevents the OSD from being shown when interacting with dashboard sliders. 96 | if s.dash.visible then return end 97 | -- Prevents the OSD from being shown on startup. 98 | if old.fresh then 99 | old.fresh = false 100 | return 101 | end 102 | -- Hide all other OSDs if visible. 103 | awesome.emit_signal('osd::new', osd) 104 | -- Reset timer. 105 | if timer.started then 106 | timer:again() 107 | else 108 | osd.visible = true 109 | awful.placement.bottom(osd, { margins = { bottom = beautiful.useless_gap } }) 110 | timer:start() 111 | end 112 | end) 113 | 114 | awesome.connect_signal('osd::new', function(new_osd) 115 | -- If the new osd is this one, do nothing. 116 | if new_osd == osd then return end 117 | -- Otherwise stop the timer and hide the osd if the timer is running. 118 | if timer.started then 119 | timer:stop() 120 | osd.visible = false 121 | end 122 | end) 123 | 124 | return osd 125 | end 126 | -------------------------------------------------------------------------------- /ui/scratch/init.lua: -------------------------------------------------------------------------------- 1 | -- If you came here looking to fix something, please know that the bling 2 | -- scratchpad module is pretty buggy and I didn't do anything! :P 3 | 4 | return { 5 | music = require(... .. '.music') 6 | } 7 | -------------------------------------------------------------------------------- /ui/scratch/music.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local awful = require('awful') 4 | local dpi = require('beautiful').xresources.apply_dpi 5 | 6 | local apps = require('config.apps') 7 | local scratch = require('module.bling').module.scratchpad 8 | 9 | local s = awful.screen.focused() 10 | local h = dpi(400) 11 | local w = dpi(600) 12 | 13 | return scratch({ 14 | -- `autoclose` is the buggiest shit on earth. 15 | command = apps.terminal .. ' --role="musicpad" -e ncmpcpp', 16 | rule = { role = 'musicpad' }, 17 | sticky = true, 18 | floating = true, 19 | geometry = { 20 | height = h, width = w, 21 | x = (s.geometry.width - w) / 2, 22 | y = (s.geometry.height - h) / 2 + s.bar.height 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /ui/time/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local beautiful = require('beautiful') 4 | local wibox = require('wibox') 5 | 6 | local dpi = beautiful.xresources.apply_dpi 7 | 8 | local color = require(beautiful.colorscheme) 9 | local mods = require('ui.time.module') 10 | 11 | local width, height, margin = dpi(300), dpi(370), dpi(6) 12 | 13 | return function(s) 14 | local panel = wibox({ 15 | ontop = true, 16 | visible = false, 17 | width = width, 18 | height = height, 19 | x = s.geometry.width - (margin + width), 20 | y = margin + s.bar.height, 21 | bg = color.bg0, 22 | border_width = dpi(1), 23 | border_color = color.bg3, 24 | widget = { 25 | widget = wibox.container.margin, 26 | margins = dpi(16), 27 | { 28 | layout = wibox.layout.fixed.vertical, 29 | spacing = dpi(16), 30 | mods.clock(), 31 | { 32 | widget = wibox.container.background, 33 | bg = color.bg3, 34 | forced_height = dpi(1) 35 | }, 36 | { 37 | widget = wibox.container.constraint, 38 | strategy = 'exact', 39 | height = dpi(270), 40 | mods.calendar.main_widget 41 | }, 42 | mods.weather() 43 | } 44 | } 45 | }) 46 | 47 | function panel:hide() 48 | self.visible = false 49 | end 50 | 51 | function panel:show() 52 | s.dash:hide() 53 | self.visible = true 54 | end 55 | 56 | function panel:toggle() 57 | if self.visible then 58 | self:hide() 59 | else 60 | self:show() 61 | end 62 | end 63 | 64 | local grown = false 65 | require('signal.system.weather'):connect_signal('weather::data', function() 66 | if not grown then 67 | panel:geometry({ 68 | x = panel.x, y = panel.y, width = panel.width, 69 | height = panel.height + dpi(200) 70 | }) 71 | grown = true 72 | end 73 | end) 74 | 75 | return panel 76 | end 77 | -------------------------------------------------------------------------------- /ui/time/module/calendar.lua: -------------------------------------------------------------------------------- 1 | -- From Myagko, see: 2 | -- https://github.com/myagko/dotfiles/blob/0122545e8245d11852fb6785be8fc72c41928574/home/.config/awesome/ui/calendar.lua 3 | local require, math, os = require, math, os 4 | 5 | local awful = require("awful") 6 | local wibox = require("wibox") 7 | local beautiful = require("beautiful") 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local color = require(beautiful.colorscheme) 11 | local icons = require('theme.icons') 12 | 13 | local calendar = {} 14 | local instance = nil 15 | 16 | local hebr_format = { 17 | [1] = 7, 18 | [2] = 1, 19 | [3] = 2, 20 | [4] = 3, 21 | [5] = 4, 22 | [6] = 5, 23 | [7] = 6 24 | } 25 | 26 | local wday_map = { 27 | ['Mon'] = 'Mo', 28 | ['Tue'] = 'Tu', 29 | ['Wed'] = 'We', 30 | ['Thu'] = 'Th', 31 | ['Fri'] = 'Fr', 32 | ['Sat'] = 'Sa', 33 | ['Sun'] = 'Su' 34 | } 35 | 36 | local function create_wday_widget(wday, col) 37 | local fg_color = col or color.fg0 38 | return wibox.widget { 39 | widget = wibox.container.background, 40 | fg = fg_color, 41 | { 42 | widget = wibox.container.margin, 43 | margins = dpi(10), 44 | { 45 | widget = wibox.widget.textbox, 46 | align = "center", 47 | text = wday 48 | } 49 | } 50 | } 51 | end 52 | 53 | local function create_day_widget(day, is_current, is_another_month) 54 | local fg_color = color.fg0 55 | local bg_color = color.bg1 56 | 57 | if is_current then 58 | fg_color = color.bg0 59 | bg_color = color.accent 60 | elseif is_another_month then 61 | fg_color = color.fg2 62 | bg_color = color.bg1 .. '80' -- TODO 63 | end 64 | 65 | return wibox.widget { 66 | widget = wibox.container.background, 67 | fg = fg_color, 68 | bg = bg_color, 69 | { 70 | widget = wibox.container.margin, 71 | margins = dpi(10), 72 | { 73 | widget = wibox.widget.textbox, 74 | halign = "center", 75 | valign = "center", 76 | text = day 77 | } 78 | } 79 | } 80 | end 81 | 82 | local function button(icon, action) 83 | local widget = wibox.widget({ 84 | widget = wibox.container.background, 85 | bg = color.bg1, 86 | buttons = { 87 | awful.button({}, 1, action) 88 | }, 89 | { 90 | widget = wibox.container.margin, 91 | margins = { 92 | top = dpi(6), bottom = dpi(4), 93 | left = dpi(8), right = dpi(8) 94 | }, 95 | { 96 | widget = wibox.widget.textbox, 97 | font = icons.font .. icons.size, 98 | text = icons[icon], 99 | halign = 'center', 100 | valign = 'center' 101 | } 102 | } 103 | }) 104 | widget:connect_signal('mouse::enter', function(self) 105 | self.bg = color.bg2 .. '80' 106 | self.fg = color.accent 107 | end) 108 | widget:connect_signal('mouse::leave', function(self) 109 | self.bg = color.bg1 110 | self.fg = color.fg0 111 | end) 112 | 113 | return widget 114 | end 115 | 116 | function calendar:set(date) 117 | calendar.day_layout:reset() 118 | self.date = date 119 | 120 | local curr_date = os.date("*t") 121 | local firstday = os.date("*t", os.time({ year = date.year, month = date.month, day = 1 })) 122 | local lastday = os.date("*t", os.time({ year = date.year, month = date.month + 1, day = 0 })) 123 | 124 | local month_count = lastday.day 125 | local month_start = not self.sun_start and hebr_format[firstday.wday] or firstday.wday 126 | local rows = math.max(5, math.min(6, 5 - (36 - (month_start + month_count)))) 127 | 128 | local month_prev_lastday = os.date("*t", os.time({ year = date.year, month = date.month, day = 0 })).day 129 | local month_prev_count = month_start - 1 130 | local month_next_count = rows*7 - lastday.day - month_prev_count 131 | 132 | self.top_widget.title = os.date("%B %Y", os.time(date)) 133 | 134 | for day = month_prev_lastday - (month_prev_count - 1), month_prev_lastday, 1 do 135 | self.day_layout:add(create_day_widget(day, false, true)) 136 | end 137 | 138 | for day = 1, month_count, 1 do 139 | local is_current = day == curr_date.day and date.month == curr_date.month and date.year == curr_date.year 140 | self.day_layout:add(create_day_widget(day, is_current, false)) 141 | end 142 | 143 | for day = 1, month_next_count, 1 do 144 | self.day_layout:add(create_day_widget(day, false, true)) 145 | end 146 | end 147 | 148 | function calendar:inc(dir) 149 | local new_calendar_month = self.date.month + dir 150 | self:set({ year = self.date.year, month = new_calendar_month, day = self.date.day }) 151 | end 152 | 153 | local function new() 154 | local ret = calendar 155 | 156 | 157 | ret.sun_start = false 158 | 159 | ret.day_layout = wibox.widget { 160 | layout = wibox.layout.grid, 161 | forced_num_cols = 7, 162 | expand = true, 163 | forced_height = dpi(230) 164 | } 165 | 166 | ret.wday_layout = wibox.widget { 167 | layout = wibox.layout.flex.horizontal 168 | } 169 | 170 | for i = 1, 7 do 171 | if ret.sun_start then 172 | i = i - 1 173 | if i > 0 and i < 6 then 174 | ret.wday_layout:add(create_wday_widget(wday_map[os.date("%a", os.time({year = 1, month = 1, day = i}))])) 175 | else 176 | ret.wday_layout:add(create_wday_widget(wday_map[os.date("%a", os.time({year = 1, month = 1, day = i}))], color.red)) 177 | end 178 | else 179 | if i < 6 then 180 | ret.wday_layout:add(create_wday_widget(wday_map[os.date("%a", os.time({year = 1, month = 1, day = i}))])) 181 | else 182 | ret.wday_layout:add(create_wday_widget(wday_map[os.date("%a", os.time({year = 1, month = 1, day = i}))], color.red)) 183 | end 184 | end 185 | end 186 | 187 | local title_text = wibox.widget({ 188 | widget = wibox.widget.textbox, 189 | halign = "center", 190 | valign = "center" 191 | }) 192 | local title = wibox.widget({ 193 | widget = wibox.container.background, 194 | buttons = { 195 | awful.button({}, 1, function() 196 | ret:set(os.date("*t")) 197 | end) 198 | }, 199 | title_text 200 | }) 201 | title:connect_signal('mouse::enter', function(self) 202 | self.fg = color.accent 203 | end) 204 | title:connect_signal('mouse::leave', function(self) 205 | self.fg = color.fg0 206 | end) 207 | 208 | ret.top_widget = wibox.widget { 209 | widget = wibox.container.background, 210 | bg = color.bg1, 211 | { 212 | layout = wibox.layout.align.horizontal, 213 | { 214 | widget = wibox.container.margin, 215 | margins = { 216 | top = dpi(8), bottom = dpi(6), 217 | left = dpi(10), right = dpi(10) 218 | }, 219 | title 220 | }, 221 | nil, 222 | { 223 | widget = wibox.layout.fixed.horizontal, 224 | { 225 | widget = wibox.container.background, 226 | bg = color.bg3, 227 | forced_width = dpi(1) 228 | }, 229 | button('arrow_left', function() ret:inc(-1) end), 230 | { 231 | widget = wibox.container.background, 232 | bg = color.bg3, 233 | forced_width = dpi(1) 234 | }, 235 | button('arrow_right', function() ret:inc(1) end) 236 | } 237 | }, 238 | set_title = function(_, text) 239 | title_text.text = text 240 | end 241 | } 242 | 243 | ret.main_widget = wibox.widget { 244 | widget = wibox.container.background, 245 | bg = color.bg0, 246 | border_width = dpi(1), 247 | border_color = color.bg3, 248 | { 249 | layout = wibox.layout.align.vertical, 250 | { 251 | layout = wibox.layout.fixed.vertical, 252 | ret.top_widget, 253 | { 254 | widget = wibox.container.background, 255 | bg = color.bg3, 256 | forced_height = dpi(1) 257 | } 258 | }, 259 | nil, 260 | { 261 | widget = wibox.container.margin, 262 | margins = { 263 | top = dpi(8), bottom = dpi(16), 264 | left = dpi(16), right = dpi(16) 265 | }, 266 | { 267 | layout = wibox.layout.fixed.vertical, 268 | spacing = dpi(5), 269 | ret.wday_layout, 270 | ret.day_layout 271 | } 272 | } 273 | } 274 | } 275 | 276 | ret:set(os.date("*t")) 277 | 278 | return ret 279 | end 280 | 281 | if not instance then 282 | instance = new() 283 | end 284 | 285 | return instance 286 | -------------------------------------------------------------------------------- /ui/time/module/clock.lua: -------------------------------------------------------------------------------- 1 | local require, os = require, os 2 | 3 | local beautiful = require('beautiful') 4 | local wibox = require('wibox') 5 | 6 | local helpers = require('helpers') 7 | local widget = require('widget') 8 | local color = require(beautiful.colorscheme) 9 | 10 | return function() 11 | local hour = wibox.widget({ 12 | widget = wibox.widget.textclock, 13 | format = '%H:%M:%S', 14 | refresh = 1, 15 | font = beautiful.font_bitm .. beautiful.bitm_size * 2, 16 | halign = 'center' 17 | }) 18 | 19 | local date = widget.textbox.colored({ 20 | color = color.fg2, 21 | align = 'center' 22 | }) 23 | require('gears').timer({ 24 | timeout = 60, 25 | call_now = true, 26 | autostart = true, 27 | callback = function() 28 | local day = tonumber(os.date('%e')) 29 | date.text = 30 | os.date('%A, the ') .. day .. helpers.get_suffix(day) .. os.date(' of %B') 31 | end 32 | }) 33 | 34 | return wibox.widget({ 35 | layout = wibox.layout.fixed.vertical, 36 | hour, date 37 | }) 38 | end 39 | -------------------------------------------------------------------------------- /ui/time/module/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | local path = ... .. '.' 3 | return setmetatable({}, { 4 | __index = function(_, key) 5 | local module, _ = require(path .. key) 6 | return module 7 | end 8 | }) 9 | -------------------------------------------------------------------------------- /ui/time/module/weather.lua: -------------------------------------------------------------------------------- 1 | local require, string, os, table, ipairs = require, string, os, table, ipairs 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local imperial = require('config.user').imperial 10 | local weather = require('signal.system.weather') 11 | local widget = require('widget') 12 | local color = require(beautiful.colorscheme) 13 | local icons = require('theme.icons') 14 | 15 | return function() 16 | -- Current weather widgets! 17 | local _C = {} 18 | _C.icon = widget.textbox.colored({ 19 | text = icons.weather['day_clear'], 20 | font = icons.font .. icons.size * 12, 21 | color = color.bg3 .. '80' 22 | }) 23 | _C.desc = widget.textbox.colored({ 24 | text = 'No weather info', 25 | font = beautiful.font_bitm .. beautiful.bitm_size * 2 26 | }) 27 | _C.humy = widget.textbox.colored({ 28 | text = 'Humidity: N/A', 29 | color = color.fg2 30 | }) 31 | _C.temp = widget.textbox.colored({ 32 | text = 'N/A', 33 | align = 'right', 34 | font = beautiful.font_bitm .. beautiful.bitm_size * 2 35 | }) 36 | _C.feel = widget.textbox.colored({ 37 | text = 'N/A', 38 | align = 'right', 39 | color = color.fg2 40 | }) 41 | 42 | -- Hourly weather widgets! 43 | local _H = {} 44 | local hour_widgets = wibox.widget({ 45 | layout = wibox.layout.flex.horizontal, 46 | spacing = dpi(8) 47 | }) 48 | 49 | local function hourly(index) 50 | local time = widget.textbox.colored({ 51 | text = '+' .. string.format('%02d', index) .. ':00', 52 | font = beautiful.font_mono .. beautiful.bitm_size, 53 | align = 'center', 54 | color = color.fg1 55 | }) 56 | local icon = widget.textbox.colored({ 57 | text = icons.weather['day_clear'], 58 | font = icons.font .. icons.size * 2, 59 | align = 'center' 60 | }) 61 | local temp = widget.textbox.colored({ 62 | text = 'N/A', 63 | font = beautiful.font_mono .. beautiful.bitm_size, 64 | align = 'center' 65 | }) 66 | local rain = widget.textbox.colored({ 67 | text = 'N/A', 68 | font = beautiful.font_mono .. beautiful.bitm_size, 69 | align = 'center', 70 | color = color.fg2 71 | }) 72 | 73 | return wibox.widget({ 74 | layout = wibox.layout.fixed.vertical, 75 | time, icon, temp, rain, 76 | set_time = function(_, t) 77 | time.text = t 78 | end, 79 | set_icon = function(_, i) 80 | icon.text = i 81 | end, 82 | set_temp = function(_, t) 83 | temp.text = t 84 | end, 85 | set_rain = function(_, h) 86 | rain.text = h 87 | end 88 | }) 89 | end 90 | 91 | for i = 1, 6, 1 do 92 | local w = hourly(i) 93 | table.insert(_H, w) 94 | hour_widgets:add(w) 95 | end 96 | 97 | -- Daily weather widgets! 98 | local _D = {} 99 | local day_widgets = wibox.widget({ 100 | layout = wibox.layout.flex.horizontal, 101 | visible = false, 102 | spacing = dpi(16) 103 | }) 104 | local weekdays = { 105 | 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' 106 | } 107 | 108 | local function daily(index) 109 | local time = widget.textbox.colored({ 110 | text = '+' .. index, 111 | font = beautiful.font_mono .. beautiful.bitm_size, 112 | color = color.fg1 113 | }) 114 | local icon = widget.textbox.colored({ 115 | text = icons.weather['day_clear'], 116 | font = icons.font .. icons.size * 4 117 | }) 118 | local max = widget.textbox.colored({ 119 | text = 'N/A', 120 | font = beautiful.font_mono .. beautiful.bitm_size 121 | }) 122 | local rain = widget.textbox.colored({ 123 | text = 'N/A', 124 | font = beautiful.font_mono .. beautiful.bitm_size 125 | }) 126 | local humy = widget.textbox.colored({ 127 | text = 'N/A', 128 | font = beautiful.font_mono .. beautiful.bitm_size, 129 | color = color.fg2 130 | }) 131 | local min = widget.textbox.colored({ 132 | text = 'N/A', 133 | font = beautiful.font_mono .. beautiful.bitm_size, 134 | color = color.fg2 135 | }) 136 | 137 | return wibox.widget({ 138 | widget = wibox.container.margin, 139 | margins = { top = dpi(1), bottom = dpi(2) }, 140 | { 141 | layout = wibox.layout.fixed.horizontal, 142 | spacing = dpi(9), 143 | icon, 144 | { 145 | layout = wibox.layout.fixed.vertical, 146 | spacing = dpi(2), 147 | time, 148 | { 149 | layout = wibox.layout.fixed.vertical, 150 | { 151 | layout = wibox.layout.fixed.horizontal, 152 | widget.textbox.colored({ 153 | text = 'T: ', 154 | color = color.red 155 | }), max, wibox.widget.textbox('/'), min, 156 | wibox.widget.textbox(imperial and '°F' or '°C') 157 | }, 158 | { 159 | layout = wibox.layout.fixed.horizontal, 160 | widget.textbox.colored({ 161 | text = 'H: ', 162 | color = color.yellow 163 | }), humy 164 | }, 165 | { 166 | layout = wibox.layout.fixed.horizontal, 167 | widget.textbox.colored({ 168 | text = 'R: ', 169 | color = color.blue 170 | }), rain 171 | } 172 | } 173 | } 174 | }, 175 | set_time = function(_, d) 176 | d = d + index + 1 177 | if d > 7 then 178 | d = d - 7 179 | end 180 | time.text = weekdays[d] 181 | end, 182 | set_icon = function(_, i) 183 | icon.text = i 184 | end, 185 | set_max = function(_, t) 186 | max.text = t:gsub('°[C,F]', '') 187 | end, 188 | set_min = function(_, t) 189 | min.text = t:gsub('°[C,F]', '') 190 | end, 191 | set_rain = function(_, r) 192 | rain.text = r 193 | end, 194 | set_humy = function(_, r) 195 | humy.text = r 196 | end 197 | }) 198 | end 199 | 200 | for i = 1, 2, 1 do 201 | local w = daily(i) 202 | table.insert(_D, w) 203 | day_widgets:add(w) 204 | end 205 | 206 | -- Switch between hourly and daily forecast. 207 | local hour_toggle = wibox.widget({ 208 | widget = wibox.container.background, 209 | bg = color.bg2, 210 | { 211 | widget = wibox.container.margin, 212 | margins = { 213 | top = dpi(4), bottom = dpi(4), 214 | left = dpi(8), right = dpi(8) 215 | }, 216 | { 217 | widget = widget.textbox.colored({ 218 | text = 'By Hour', 219 | align = 'center' 220 | }), 221 | id = 'text' 222 | } 223 | }, 224 | set_col = function(self, col) 225 | self:get_children_by_id('text')[1].color = col 226 | end 227 | }) 228 | local day_toggle = wibox.widget({ 229 | widget = wibox.container.background, 230 | { 231 | widget = wibox.container.margin, 232 | margins = { 233 | top = dpi(4), bottom = dpi(4), 234 | left = dpi(8), right = dpi(8) 235 | }, 236 | { 237 | widget = widget.textbox.colored({ 238 | text = 'By Day', 239 | align = 'center', 240 | color = color.fg2 241 | }), 242 | id = 'text' 243 | } 244 | }, 245 | set_col = function(self, col) 246 | self:get_children_by_id('text')[1].color = col 247 | end 248 | }) 249 | local toggle = wibox.widget({ 250 | widget = wibox.container.background, 251 | bg = color.bg1, 252 | border_width = dpi(1), 253 | border_color = color.bg3, 254 | { 255 | layout = wibox.layout.fixed.horizontal, 256 | { 257 | widget = hour_toggle, 258 | buttons = { 259 | awful.button(nil, 1, function() 260 | hour_widgets.visible = true 261 | day_widgets.visible = false 262 | hour_toggle.bg = color.bg2 263 | hour_toggle.col = color.fg0 264 | day_toggle.bg = color.bg2 .. '80' 265 | day_toggle.col = color.fg2 266 | end) 267 | } 268 | }, 269 | { 270 | widget = wibox.container.background, 271 | bg = color.bg3, 272 | forced_width = dpi(1) 273 | }, 274 | { 275 | widget = day_toggle, 276 | buttons = { 277 | awful.button(nil, 1, function() 278 | day_widgets.visible = true 279 | hour_widgets.visible = false 280 | day_toggle.bg = color.bg2 281 | day_toggle.col = color.fg0 282 | hour_toggle.bg = color.bg2 .. '80' 283 | hour_toggle.col = color.fg2 284 | end) 285 | } 286 | } 287 | } 288 | }) 289 | 290 | local w = wibox.widget({ 291 | widget = wibox.container.background, 292 | bg = color.bg1, 293 | border_width = dpi(1), 294 | border_color = color.bg3, 295 | visible = false, 296 | { 297 | widget = wibox.container.margin, 298 | margins = dpi(16), 299 | { 300 | widget = wibox.layout.stack, 301 | _C.icon, 302 | { 303 | layout = wibox.layout.fixed.vertical, 304 | spacing = dpi(12), 305 | { 306 | layout = wibox.layout.align.horizontal, 307 | { 308 | layout = wibox.layout.fixed.vertical, 309 | _C.desc, 310 | _C.humy 311 | }, 312 | nil, 313 | { 314 | layout = wibox.layout.fixed.vertical, 315 | _C.temp, 316 | _C.feel 317 | } 318 | }, 319 | { 320 | layout = wibox.layout.align.horizontal, 321 | expand = 'none', 322 | nil, nil, toggle 323 | }, 324 | { 325 | layout = wibox.layout.align.horizontal, 326 | expand = 'none', 327 | nil, 328 | { 329 | layout = wibox.layout.stack, 330 | hour_widgets, 331 | day_widgets 332 | }, 333 | nil 334 | } 335 | } 336 | } 337 | } 338 | }) 339 | 340 | -- Global signals won't cut it, they get emitted before this widget is even drawn. 341 | weather:connect_signal('weather::data', function(_, info) 342 | w.visible = true 343 | 344 | -- Current. 345 | _C.desc.text = info.description 346 | _C.humy.text = 'Humidity: ' .. info.humidity 347 | _C.temp.text = info.temperature 348 | _C.feel.text = info.feels_like 349 | _C.icon.text = icons.weather[info.icon] 350 | 351 | -- Hourly. 352 | -- Widget can only hold 6 at a time, make sure they're relevant! 353 | local off = 0 354 | if #info.by_hour > #_H then 355 | local curr_h = tonumber(os.date('%H')) 356 | for i = 1, #info.by_hour - #_H, 1 do 357 | if curr_h > (i * 24 / #info.by_hour) then 358 | off = off + 1 359 | end 360 | end 361 | end 362 | for i, h in ipairs(_H) do 363 | local hour = info.by_hour[i + off] 364 | h.time = hour.time 365 | h.icon = icons.weather[hour.icon] 366 | h.temp = hour.temperature 367 | h.rain = hour.rain_chance 368 | end 369 | 370 | -- Daily. 371 | for i, d in ipairs(_D) do 372 | d.time = tonumber(os.date('%w')) 373 | d.icon = icons.weather[info.by_day[i].icon] 374 | d.max = info.by_day[i].max_temp 375 | d.min = info.by_day[i].min_temp 376 | d.rain = info.by_day[i].rain_chance 377 | d.humy = info.by_day[i].humidity 378 | end 379 | end) 380 | 381 | -- Since the panel isn't drawn from the get-go, it may fail to catch the first emision 382 | -- of the `weather::data` signal. I opted to make the widget request a new emision 383 | -- when drawn for the first time. 384 | weather:request_data() 385 | 386 | return w 387 | end 388 | -------------------------------------------------------------------------------- /ui/titlebar/init.lua: -------------------------------------------------------------------------------- 1 | -- Returns titlebars for normal clients, this structure allows one to 2 | -- easily define special titlebars for particular clients. 3 | return { 4 | normal = require(... .. '.normal') 5 | } 6 | -------------------------------------------------------------------------------- /ui/titlebar/normal.lua: -------------------------------------------------------------------------------- 1 | local require, client = require, client 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | local wibox = require('wibox') 7 | 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local tabbed = require('module.bling').widget.tabbed_misc 11 | local widget = require('widget') 12 | local color = require(beautiful.colorscheme) 13 | local icons = require('theme.icons') 14 | 15 | --- The titlebar to be used on normal clients. 16 | return function(c) 17 | local function button(icon, hover, action) 18 | local w = wibox.widget({ 19 | widget = wibox.container.background, 20 | bg = color.bg2 .. '90', 21 | border_width = dpi(1), 22 | border_color = color.bg3, 23 | { 24 | widget = wibox.container.margin, 25 | margins = { left = dpi(5), right = dpi(5) }, 26 | { 27 | widget = widget.textbox.colored({ 28 | text = icon, 29 | font = icons.font .. icons.size, 30 | align = 'center' 31 | }), 32 | id = 'image_role' 33 | } 34 | }, 35 | buttons = { awful.button(nil, 1, action) }, 36 | set_fg_col = function(self, fg) 37 | self:get_children_by_id('image_role')[1].color = fg 38 | end, 39 | set_bd_col = function(self, bd) 40 | self.border_color = bd 41 | end, 42 | set_bg_col = function(self, bg) 43 | self.bg = bg 44 | end 45 | }) 46 | 47 | -- Changes the icons for a lighter version when the client is focused. Reverts on 48 | -- focus loss. 49 | client.connect_signal('property::active', function() 50 | if c.active then 51 | w.opacity = 1 52 | else 53 | w.opacity = 0.66 54 | end 55 | end) 56 | 57 | -- Adjust colors when hovering. 58 | w:connect_signal('mouse::enter', function(self) 59 | self.fg_col = hover 60 | self.bd_col = hover 61 | self.bg_col = color.bg2 62 | end) 63 | w:connect_signal('mouse::leave', function(self) 64 | self.fg_col = color.fg0 65 | self.bd_col = color.bg3 66 | self.bg_col = color.bg2 .. '90' 67 | end) 68 | 69 | return w 70 | end 71 | 72 | local tabs = tabbed.titlebar_indicator(c, { 73 | bg_color = color.bg1 .. '80', 74 | bg_color_focus = color.transparent, 75 | fg_color = color.fg0 .. 'AB', 76 | fg_color_focus = color.accent, 77 | layout = wibox.layout.flex.horizontal, 78 | layout_spacing = 0, 79 | widget_template = { 80 | widget = wibox.container.background, 81 | id = 'bg_role', 82 | { 83 | widget = wibox.container.margin, 84 | margins = { 85 | left = dpi(11), right = dpi(11), 86 | top = dpi(11), bottom = dpi(11) 87 | }, 88 | { 89 | widget = wibox.widget.textbox, 90 | valign = 'center', 91 | id = 'text_role' 92 | } 93 | }, 94 | create_callback = function(self, window, _) 95 | self.text = window.name 96 | end, 97 | update_callback = function(self, window, group) 98 | self.create_callback(self, window, group) 99 | end 100 | } 101 | }) 102 | 103 | local top = wibox.widget({ 104 | widget = wibox.container.background, 105 | bg = color.bg1, 106 | border_width = dpi(1), 107 | border_color = color.bg3, 108 | { 109 | layout = wibox.layout.align.horizontal, 110 | expand = 'outer', 111 | -- Left 112 | { 113 | layout = wibox.layout.fixed.horizontal, 114 | { 115 | widget = wibox.container.margin, 116 | margins = dpi(6), 117 | button(icons['title_pin'], color.accent, 118 | function() 119 | c.sticky = not c.sticky 120 | end 121 | ) 122 | }, 123 | { 124 | widget = wibox.container.background, 125 | bg = color.bg3, 126 | forced_width = dpi(1) 127 | } 128 | }, 129 | -- Middle 130 | { 131 | widget = wibox.container.background, 132 | bg = color.bg0 .. '80', 133 | tabs 134 | }, 135 | -- Right 136 | { 137 | layout = wibox.layout.fixed.horizontal, 138 | { 139 | widget = wibox.container.background, 140 | bg = color.bg3, 141 | forced_width = dpi(1) 142 | }, 143 | { 144 | widget = wibox.container.margin, 145 | margins = dpi(7), 146 | { 147 | layout = wibox.layout.fixed.horizontal, 148 | spacing = dpi(2), 149 | button(icons['title_minimize'], color.accent, 150 | function() 151 | gears.timer.delayed_call(function() 152 | c.minimized = not c.minimized 153 | end) 154 | end 155 | ), 156 | button(icons['title_maximize'], color.accent, 157 | function() 158 | c.maximized = not c.maximized 159 | c:raise() 160 | end 161 | ), 162 | button(icons['title_close'], color.red, function() c:kill() end) 163 | } 164 | } 165 | } 166 | } 167 | }) 168 | 169 | local bottom = wibox.widget({ 170 | widget = wibox.container.background, 171 | bg = color.bg1, 172 | border_width = dpi(1), 173 | border_color = color.bg3, 174 | { 175 | layout = wibox.layout.fixed.horizontal, 176 | { 177 | widget = wibox.container.background, 178 | bg = color.transparent, 179 | forced_width = dpi(48), 180 | buttons = { 181 | awful.button(nil, 1, function() 182 | c:activate({ context = 'titlebar', action = 'mouse_move' }) 183 | end), 184 | awful.button(nil, 3, function() 185 | c:activate({ context = 'titlebar', action = 'mouse_resize' }) 186 | end) 187 | } 188 | }, 189 | { 190 | widget = wibox.container.background, 191 | bg = color.bg3, 192 | forced_width = dpi(1) 193 | } 194 | } 195 | }) 196 | 197 | local empty = wibox.widget({ 198 | widget = wibox.container.background, 199 | bg = color.bg3 200 | }) 201 | 202 | awful.titlebar(c, { position = 'top', size = dpi(33) }).widget = top 203 | awful.titlebar(c, { position = 'bottom', size = dpi(7) }).widget = bottom 204 | awful.titlebar(c, { position = 'left', size = dpi(1) }).widget = empty 205 | awful.titlebar(c, { position = 'right', size = dpi(1) }).widget = empty 206 | end 207 | -------------------------------------------------------------------------------- /ui/wibar/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local color = require(beautiful.colorscheme) 10 | local module = require(... .. '.module') 11 | 12 | return function(s) 13 | -- Left widgets. 14 | local left = wibox.widget({ 15 | layout = wibox.layout.fixed.horizontal, 16 | { 17 | widget = wibox.container.margin, 18 | margins = { 19 | top = dpi(6), bottom = dpi(6), 20 | right = dpi(16) 21 | }, 22 | { 23 | layout = wibox.layout.fixed.horizontal, 24 | spacing = dpi(12), 25 | { 26 | layout = wibox.layout.fixed.horizontal, 27 | module.layoutbox(s), 28 | module.taglist(s) 29 | }, 30 | module.launcher(s) 31 | } 32 | }, 33 | { 34 | widget = wibox.container.background, 35 | bg = color.bg3, 36 | forced_width = dpi(1) 37 | } 38 | }) 39 | 40 | -- Middle widgets. 41 | local center = wibox.widget({ 42 | widget = wibox.container.background, 43 | bg = color.bg1 .. '80', 44 | { 45 | widget = wibox.container.margin, 46 | margins = { 47 | left = dpi(9), right = dpi(9), 48 | top = dpi(6), bottom = dpi(6) 49 | }, 50 | module.tasklist(s) 51 | } 52 | }) 53 | 54 | -- Right widgets. 55 | local right = wibox.widget({ 56 | layout = wibox.layout.fixed.horizontal, 57 | { 58 | widget = wibox.container.background, 59 | bg = color.bg3, 60 | forced_width = dpi(1) 61 | }, 62 | { 63 | widget = wibox.container.margin, 64 | margins = { left = dpi(12) }, 65 | { 66 | layout = wibox.layout.fixed.horizontal, 67 | spacing = dpi(12), 68 | { 69 | widget = wibox.container.margin, 70 | margins = { 71 | top = dpi(6), bottom = dpi(6) 72 | }, 73 | module.systray() 74 | }, 75 | awful.widget.keyboardlayout(), 76 | { 77 | widget = wibox.container.margin, 78 | margins = { 79 | top = dpi(6), bottom = dpi(6) 80 | }, 81 | { 82 | layout = wibox.layout.fixed.horizontal, 83 | spacing = dpi(12), 84 | module.status(), 85 | module.clock(s), 86 | module.dash(s) 87 | } 88 | } 89 | } 90 | } 91 | }) 92 | 93 | -- Create the wibox 94 | return awful.wibar({ 95 | position = 'top', 96 | height = dpi(36), 97 | screen = s, 98 | widget = { 99 | widget = wibox.container.background, 100 | bg = color.bg3, 101 | { 102 | widget = wibox.container.margin, 103 | margins = { bottom = dpi(1) }, 104 | { 105 | widget = wibox.container.background, 106 | bg = color.bg0, 107 | { 108 | widget = wibox.container.margin, 109 | margins = { left = dpi(16), right = dpi(16) }, 110 | { 111 | layout = wibox.layout.align.horizontal, 112 | expand = 'outer', 113 | left, 114 | center, 115 | right 116 | } 117 | } 118 | } 119 | } 120 | } 121 | }) 122 | end 123 | -------------------------------------------------------------------------------- /ui/wibar/module/clock.lua: -------------------------------------------------------------------------------- 1 | local require, os = require, os 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local widget = require('widget') 10 | local helpers = require('helpers') 11 | local color = require(beautiful.colorscheme) 12 | local icons = require('theme.icons') 13 | local weather = require('signal.system.weather') 14 | 15 | return function(s) 16 | -- A simple widget that shows the correct suffix for the current date. 17 | local day_suffix = wibox.widget({ widget = wibox.widget.textbox }) 18 | require('gears').timer({ 19 | timeout = 60, 20 | call_now = true, 21 | autostart = true, 22 | callback = function() 23 | local day = tonumber(os.date('%d')) 24 | day_suffix.markup = os.date('%B ') .. day .. helpers.get_suffix(day) 25 | end 26 | }) 27 | 28 | local clock = wibox.widget({ 29 | widget = wibox.container.background, 30 | fg = color.fg0, 31 | { 32 | layout = wibox.layout.fixed.horizontal, 33 | spacing = dpi(12), 34 | day_suffix, 35 | { 36 | widget = wibox.widget.textclock, 37 | format = '%H:%M' 38 | } 39 | }, 40 | buttons = { 41 | awful.button(nil, 1, function() 42 | if s.time then s.time:toggle() end 43 | end) 44 | } 45 | }) 46 | clock:connect_signal('mouse::enter', function(self) 47 | self.fg = color.accent 48 | end) 49 | clock:connect_signal('mouse::leave', function(self) 50 | self.fg = color.fg0 51 | end) 52 | 53 | -- Some compact weather information. 54 | local current_weather = wibox.widget({ 55 | layout = wibox.layout.fixed.horizontal, 56 | spacing = dpi(9), 57 | visible = false, 58 | { 59 | widget = wibox.container.margin, 60 | margins = { 61 | top = dpi(5), bottom = dpi(5) 62 | }, 63 | { 64 | widget = wibox.container.background, 65 | bg = color.bg2, 66 | forced_width = dpi(1) 67 | } 68 | }, 69 | { 70 | layout = wibox.layout.fixed.horizontal, 71 | spacing = dpi(6), 72 | { 73 | widget = widget.textbox.colored({ 74 | text = icons.weather['net_none'], 75 | font = icons.font .. icons.size 76 | }), 77 | id = 'icon' 78 | }, 79 | { 80 | widget = widget.textbox.colored({ 81 | text = 'N/A' 82 | }), 83 | id = 'temp' 84 | } 85 | }, 86 | buttons = { 87 | awful.button(nil, 1, function() 88 | if s.time then s.time:toggle() end 89 | end) 90 | }, 91 | set_col = function(self, col) 92 | self:get_children_by_id('icon')[1].color = col 93 | self:get_children_by_id('temp')[1].color = col 94 | end, 95 | set_icon = function(self, icon) 96 | self:get_children_by_id('icon')[1].text = icon 97 | end, 98 | set_temp = function(self, temp) 99 | self:get_children_by_id('temp')[1].text = temp 100 | end 101 | }) 102 | current_weather:connect_signal('mouse::enter', function(self) 103 | self.col = color.accent 104 | end) 105 | current_weather:connect_signal('mouse::leave', function(self) 106 | self.col = color.fg0 107 | end) 108 | 109 | weather:connect_signal('weather::data', function(_, data) 110 | current_weather.visible = true 111 | current_weather.icon = icons.weather[data.icon] 112 | current_weather.temp = data.temperature 113 | end) 114 | 115 | weather:request_data() 116 | 117 | return wibox.widget({ 118 | layout = wibox.layout.fixed.horizontal, 119 | spacing = dpi(9), 120 | clock, 121 | current_weather 122 | }) 123 | end 124 | -------------------------------------------------------------------------------- /ui/wibar/module/dash.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | local wibox = require('wibox') 7 | 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local color = require(beautiful.colorscheme) 11 | local widget = require('widget') 12 | local icons = require('theme.icons') 13 | local user = require('config.user') 14 | 15 | -- Create a launcher widget. Opens the Awesome menu when clicked. 16 | return function(s) 17 | local arrow = widget.textbox.colored({ 18 | text = icons['arrow_down'], 19 | font = icons.font .. icons.size, 20 | align = 'center' 21 | }) 22 | 23 | local idle 24 | if not user.lite or user.lite == nil then 25 | idle = wibox.widget({ 26 | widget = wibox.widget.imagebox, 27 | image = beautiful.pfp, 28 | vertical_fit_policy = 'fit', 29 | horizontal_fit_policy = 'fit' 30 | }) 31 | else 32 | idle = wibox.widget({ 33 | widget = wibox.container.margin, 34 | margins = { 35 | left = dpi(7), right = dpi(7) 36 | }, 37 | widget.textbox.colored({ 38 | text = icons['util_hamburger'], 39 | font = icons.font .. icons.size 40 | }) 41 | }) 42 | end 43 | 44 | local w = wibox.widget({ 45 | widget = wibox.container.background, 46 | bg = color.bg1, 47 | shape = function(cr, w, h) 48 | gears.shape.circle(cr, w, h) 49 | end, 50 | forced_height = dpi(23), 51 | forced_width = dpi(23), 52 | { 53 | layout = wibox.layout.stack, 54 | idle, 55 | { 56 | widget = wibox.container.background, 57 | visible = false, 58 | bg = color.bg2 .. 'C0', 59 | id = 'hover_over', 60 | arrow 61 | } 62 | }, 63 | buttons = { 64 | awful.button(nil, 1, function() 65 | s.dash:toggle() 66 | arrow.text = s.dash.visible and icons['arrow_up'] or icons['arrow_down'] 67 | end) 68 | }, 69 | set_hover = function(self, bool) 70 | self:get_children_by_id('hover_over')[1].visible = bool 71 | end 72 | }) 73 | w:connect_signal('mouse::enter', function(self) 74 | self.hover = true 75 | arrow.direction = s.dash.visible and 'north' or 'south' 76 | end) 77 | w:connect_signal('mouse::leave', function(self) 78 | self.hover = false 79 | end) 80 | 81 | return w 82 | end 83 | -------------------------------------------------------------------------------- /ui/wibar/module/init.lua: -------------------------------------------------------------------------------- 1 | -- Return a table containing all bar modules, with a name attached 2 | -- to each. 3 | local require = require 4 | local path = ... .. '.' 5 | return setmetatable({}, { 6 | __index = function(_, key) 7 | local module, _ = require(path .. key) 8 | return module 9 | end 10 | }) 11 | -------------------------------------------------------------------------------- /ui/wibar/module/launcher.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local widget = require('widget') 10 | local color = require(beautiful.colorscheme) 11 | local icons = require('theme.icons') 12 | 13 | return function(s) 14 | local w = wibox.widget({ 15 | layout = wibox.layout.fixed.horizontal, 16 | spacing = dpi(6), 17 | { 18 | widget = widget.textbox.colored({ 19 | text = icons['util_magnifier'], 20 | font = icons.font .. icons.size 21 | }), 22 | id = 'icon_role' 23 | }, 24 | { 25 | widget = widget.textbox.colored({ text = 'Search' }), 26 | id = 'text_role' 27 | }, 28 | buttons = { 29 | awful.button(nil, 1, function() s.launcher:open() end) 30 | }, 31 | set_fg = function(self, col) 32 | self:get_children_by_id('text_role')[1].color = col 33 | self:get_children_by_id('icon_role')[1].color = col 34 | end 35 | }) 36 | w:connect_signal('mouse::enter', function(self) 37 | self.fg = color.accent 38 | end) 39 | w:connect_signal('mouse::leave', function(self) 40 | self.fg = color.fg0 41 | end) 42 | 43 | return w 44 | end 45 | -------------------------------------------------------------------------------- /ui/wibar/module/layoutbox.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local widget = require('widget') 10 | local color = require(beautiful.colorscheme) 11 | 12 | return function(s) 13 | -- Create a textbox widget which will contain an icon indicating which layout we're using. 14 | -- We need one layoutbox per screen. 15 | -- NOTE: the layoutbox widget used here is custom and can be found at `widget.layoutbox`. 16 | local layout = wibox.widget({ 17 | widget = wibox.container.background, 18 | bg = color.bg1, 19 | { 20 | layout = wibox.layout.fixed.horizontal, 21 | { 22 | widget = wibox.container.background, 23 | bg = color.bg3, 24 | forced_width = dpi(1) 25 | }, 26 | { 27 | layout = wibox.layout.align.vertical, 28 | { 29 | widget = wibox.container.background, 30 | bg = color.bg3, 31 | forced_height = dpi(1) 32 | }, 33 | { 34 | widget = wibox.container.margin, 35 | margins = dpi(6), 36 | { 37 | widget = wibox.container.constraint, 38 | strategy = 'exact', 39 | height = dpi(9), 40 | widget.layoutbox({ screen = s }) 41 | } 42 | }, 43 | { 44 | widget = wibox.container.background, 45 | bg = color.bg3, 46 | forced_height = dpi(1) 47 | } 48 | } 49 | }, 50 | buttons = { 51 | awful.button(nil, 1, function() awful.layout.inc( 1) end), 52 | awful.button(nil, 3, function() awful.layout.inc(-1) end), 53 | awful.button(nil, 4, function() awful.layout.inc(-1) end), 54 | awful.button(nil, 5, function() awful.layout.inc( 1) end) 55 | } 56 | }) 57 | layout:connect_signal('mouse::enter', function(self) 58 | self.bg = color.bg2 59 | end) 60 | layout:connect_signal('mouse::leave', function(self) 61 | self.bg = color.bg1 62 | end) 63 | 64 | return layout 65 | end 66 | -------------------------------------------------------------------------------- /ui/wibar/module/status.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local beautiful = require('beautiful') 4 | local wibox = require('wibox') 5 | 6 | local dpi = beautiful.xresources.apply_dpi 7 | 8 | local color = require(beautiful.colorscheme) 9 | local icons = require('theme.icons') 10 | local widget = require('widget') 11 | local audio = require('signal.system.audio') 12 | local battery = require('signal.system.battery') 13 | 14 | local audio_widget = widget.textbox.colored({ 15 | text = icons['audio_muted'], 16 | font = icons.font .. icons.size, 17 | color = color.red 18 | }) 19 | audio:connect_signal('sinks::default', function(_, default_sink) 20 | if default_sink.mute or default_sink.volume == 0 then 21 | audio_widget.text = icons['audio_muted'] 22 | audio_widget.color = color.red 23 | elseif default_sink.volume < 50 then 24 | audio_widget.text = icons['audio_decrease'] 25 | audio_widget.color = color.fg0 26 | else 27 | audio_widget.text = icons['audio_increase'] 28 | audio_widget.color = color.fg0 29 | end 30 | end) 31 | 32 | -- Only assigned if a valid battery is found. 33 | local battery_icon = widget.textbox.colored({ 34 | text = icons.battery['UNKNOWN'], 35 | font = icons.font .. icons.size 36 | }) 37 | local battery_level = widget.textbox.colored({ 38 | text = 'N/A', 39 | }) 40 | local battery_widget = wibox.widget({ 41 | widget = wibox.container.margin, 42 | margins = { left = dpi(12) }, 43 | visible = false, 44 | { 45 | layout = wibox.layout.fixed.horizontal, 46 | spacing = dpi(6), 47 | battery_icon, 48 | battery_level 49 | } 50 | }) 51 | battery:connect_signal('update', function(_, percent, state, _, _, _) 52 | battery_widget.visible = true 53 | 54 | battery_level.text = percent .. '%' 55 | if state == 'CHARGING' or state == 'FULLY_CHARGED' then 56 | battery_icon.text = icons.battery[state] 57 | elseif percent >= 95 then 58 | battery_icon.text = icons.battery['FULL'] 59 | elseif percent >= 70 then 60 | battery_icon.text = icons.battery['HIGH'] 61 | elseif percent >= 40 then 62 | battery_icon.text = icons.battery['NORMAL'] 63 | elseif percent >= 20 then 64 | battery_icon.text = icons.battery['LOW'] 65 | else 66 | battery_icon.text = icons.battery['CRITICAL'] 67 | end 68 | end) 69 | 70 | return function() 71 | return wibox.widget({ 72 | widget = wibox.container.background, 73 | bg = color.bg1, 74 | border_width = dpi(1), 75 | border_color = color.bg3, 76 | { 77 | widget = wibox.container.margin, 78 | margins = { 79 | top = dpi(6), bottom = dpi(6), 80 | left = dpi(12), right = dpi(12) 81 | }, 82 | { 83 | layout = wibox.layout.fixed.horizontal, 84 | audio_widget, 85 | battery_widget 86 | } 87 | } 88 | }) 89 | end 90 | -------------------------------------------------------------------------------- /ui/wibar/module/systray.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local wibox = require('wibox') 6 | 7 | local dpi = beautiful.xresources.apply_dpi 8 | 9 | local widget = require('widget') 10 | local color = require(beautiful.colorscheme) 11 | local icons = require('theme.icons') 12 | 13 | return function() 14 | -- The systray itself. 15 | local systray = wibox.widget({ 16 | layout = wibox.layout.fixed.horizontal, 17 | visible = false, 18 | { 19 | widget = wibox.container.background, 20 | bg = color.bg3, 21 | forced_width = dpi(1) 22 | }, 23 | { 24 | layout = wibox.layout.fixed.vertical, 25 | { 26 | widget = wibox.container.background, 27 | bg = color.bg3, 28 | forced_height = dpi(1) 29 | }, 30 | { 31 | widget = wibox.container.background, 32 | bg = color.bg1, 33 | { 34 | widget = wibox.container.margin, 35 | margins = dpi(3), 36 | { 37 | widget = wibox.container.constraint, 38 | strategy = 'exact', 39 | height = dpi(15), 40 | wibox.widget.systray() 41 | } 42 | } 43 | }, 44 | { 45 | widget = wibox.container.background, 46 | bg = color.bg3, 47 | forced_height = dpi(1) 48 | } 49 | } 50 | }) 51 | 52 | -- The arrow image. 53 | local switch = widget.textbox.colored({ 54 | text = icons['arrow_left'], 55 | font = icons.font .. icons.size, 56 | align = 'center' 57 | }) 58 | local switchbox = wibox.widget({ 59 | widget = wibox.container.background, 60 | bg = color.bg1, 61 | border_width = dpi(1), 62 | border_color = color.bg3, 63 | { 64 | widget = wibox.container.margin, 65 | margins = { 66 | top = dpi(7), bottom = dpi(7), 67 | left = dpi(6), right = dpi(6) 68 | }, 69 | switch 70 | }, 71 | buttons = { 72 | awful.button(nil, 1, function() 73 | if systray.visible then 74 | systray.visible = false 75 | switch.text = icons['arrow_left'] 76 | else 77 | systray.visible = true 78 | switch.text = icons['arrow_right'] 79 | end 80 | end) 81 | } 82 | }) 83 | switchbox:connect_signal('mouse::enter', function(self) 84 | self.bg = color.bg2 85 | end) 86 | switchbox:connect_signal('mouse::leave', function(self) 87 | self.bg = color.bg1 88 | end) 89 | 90 | -- When hovered, lights up the switch and when clicked, switches states and changes 91 | -- the icon's direction. 92 | return wibox.widget({ 93 | layout = wibox.layout.fixed.horizontal, 94 | systray, 95 | switchbox 96 | }) 97 | end 98 | -------------------------------------------------------------------------------- /ui/wibar/module/taglist.lua: -------------------------------------------------------------------------------- 1 | local require, client, awesome = require, client, awesome 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | local wibox = require('wibox') 7 | 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local bling = require('module.bling') 11 | local color = require(beautiful.colorscheme) 12 | local user = require('config.user') 13 | local mod = require('binds.mod') 14 | local modkey = mod.modkey 15 | 16 | local preview = user.lite == nil or not user.lite 17 | 18 | return function(s) 19 | if preview then 20 | -- Enable and customize the tag preview widget. 21 | bling.widget.tag_preview.enable({ 22 | show_client_content = true, 23 | scale = 0.125, 24 | honor_padding = true, 25 | honor_workarea = true, 26 | placement_fn = function(c) 27 | awful.placement.next_to(c, { 28 | margins = { top = beautiful.useless_gap, left = dpi(40) }, 29 | preferred_positions = 'bottom', 30 | preferred_anchors = 'front', 31 | geometry = s.bar 32 | }) 33 | end 34 | }) 35 | end 36 | 37 | -- Create the taglist. 38 | local tags = awful.widget.taglist({ 39 | screen = s, 40 | filter = awful.widget.taglist.filter.all, 41 | buttons = { 42 | -- Left-clicking a tag changes to it. 43 | awful.button(nil, 1, function(t) t:view_only() end), 44 | -- Mod + Left-clicking a tag sends the currently focused client to it. 45 | awful.button({ modkey }, 1, function(t) 46 | if client.focus then 47 | client.focus:move_to_tag(t) 48 | end 49 | end), 50 | -- Right-clicking a tag makes its contents visible in the current one. 51 | awful.button(nil, 3, awful.tag.viewtoggle), 52 | -- Mod + Right-clicking a tag makes the currently focused client visible in it. 53 | awful.button({ modkey }, 3, function(t) 54 | if client.focus then 55 | client.focus:toggle_tag(t) 56 | end 57 | end), 58 | -- Mousewheel scrolling cycles through tags. 59 | awful.button(nil, 4, function(t) awful.tag.viewprev(t.screen) end), 60 | awful.button(nil, 5, function(t) awful.tag.viewnext(t.screen) end) 61 | }, 62 | layout = { 63 | layout = wibox.layout.fixed.horizontal, 64 | spacing = dpi(8) 65 | }, 66 | style = { 67 | bg_focus = color.accent, 68 | bg_occupied = color.fg2, 69 | bg_empty = color.bg4 .. 'ac', 70 | bg_urgent = color.red 71 | }, 72 | 73 | -- The fun stuff. 74 | widget_template = { 75 | widget = wibox.container.margin, 76 | -- The purpose of this margin widget is purely to fatten the hitbox of the tag 77 | -- lines, as to make them more mouse friendly. 78 | margins = { 79 | top = dpi(11), bottom = dpi(11) 80 | }, 81 | { 82 | widget = wibox.container.background, 83 | id = 'background_role', 84 | -- Create the tag icon as an empty textbox. 85 | wibox.widget.textbox() 86 | }, 87 | -- Create a callback to change its size with an animation depending 88 | -- on focus and occupation. 89 | create_callback = function(self, tag) 90 | local bar = self:get_children_by_id('background_role')[1] 91 | self.update = function() 92 | if tag.selected then 93 | -- If the tag is focused: 94 | bar.forced_width = dpi(48) 95 | elseif #tag:clients() > 0 then 96 | -- If the tag is occupied: 97 | bar.forced_width = dpi(32) 98 | else 99 | -- If the tag is unoccupied and unfocused: 100 | bar.forced_width = dpi(16) 101 | end 102 | end 103 | -- Generate the bar sizes once. 104 | self.update() 105 | 106 | if preview then 107 | -- Show a preview of the tag if it's hovered for a second. 108 | local visible, hovered = false, false 109 | local timer = gears.timer({ 110 | timeout = 1, 111 | single_shot = true, 112 | callback = function() 113 | if not client.focus or not client.focus.fullscreen then 114 | if not visible and hovered then 115 | if #tag:clients() > 0 then 116 | visible = true 117 | awesome.emit_signal('bling::tag_preview::update', tag) 118 | awesome.emit_signal("bling::tag_preview::visibility", s, true) 119 | end 120 | end 121 | end 122 | end 123 | }) 124 | self:connect_signal('mouse::enter', function() 125 | hovered = true 126 | timer:start() 127 | end) 128 | self:connect_signal('mouse::leave', function() 129 | hovered = false 130 | if visible then 131 | visible = false 132 | timer:stop() 133 | awesome.emit_signal("bling::tag_preview::visibility", s, false) 134 | end 135 | end) 136 | end 137 | end, 138 | -- Then update on callback. 139 | update_callback = function(self) 140 | self.update() 141 | end 142 | } 143 | }) 144 | 145 | return wibox.widget({ 146 | widget = wibox.container.background, 147 | bg = color.bg1, 148 | border_width = dpi(1), 149 | border_color = color.bg3, 150 | { 151 | widget = wibox.container.margin, 152 | margins = { 153 | left = dpi(11), right = dpi(11) 154 | }, 155 | tags 156 | } 157 | }) 158 | end 159 | -------------------------------------------------------------------------------- /ui/wibar/module/tasklist.lua: -------------------------------------------------------------------------------- 1 | local require, awesome, client = require, awesome, client 2 | 3 | local awful = require('awful') 4 | local beautiful = require('beautiful') 5 | local gears = require('gears') 6 | local wibox = require('wibox') 7 | 8 | local dpi = beautiful.xresources.apply_dpi 9 | 10 | local bling = require('module.bling') 11 | local color = require(beautiful.colorscheme) 12 | local user = require('config.user') 13 | 14 | local preview = user.lite == nil or not user.lite 15 | 16 | return function(s) 17 | if preview then 18 | -- Enable and customize the task preview widget. 19 | bling.widget.task_preview.enable({ 20 | placement_fn = function(c) 21 | awful.placement.next_to(c, { 22 | margins = { top = beautiful.useless_gap }, 23 | preferred_positions = 'bottom', 24 | preferred_anchors = 'middle', 25 | geometry = s.bar 26 | }) 27 | end, 28 | structure = { 29 | widget = wibox.container.background, 30 | bg = color.bg1, 31 | border_width = dpi(1), 32 | border_color = color.bg3, 33 | { 34 | widget = wibox.container.margin, 35 | margins = { 36 | top = dpi(6), bottom = dpi(8), 37 | left = dpi(8), right = dpi(8) 38 | }, 39 | { 40 | layout = wibox.layout.fixed.vertical, 41 | spacing = dpi(4), 42 | { 43 | widget = wibox.widget.textbox, 44 | id = 'name_role' 45 | }, 46 | { 47 | widget = wibox.widget.imagebox, 48 | resize = true, 49 | valign = 'center', 50 | halign = 'center', 51 | id = 'image_role' 52 | } 53 | } 54 | } 55 | } 56 | }) 57 | end 58 | 59 | -- Create a tasklist widget. 60 | return awful.widget.tasklist({ 61 | screen = s, 62 | filter = awful.widget.tasklist.filter.currenttags, 63 | buttons = { 64 | -- Left-clicking a client indicator minimizes it if it's unminimized, or 65 | -- unminimizes it if it's minimized. 66 | awful.button(nil, 1, function(c) 67 | c:activate({ context = 'tasklist', action = 'toggle_minimization' }) 68 | end), 69 | -- Right-clicking a client indicator shows the list of all open clients in all 70 | -- visible tags. 71 | awful.button(nil, 3, function() awful.menu.client_list({ 72 | theme = { width = 250 } }) 73 | end), 74 | -- Mousewheel scrolling cycles through clients. 75 | awful.button(nil, 4, function() awful.client.focus.byidx(-1) end), 76 | awful.button(nil, 5, function() awful.client.focus.byidx( 1) end) 77 | }, 78 | layout = { 79 | layout = wibox.layout.flex.horizontal, 80 | spacing = dpi(6) 81 | }, 82 | style = { 83 | -- Colors. 84 | bg_minimize = color.bg1, 85 | fg_minimize = color.bg4, 86 | bg_normal = color.bg1, 87 | fg_normal = color.fg2, 88 | bg_focus = color.bg2 .. '80', 89 | fg_focus = color.accent, 90 | bg_urgent = color.bg1, 91 | fg_urgent = color.red, 92 | shape_border_width = dpi(1), 93 | shape_border_color = color.bg3, 94 | shape_border_color_focus = color.bg4 .. 'BF', 95 | shape_border_color_minimized = color.bg2, 96 | shape_border_color_urgent = color.red, 97 | -- Styling. 98 | font = beautiful.font, 99 | disable_icon = true, 100 | maximized = '[+]', 101 | minimized = '[-]', 102 | sticky = '[*]', 103 | floating = '[~]', 104 | ontop = '[^]', 105 | above = '[!]' 106 | }, 107 | widget_template = { 108 | widget = wibox.container.background, 109 | id = 'background_role', 110 | { 111 | widget = wibox.container.margin, 112 | margins = { 113 | left = dpi(13), right = dpi(13) 114 | }, 115 | { 116 | widget = wibox.widget.textbox, 117 | valign = 'center', 118 | id = 'text_role' 119 | } 120 | }, 121 | create_callback = function(self, task) 122 | if preview then 123 | -- Show a preview of the task if it's hovered for a second. 124 | local visible, hovered = false, false 125 | local timer = gears.timer({ 126 | timeout = 1, 127 | single_shot = true, 128 | callback = function() 129 | if not client.focus or not client.focus.fullscreen then 130 | if not visible and hovered then 131 | visible = true 132 | awesome.emit_signal("bling::task_preview::visibility", s, true, task) 133 | end 134 | end 135 | end 136 | }) 137 | self:connect_signal('mouse::enter', function() 138 | hovered = true 139 | timer:start() 140 | end) 141 | self:connect_signal('mouse::leave', function() 142 | hovered = false 143 | if visible then 144 | visible = false 145 | timer:stop() 146 | awesome.emit_signal("bling::task_preview::visibility", s, false, task) 147 | end 148 | end) 149 | end 150 | end 151 | } 152 | }) 153 | end 154 | -------------------------------------------------------------------------------- /widget/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | return { 4 | layoutbox = require(... .. '.layoutbox'), 5 | textbox = require(... .. '.textbox') 6 | } 7 | -------------------------------------------------------------------------------- /widget/layoutbox.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------- 2 | --- Display the current client layout (`awful.layout`) icon or name. 3 | -- 4 | -- @DOC_awful_widget_layoutbox_default_EXAMPLE@ 5 | -- 6 | -- @author Julien Danjou <julien@danjou.info> 7 | -- @copyright 2009 Julien Danjou 8 | -- @widgetmod awful.widget.layoutbox 9 | -- @supermodule wibox.layout.fixed 10 | --------------------------------------------------------------------------- 11 | --- Modified to use my icon font instead of the usual images. 12 | 13 | local require, pairs, assert, type, setmetatable 14 | = require, pairs, assert, type, setmetatable 15 | local capi = { screen = screen, tag = tag } 16 | local layout = require("awful.layout") 17 | local tooltip = require("awful.tooltip") 18 | local beautiful = require("beautiful") 19 | local wibox = require("wibox") 20 | local gdebug = require("gears.debug") 21 | local gtable = require("gears.table") 22 | 23 | local icon = require('theme.icons') 24 | 25 | local function get_screen(s) 26 | return s and capi.screen[s] 27 | end 28 | 29 | local layoutbox = { mt = {} } 30 | 31 | local boxes = nil 32 | 33 | local function update(w, screen) 34 | screen = get_screen(screen) 35 | local name = layout.getname(layout.get(screen)) 36 | w._layoutbox_tooltip:set_text(name or "[no name]") 37 | if icon.layout[name] then 38 | w.text = icon.layout[name] 39 | w.font = icon.font .. icon.size 40 | else 41 | w.text = name 42 | w.font = beautiful.font 43 | end 44 | end 45 | 46 | local function update_from_tag(t) 47 | local screen = get_screen(t.screen) 48 | local w = boxes[screen] 49 | if w then 50 | update(w, screen) 51 | end 52 | end 53 | 54 | --- Create a layoutbox widget. It draws a picture with the current layout 55 | -- symbol of the current tag. 56 | -- @constructorfct awful.widget.layoutbox 57 | -- @tparam table args The arguments. 58 | -- @tparam screen args.screen The screen number that the layout will be represented for. 59 | -- @tparam table args.buttons The `awful.button`s for this layoutbox. 60 | -- @return The layoutbox. 61 | function layoutbox.new(args) 62 | args = args or {} 63 | local screen = args.screen 64 | 65 | if type(args) == "number" or type(args) == "screen" or args.fake_remove then 66 | screen, args = args, {} 67 | 68 | gdebug.deprecate( 69 | "Use awful.widget.layoutbox{screen=s} instead of awful.widget.layoutbox(screen)", 70 | {deprecated_in=5} 71 | ) 72 | end 73 | 74 | assert(type(args) == "table") 75 | 76 | screen = get_screen(screen or 1) 77 | 78 | -- Do we already have the update callbacks registered? 79 | if boxes == nil then 80 | boxes = setmetatable({}, { __mode = "kv" }) 81 | capi.tag.connect_signal("property::selected", update_from_tag) 82 | capi.tag.connect_signal("property::layout", update_from_tag) 83 | capi.tag.connect_signal("property::screen", function() 84 | for s, w in pairs(boxes) do 85 | if s.valid then update(w, s) end 86 | end 87 | end) 88 | layoutbox.boxes = boxes 89 | end 90 | 91 | -- Do we already have a layoutbox for this screen? 92 | local w = boxes[screen] 93 | if not w then 94 | w = wibox.widget.textbox() 95 | w._layoutbox_tooltip = tooltip {objects = {w}, delay_show = 1} 96 | 97 | -- Apply the buttons, visible, forced_width and so on 98 | gtable.crush(w, args) 99 | 100 | update(w, screen) 101 | boxes[screen] = w 102 | end 103 | 104 | return w 105 | end 106 | 107 | function layoutbox.mt:__call(...) 108 | return layoutbox.new(...) 109 | end 110 | 111 | return setmetatable(layoutbox, layoutbox.mt) 112 | -------------------------------------------------------------------------------- /widget/textbox/colored.lua: -------------------------------------------------------------------------------- 1 | --- A colorable textbox widget. 2 | 3 | local require, type, setmetatable = require, type, setmetatable 4 | 5 | local beautiful = require('beautiful') 6 | local gears = require('gears') 7 | local wibox = require('wibox') 8 | 9 | local textbox = { mt = {} } 10 | 11 | --- Create a colorable textbox widget. Like a regular `wibox.widget.textbox`, but also 12 | --- capable of coloring its text. 13 | -- @constructorfct widget.textbox.colored 14 | -- @tparam string The text to be displayed. 15 | -- @tparam table args The arguments. 16 | -- @tparam string args.text The text to be displayed. 17 | -- @tparam string args.font The font to be used. 18 | -- @tparam string args.color The color the text will be by default. 19 | -- @tparam string args.align The horizontal text alignment (left, center, right). 20 | -- @return The colored textbox. 21 | function textbox.new(args) 22 | -- Make sure the args have the correct type. 23 | local args_type = type(args) 24 | if args_type == 'string' or args_type == 'number' then 25 | local text = args 26 | args = {} 27 | args.text = text 28 | end 29 | assert(type(args) == 'table') 30 | 31 | -- Normalize args. 32 | local conf = gears.table.crush({ 33 | text = '', 34 | font = beautiful.font, 35 | color = beautiful.fg_normal, 36 | align = 'left' 37 | }, args, true) 38 | 39 | local w = wibox.widget({ 40 | widget = wibox.container.background, 41 | fg = conf.color, 42 | { 43 | widget = wibox.widget.textbox, 44 | markup = conf.text, 45 | font = conf.font, 46 | halign = conf.align, 47 | id = 'text_role' 48 | }, 49 | set_text = function(self, new_text) 50 | self:get_children_by_id('text_role')[1].markup = new_text 51 | end, 52 | set_color = function(self, new_color) 53 | self.fg = new_color 54 | end 55 | }) 56 | 57 | return w 58 | end 59 | 60 | function textbox.mt:__call(...) 61 | return textbox.new(...) 62 | end 63 | 64 | return setmetatable(textbox, textbox.mt) 65 | -------------------------------------------------------------------------------- /widget/textbox/init.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | 3 | return { 4 | colored = require(... .. '.colored'), 5 | scrolling = require(... .. '.scrolling') 6 | } 7 | -------------------------------------------------------------------------------- /widget/textbox/scrolling.lua: -------------------------------------------------------------------------------- 1 | --- A scrolling, colorable textbox widget. 2 | 3 | local require, type, setmetatable = require, type, setmetatable 4 | 5 | local beautiful = require('beautiful') 6 | local gears = require('gears') 7 | local wibox = require('wibox') 8 | 9 | local textbox = { mt = {} } 10 | 11 | --- Create a scrolling, colorable textbox widget. Like a regular `wibox.widget.textbox`, 12 | --- but also capable of coloring its text, and scrolling in either axis. 13 | -- @constructorfct widget.textbox.scrolling 14 | -- @tparam string The text to be displayed. 15 | -- @tparam table args The arguments. 16 | -- @tparam string args.text The text to be displayed. 17 | -- @tparam string args.font The font to be used. 18 | -- @tparam string args.color The color the text will be by default. 19 | -- @tparam string args.align The horizontal text alignment (left, center, right). 20 | -- @tparam string args.dir The axis on which to scroll (horizontal, vertical). 21 | -- @return The scrollable, colored textbox. 22 | function textbox.new(args) 23 | -- Make sure the args have the correct type. 24 | local args_type = type(args) 25 | if args_type == 'string' or args_type == 'number' then 26 | local text = args 27 | args = {} 28 | args.text = text 29 | end 30 | assert(type(args) == 'table') 31 | 32 | -- Normalize args. 33 | local conf = gears.table.crush({ 34 | text = '', 35 | font = beautiful.font, 36 | color = beautiful.fg_normal, 37 | align = 'left', 38 | dir = 'horizontal' 39 | }, args, true) 40 | 41 | local scroll 42 | if conf.dir == 'horizontal' then 43 | scroll = wibox.container.scroll.horizontal 44 | else 45 | scroll = wibox.container.scroll.vertical 46 | end 47 | 48 | local w = wibox.widget({ 49 | widget = scroll, 50 | step_function = 51 | wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 52 | speed = 50, 53 | { 54 | widget = require('widget.textbox.colored')({ 55 | text = conf.text, 56 | font = conf.font, 57 | color = conf.color, 58 | align = conf.align 59 | }), 60 | id = 'text_role' 61 | }, 62 | set_text = function(self, new_text) 63 | self:get_children_by_id('text_role')[1].text = new_text 64 | end, 65 | set_color = function(self, new_color) 66 | self:get_children_by_id('text_role')[1].fg = new_color 67 | end 68 | }) 69 | 70 | return w 71 | end 72 | 73 | function textbox.mt:__call(...) 74 | return textbox.new(...) 75 | end 76 | 77 | return setmetatable(textbox, textbox.mt) 78 | 79 | --------------------------------------------------------------------------------