├── LICENSE ├── autostart.lua ├── bar ├── init.lua └── modules │ ├── battery.lua │ ├── brightness.lua │ ├── lain_battery.lua │ ├── lain_mpd.lua │ ├── mpd.lua │ ├── systray.lua │ ├── taglist.lua │ ├── time.lua │ └── volume.lua ├── external └── fancy_taglist.lua ├── helper.lua ├── keys.lua ├── notifications.lua ├── rc.lua ├── rules.lua ├── theme ├── gtk.lua └── init.lua └── wallpaper.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 sachnr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /autostart.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful.init") 2 | 3 | -- reload processes on config reload 4 | awful.spawn.easy_async_with_shell( 5 | 'ps x | grep "blueman-applet" | grep -v grep | awk "{print $1}" | xargs kill', 6 | function() 7 | awful.spawn("blueman-applet") 8 | end 9 | ) 10 | 11 | awful.spawn.easy_async_with_shell('ps x | grep "nm-applet" | grep -v grep | awk "{print $1}" | xargs kill', function() 12 | awful.spawn("nm-applet --indicator") 13 | end) 14 | -- 15 | awful.spawn.easy_async_with_shell('ps x | grep "pasystray" | grep -v grep | awk "{print $1}" | xargs kill', function() 16 | awful.spawn("pasystray") 17 | end) 18 | 19 | -- awful.spawn("xrandr --output HDMI-0 --primary --mode 1280x960 --rate 144") 20 | -- awful.spawn("xrandr --output HDMI-0 --primary --mode 1920x1080 --rate 144") 21 | -- awful.spawn("xrandr --output DP-0 --primary --mode 1920x1080 --rate 144") 22 | -- 23 | awful.spawn("nvidia-settings --load-config-only") 24 | awful.spawn("autorandr --load desktop") 25 | 26 | awful.spawn.easy_async_with_shell( 27 | 'ps x | grep "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1" | grep -v grep | awk "{print $1}" | xargs kill', 28 | function() 29 | awful.spawn("/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1") 30 | end 31 | ) 32 | 33 | awful.spawn.easy_async_with_shell('ps x | grep "picom" | grep -v grep | awk "{print $1}" | xargs kill', function() 34 | awful.spawn("picom") 35 | end) 36 | 37 | awful.spawn("xset r rate 560 50") 38 | -------------------------------------------------------------------------------- /bar/init.lua: -------------------------------------------------------------------------------- 1 | local wibox = require("wibox") 2 | local beautiful = require("beautiful") 3 | local systray = require("bar.modules.systray") 4 | local dpi = beautiful.xresources.apply_dpi 5 | local awful = require("awful") 6 | local naughty = require("naughty") 7 | 8 | -- Table of layouts to cover with awful.layout.inc, order matters. 9 | ---@diagnostic disable-next-line: undefined-global 10 | tag.connect_signal("request::default_layouts", function() 11 | awful.layout.append_default_layouts({ 12 | awful.layout.suit.tile, 13 | awful.layout.suit.floating, 14 | }) 15 | end) 16 | 17 | local tags = {} 18 | local bar = {} 19 | 20 | bar.setup = function(args) 21 | setmetatable(args, { __index = { style = "horizontal" } }) 22 | 23 | local fancy_taglist = require("bar.modules.taglist") 24 | local time = require("bar.modules.time") 25 | local volume = require("bar.modules.volume") 26 | local mpd_text = require("bar.modules.mpd") 27 | local brightness = require("bar.modules.brightness") 28 | local battery = require("bar.modules.battery") 29 | 30 | require("bar.modules.lain_mpd")() 31 | require("bar.modules.lain_battery")({ 32 | battery = "BAT0", 33 | notify = true, 34 | }) 35 | 36 | ---@diagnostic disable-next-line: undefined-global 37 | awful.screen.connect_for_each_screen(function(s) 38 | tags[s] = awful.tag({ "1", "2", "3", "4", "5", "6", "7", "8", "9" }, s, awful.layout.layouts[1]) 39 | 40 | s.mywibox = awful.wibar({ 41 | position = "bottom", 42 | screen = s, 43 | height = dpi(28), 44 | widget = { 45 | layout = wibox.layout.align.horizontal, 46 | expand = "none", 47 | { -- Left widgets 48 | fancy_taglist.setup(s), 49 | layout = wibox.layout.fixed.horizontal, 50 | }, 51 | { -- Middle widget 52 | mpd_text.setup(), 53 | layout = wibox.layout.flex.horizontal, 54 | }, 55 | { -- Right widgets 56 | (_G.laptop and battery.setup({}) or nil), 57 | (_G.laptop and brightness.setup({}) or nil), 58 | volume.setup(), 59 | time.setup(), 60 | systray.setup(), 61 | layout = wibox.layout.fixed.horizontal, 62 | }, 63 | }, 64 | }) 65 | end) 66 | end 67 | 68 | return bar 69 | -------------------------------------------------------------------------------- /bar/modules/battery.lua: -------------------------------------------------------------------------------- 1 | local wibox = require("wibox.init") 2 | local beautiful = require("beautiful") 3 | local dpi = beautiful.xresources.apply_dpi 4 | local helper = require("helper") 5 | 6 | local M = {} 7 | 8 | local timeout = 2 9 | 10 | M.icons = { 11 | crit = "", 12 | low = "", 13 | med = "", 14 | high = "", 15 | full = "", 16 | } 17 | 18 | local function battery_update() 19 | local icon = M.icons.full 20 | if bat_now.perc <= 20 then 21 | icon = M.icons.crit 22 | elseif bat_now.perc <= 40 then 23 | icon = M.icons.low 24 | elseif bat_now.perc <= 60 then 25 | icon = M.icons.med 26 | elseif bat_now.perc <= 80 then 27 | icon = M.icons.high 28 | elseif bat_now.perc <= 100 then 29 | icon = M.icons.full 30 | end 31 | 32 | local color = beautiful.fg_normal 33 | if bat_now.status == "Charging" then 34 | color = beautiful.bg_success 35 | elseif bat_now.status == "Discharging" then 36 | color = beautiful.bg_warning 37 | elseif bat_now.status == "Full" then 38 | color = beautiful.fg_normal 39 | end 40 | 41 | local list = { 42 | bat_now.perc, 43 | "%", 44 | " ", 45 | } 46 | local text = table.concat(list, " ") 47 | 48 | M.widget.markup = string.format( 49 | " " .. icon .. " %s ", 50 | beautiful.font, 51 | beautiful.font_size, 52 | color, 53 | text 54 | ) 55 | end 56 | 57 | M.widget = wibox.widget({ 58 | markup = string.format( 59 | "", 60 | beautiful.font, 61 | beautiful.font_size, 62 | beautiful.fg_normal 63 | ), 64 | align = "center", 65 | valign = "center", 66 | widget = wibox.widget.textbox, 67 | }) 68 | 69 | M.setup = function() 70 | helper.newtimer("Battery Status Text", timeout, battery_update, true, true) 71 | 72 | return helper.create_rounded_widget(M.widget, dpi(12)) 73 | end 74 | 75 | return M 76 | -------------------------------------------------------------------------------- /bar/modules/brightness.lua: -------------------------------------------------------------------------------- 1 | local wibox = require("wibox.init") 2 | local beautiful = require("beautiful") 3 | local dpi = beautiful.xresources.apply_dpi 4 | local helper = require("helper") 5 | local awful = require("awful") 6 | 7 | local M = {} 8 | 9 | local step_amount = 5 10 | local brightness_icon = "󰃟" 11 | 12 | M.widget = wibox.widget({ 13 | markup = brightness_icon, 14 | font = beautiful.icon_font .. " Bold 10", 15 | align = "center", 16 | valign = "center", 17 | widget = wibox.widget.textbox, 18 | }) 19 | 20 | local function update_widget(_, stdout) 21 | M.widget.marup = string.format( 22 | "" .. brightness_icon .. " " .. stdout .. "", 23 | beautiful.icon_font, 24 | beautiful.accent 25 | ) 26 | end 27 | 28 | awful.widget.watch("sh -c 'brightnessctl -m | cut -d, -f4 | tr -d %'", 5, update_widget, M.widget) 29 | 30 | M.setup = function(opts) 31 | opts = opts or {} 32 | if opts.step ~= nil then 33 | M.step = opts.step 34 | end 35 | 36 | M.widget:buttons(awful.util.table.join( 37 | awful.button({}, 4, function() 38 | awful.spawn("brightnessctl set +" .. step_amount .. "%") 39 | end), 40 | awful.button({}, 5, function() 41 | awful.spawn("brightnessctl set " .. step_amount .. "-%") 42 | end) 43 | )) 44 | 45 | return helper.create_rounded_widget(M.widget, dpi(12)) 46 | end 47 | 48 | return M 49 | -------------------------------------------------------------------------------- /bar/modules/lain_battery.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Licensed under GNU General Public License v2 4 | * (c) 2013, Luca CPZ 5 | * (c) 2010-2012, Peter Hofmann 6 | 7 | --]] 8 | 9 | local helpers = require("helper") 10 | local fs = require("gears.filesystem") 11 | local naughty = require("naughty") 12 | local wibox = require("wibox") 13 | local beautiful = require("beautiful") 14 | local math = math 15 | local string = string 16 | local ipairs = ipairs 17 | local tonumber = tonumber 18 | 19 | -- Battery infos 20 | -- lain.widget.bat 21 | 22 | local function factory(args) 23 | local pspath = args.pspath or "/sys/class/power_supply/" 24 | 25 | if not fs.is_dir(pspath) then 26 | naughty.notify({ text = "lain.widget.bat: invalid power supply path", timeout = 0 }) 27 | return 28 | end 29 | 30 | args = args or {} 31 | 32 | local bat = { widget = args.widget or wibox.widget.textbox() } 33 | local timeout = args.timeout or 30 34 | local notify = args.notify or "on" 35 | local full_notify = args.full_notify or notify 36 | local n_perc = args.n_perc or { 5, 15 } 37 | local batteries = args.batteries or (args.battery and { args.battery }) or {} 38 | local ac = args.ac or "AC0" 39 | local settings = args.settings or function() end 40 | 41 | function bat.get_batteries() 42 | helpers.line_callback("ls -1 " .. pspath, function(line) 43 | local bstr = string.match(line, "BAT%w+") 44 | if bstr then 45 | batteries[#batteries + 1] = bstr 46 | else 47 | ac = string.match(line, "A%w+") or ac 48 | end 49 | end) 50 | end 51 | 52 | if #batteries == 0 then 53 | bat.get_batteries() 54 | end 55 | 56 | bat_notification_critical_preset = { 57 | title = "Battery exhausted", 58 | text = "Shutdown imminent", 59 | timeout = 15, 60 | fg = beautiful.fg_urgent, 61 | bg = beautiful.bg_urgent, 62 | } 63 | 64 | bat_notification_low_preset = { 65 | title = "Battery low", 66 | text = "Plug the cable!", 67 | timeout = 15, 68 | fg = beautiful.fg_warning, 69 | bg = beautiful.bg_warning, 70 | } 71 | 72 | bat_notification_charged_preset = { 73 | title = "Battery full", 74 | text = "You can unplug the cable", 75 | timeout = 15, 76 | fg = beautiful.fg_success, 77 | bg = beautiful.bg_success, 78 | } 79 | 80 | bat_now = { 81 | status = "N/A", 82 | ac_status = "N/A", 83 | perc = "N/A", 84 | time = "N/A", 85 | watt = "N/A", 86 | capacity = "N/A", 87 | } 88 | 89 | bat_now.n_status = {} 90 | bat_now.n_perc = {} 91 | bat_now.n_capacity = {} 92 | for i = 1, #batteries do 93 | bat_now.n_status[i] = "N/A" 94 | bat_now.n_perc[i] = 0 95 | bat_now.n_capacity[i] = 0 96 | end 97 | 98 | -- used to notify full charge only once before discharging 99 | local fullnotification = false 100 | 101 | function bat.update() 102 | -- luacheck: globals bat_now 103 | local sum_rate_current = 0 104 | local sum_rate_voltage = 0 105 | local sum_rate_power = 0 106 | local sum_rate_energy = 0 107 | local sum_energy_now = 0 108 | local sum_energy_full = 0 109 | local sum_charge_full = 0 110 | local sum_charge_design = 0 111 | 112 | for i, battery in ipairs(batteries) do 113 | local bstr = pspath .. battery 114 | local present = helpers.first_line(bstr .. "/present") 115 | 116 | if tonumber(present) == 1 then 117 | -- current_now(I)[uA], voltage_now(U)[uV], power_now(P)[uW] 118 | local rate_current = tonumber(helpers.first_line(bstr .. "/current_now")) 119 | local rate_voltage = tonumber(helpers.first_line(bstr .. "/voltage_now")) 120 | local rate_power = tonumber(helpers.first_line(bstr .. "/power_now")) 121 | local charge_full = tonumber(helpers.first_line(bstr .. "/charge_full")) 122 | local charge_design = tonumber(helpers.first_line(bstr .. "/charge_full_design")) 123 | 124 | -- energy_now(P)[uWh], charge_now(I)[uAh] 125 | local energy_now = 126 | tonumber(helpers.first_line(bstr .. "/energy_now") or helpers.first_line(bstr .. "/charge_now")) 127 | 128 | -- energy_full(P)[uWh], charge_full(I)[uAh] 129 | local energy_full = tonumber(helpers.first_line(bstr .. "/energy_full") or charge_full) 130 | 131 | local energy_percentage = tonumber(helpers.first_line(bstr .. "/capacity")) 132 | or math.floor((energy_now / energy_full) * 100) 133 | 134 | bat_now.n_status[i] = helpers.first_line(bstr .. "/status") or "N/A" 135 | bat_now.n_perc[i] = energy_percentage or bat_now.n_perc[i] 136 | 137 | if not charge_design or charge_design == 0 then 138 | bat_now.n_capacity[i] = 0 139 | else 140 | bat_now.n_capacity[i] = math.floor((charge_full / charge_design) * 100) 141 | end 142 | 143 | sum_rate_current = sum_rate_current + (rate_current or 0) 144 | sum_rate_voltage = sum_rate_voltage + (rate_voltage or 0) 145 | sum_rate_power = sum_rate_power + (rate_power or 0) 146 | sum_rate_energy = sum_rate_energy + (rate_power or (((rate_voltage or 0) * (rate_current or 0)) / 1e6)) 147 | sum_energy_now = sum_energy_now + (energy_now or 0) 148 | sum_energy_full = sum_energy_full + (energy_full or 0) 149 | sum_charge_full = sum_charge_full + (charge_full or 0) 150 | sum_charge_design = sum_charge_design + (charge_design or 0) 151 | end 152 | end 153 | 154 | bat_now.capacity = math.floor(math.min(100, (sum_charge_full / sum_charge_design) * 100)) 155 | 156 | -- When one of the battery is charging, others' status are either 157 | -- "Full", "Unknown" or "Charging". When the laptop is not plugged in, 158 | -- one or more of the batteries may be full, but only one battery 159 | -- discharging suffices to set global status to "Discharging". 160 | bat_now.status = bat_now.n_status[1] or "N/A" 161 | for _, status in ipairs(bat_now.n_status) do 162 | if status == "Discharging" or status == "Charging" then 163 | bat_now.status = status 164 | end 165 | end 166 | bat_now.ac_status = tonumber(helpers.first_line(string.format("%s%s/online", pspath, ac))) or "N/A" 167 | 168 | if bat_now.status ~= "N/A" then 169 | if bat_now.status ~= "Full" and sum_rate_power == 0 and bat_now.ac_status == 1 then 170 | bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100)) 171 | bat_now.time = "00:00" 172 | bat_now.watt = 0 173 | 174 | -- update {perc,time,watt} iff battery not full and rate > 0 175 | elseif bat_now.status ~= "Full" then 176 | local rate_time = 0 177 | -- Calculate time and watt if rates are greater then 0 178 | if sum_rate_power > 0 or sum_rate_current > 0 then 179 | local div = (sum_rate_power > 0 and sum_rate_power) or sum_rate_current 180 | 181 | if bat_now.status == "Charging" then 182 | rate_time = (sum_energy_full - sum_energy_now) / div 183 | else -- Discharging 184 | rate_time = sum_energy_now / div 185 | end 186 | 187 | if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199) 188 | rate_time_magnitude = math.abs(math.floor(math.log10(rate_time))) 189 | rate_time = rate_time * 10 ^ (rate_time_magnitude - 2) 190 | end 191 | end 192 | 193 | local hours = math.floor(rate_time) 194 | local minutes = math.floor((rate_time - hours) * 60) 195 | bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100)) 196 | bat_now.time = string.format("%02d:%02d", hours, minutes) 197 | bat_now.watt = tonumber(string.format("%.2f", sum_rate_energy / 1e6)) 198 | elseif bat_now.status == "Full" then 199 | bat_now.perc = 100 200 | bat_now.time = "00:00" 201 | bat_now.watt = 0 202 | end 203 | end 204 | 205 | widget = bat.widget 206 | settings() 207 | 208 | -- notifications for critical, low, and full levels 209 | if notify == "on" then 210 | if bat_now.status == "Discharging" then 211 | if tonumber(bat_now.perc) <= n_perc[1] then 212 | bat.id = naughty.notify({ 213 | preset = bat_notification_critical_preset, 214 | replaces_id = bat.id, 215 | }).id 216 | elseif tonumber(bat_now.perc) <= n_perc[2] then 217 | bat.id = naughty.notify({ 218 | preset = bat_notification_low_preset, 219 | replaces_id = bat.id, 220 | }).id 221 | end 222 | fullnotification = false 223 | elseif bat_now.status == "Full" and full_notify == "on" and not fullnotification then 224 | bat.id = naughty.notify({ 225 | preset = bat_notification_charged_preset, 226 | replaces_id = bat.id, 227 | }).id 228 | fullnotification = true 229 | end 230 | end 231 | end 232 | 233 | helpers.newtimer("batteries", timeout, bat.update) 234 | 235 | return bat 236 | end 237 | 238 | return factory 239 | -------------------------------------------------------------------------------- /bar/modules/lain_mpd.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Licensed under GNU General Public License v2 4 | * (c) 2013, Luca CPZ 5 | * (c) 2010, Adrian C. 6 | 7 | --]] 8 | 9 | local helpers = require("helper") 10 | local shell = require("awful.util").shell 11 | local escape_f = require("awful.util").escape 12 | local focused = require("awful.screen").focused 13 | local naughty = require("naughty") 14 | local wibox = require("wibox") 15 | local os = os 16 | local string = string 17 | 18 | -- MPD infos 19 | -- lain.widget.mpd 20 | 21 | local function factory(args) 22 | args = args or {} 23 | 24 | local mpd = { widget = args.widget or wibox.widget.textbox() } 25 | local timeout = args.timeout or 2 26 | local password = (args.password and #args.password > 0 and string.format("password %s\\n", args.password)) or "" 27 | local host = args.host or os.getenv("MPD_HOST") or "127.0.0.1" 28 | local port = args.port or os.getenv("MPD_PORT") or "6600" 29 | local music_dir = args.music_dir or os.getenv("HOME") .. "/Music" 30 | local cover_pattern = args.cover_pattern or "*\\.(jpg|jpeg|png|gif)$" 31 | local cover_size = args.cover_size or 100 32 | local default_art = args.default_art 33 | local notify = args.notify or "on" 34 | local followtag = args.followtag or false 35 | local settings = args.settings or function() end 36 | 37 | local mpdh = string.format("telnet://%s:%s", host, port) 38 | local echo = string.format('printf "%sstatus\\ncurrentsong\\nclose\\n"', password) 39 | local cmd = string.format("%s | curl --connect-timeout 1 -fsm 3 %s", echo, mpdh) 40 | 41 | mpd_notification_preset = { title = "Now playing", timeout = 6 } 42 | 43 | helpers.set_map("current mpd track", nil) 44 | 45 | function mpd.update() 46 | helpers.async({ shell, "-c", cmd }, function(f) 47 | mpd_now = { 48 | random_mode = false, 49 | single_mode = false, 50 | repeat_mode = false, 51 | consume_mode = false, 52 | pls_pos = "N/A", 53 | pls_len = "N/A", 54 | state = "N/A", 55 | file = "N/A", 56 | name = "N/A", 57 | artist = "N/A", 58 | title = "N/A", 59 | album = "N/A", 60 | genre = "N/A", 61 | track = "N/A", 62 | date = "N/A", 63 | time = "N/A", 64 | elapsed = "N/A", 65 | volume = "N/A", 66 | } 67 | 68 | for line in string.gmatch(f, "[^\n]+") do 69 | for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do 70 | if k == "state" then 71 | mpd_now.state = v 72 | elseif k == "file" then 73 | mpd_now.file = v 74 | elseif k == "Name" then 75 | mpd_now.name = escape_f(v) 76 | elseif k == "Artist" then 77 | mpd_now.artist = escape_f(v) 78 | elseif k == "Title" then 79 | mpd_now.title = escape_f(v) 80 | elseif k == "Album" then 81 | mpd_now.album = escape_f(v) 82 | elseif k == "Genre" then 83 | mpd_now.genre = escape_f(v) 84 | elseif k == "Track" then 85 | mpd_now.track = escape_f(v) 86 | elseif k == "Date" then 87 | mpd_now.date = escape_f(v) 88 | elseif k == "Time" then 89 | mpd_now.time = v 90 | elseif k == "elapsed" then 91 | mpd_now.elapsed = string.match(v, "%d+") 92 | elseif k == "song" then 93 | mpd_now.pls_pos = v 94 | elseif k == "playlistlength" then 95 | mpd_now.pls_len = v 96 | elseif k == "repeat" then 97 | mpd_now.repeat_mode = v ~= "0" 98 | elseif k == "single" then 99 | mpd_now.single_mode = v ~= "0" 100 | elseif k == "random" then 101 | mpd_now.random_mode = v ~= "0" 102 | elseif k == "consume" then 103 | mpd_now.consume_mode = v ~= "0" 104 | elseif k == "volume" then 105 | mpd_now.volume = v 106 | end 107 | end 108 | end 109 | 110 | mpd_notification_preset.text = 111 | string.format("%s (%s) - %s\n%s", mpd_now.artist, mpd_now.album, mpd_now.date, mpd_now.title) 112 | widget = mpd.widget 113 | settings() 114 | 115 | if mpd_now.state == "play" then 116 | if notify == "on" and mpd_now.title ~= helpers.get_map("current mpd track") then 117 | helpers.set_map("current mpd track", mpd_now.title) 118 | 119 | if followtag then 120 | mpd_notification_preset.screen = focused() 121 | end 122 | 123 | local common = { 124 | preset = mpd_notification_preset, 125 | icon = default_art, 126 | icon_size = cover_size, 127 | replaces_id = mpd.id, 128 | } 129 | 130 | if not string.match(mpd_now.file, "http.*://") then -- local file instead of http stream 131 | local path = string.format("%s/%s", music_dir, string.match(mpd_now.file, ".*/")) 132 | local cover = string.format( 133 | "find '%s' -maxdepth 1 -type f | egrep -i -m1 '%s'", 134 | path:gsub("'", "'\\''"), 135 | cover_pattern 136 | ) 137 | helpers.async({ shell, "-c", cover }, function(current_icon) 138 | common.icon = current_icon:gsub("\n", "") 139 | if #common.icon == 0 then 140 | common.icon = nil 141 | end 142 | mpd.id = naughty.notify(common).id 143 | end) 144 | else 145 | mpd.id = naughty.notify(common).id 146 | end 147 | end 148 | elseif mpd_now.state ~= "pause" then 149 | helpers.set_map("current mpd track", nil) 150 | end 151 | end) 152 | end 153 | 154 | mpd.timer = helpers.newtimer("mpd", timeout, mpd.update, true, true) 155 | 156 | return mpd 157 | end 158 | 159 | return factory 160 | -------------------------------------------------------------------------------- /bar/modules/mpd.lua: -------------------------------------------------------------------------------- 1 | local wibox = require("wibox.init") 2 | local beautiful = require("beautiful") 3 | local helper = require("helper") 4 | local awful = require("awful") 5 | 6 | local M = {} 7 | 8 | local timeout = 2 9 | 10 | M.icons = { 11 | prev = "󰒮", 12 | play = "󰐊", 13 | pause = "󰏤", 14 | next = "󰒭", 15 | stop = "", 16 | } 17 | 18 | local function mpd_update() 19 | local list = { 20 | M.icons[mpd_now.state], 21 | mpd_now.title, 22 | } 23 | local text = table.concat(list, " ") 24 | M.widget.markup = string.format( 25 | " %s ", 26 | beautiful.font, 27 | beautiful.font_size, 28 | beautiful.fg_normal, 29 | text 30 | ) 31 | end 32 | 33 | M.widget = wibox.widget({ 34 | markup = string.format( 35 | " Now Playing ", 36 | beautiful.font, 37 | beautiful.font_size, 38 | beautiful.fg_normal 39 | ), 40 | align = "center", 41 | valign = "center", 42 | widget = wibox.widget.textbox, 43 | }) 44 | 45 | M.setup = function() 46 | helper.newtimer("Mpd Status Text", timeout, mpd_update, true, true) 47 | 48 | M.widget:connect_signal("button::press", function(_, _, _, button) 49 | if button == 1 then 50 | awful.spawn("mpc toggle") 51 | end 52 | if button == 3 then 53 | awful.spawn("ghostty -e ncmpcpp") 54 | end 55 | if button == 4 then 56 | awful.spawn("mpc volume +5") 57 | end 58 | if button == 5 then 59 | awful.spawn("mpc volume -5") 60 | end 61 | end) 62 | 63 | return M.widget 64 | end 65 | 66 | return M 67 | -------------------------------------------------------------------------------- /bar/modules/systray.lua: -------------------------------------------------------------------------------- 1 | local wibox = require("wibox.init") 2 | local helper = require("helper") 3 | local beautiful = require("beautiful") 4 | local awful = require("awful") 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local M = {} 8 | 9 | M.setup = function() 10 | local tray = wibox.widget.systray() 11 | tray.opacity = 1.0 12 | 13 | local widget = wibox.widget({ 14 | { 15 | widget = tray, 16 | }, 17 | left = dpi(6), 18 | top = dpi(2), 19 | bottom = dpi(2), 20 | right = dpi(6), 21 | widget = wibox.container.margin, 22 | }) 23 | 24 | return helper.create_rounded_widget(widget, dpi(10)) 25 | end 26 | 27 | return M 28 | -------------------------------------------------------------------------------- /bar/modules/taglist.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local fancy_taglist = require("external.fancy_taglist") 3 | local beautiful = require("beautiful") 4 | local dpi = beautiful.xresources.apply_dpi 5 | local helper = require("helper") 6 | 7 | local M = {} 8 | 9 | local tag_buttons = { 10 | awful.button({}, 1, function(t) 11 | t:view_only() 12 | end), 13 | awful.button({}, 4, function(t) 14 | awful.tag.viewprev(t.screen) 15 | end), 16 | awful.button({}, 5, function(t) 17 | awful.tag.viewnext(t.screen) 18 | end), 19 | } 20 | 21 | local tasklist_buttons = { 22 | awful.button({}, 1, function(c) 23 | c:emit_signal("request::activate", "tasklist", { raise = true, switch_to_tags = true }) 24 | end), 25 | awful.button({}, 3, function() 26 | awful.menu.client_list({ theme = { width = 250 } }) 27 | end), 28 | awful.button({}, 4, function() 29 | awful.client.focus.byidx(1) 30 | end), 31 | awful.button({}, 5, function() 32 | awful.client.focus.byidx(-1) 33 | end), 34 | } 35 | 36 | M.setup = function(s) 37 | s.mytaglist = fancy_taglist.new({ 38 | screen = s, 39 | taglist = { buttons = tag_buttons }, 40 | tasklist = { buttons = tasklist_buttons }, 41 | }) 42 | 43 | local rounded_widget = helper.create_rounded_widget(s.mytaglist, dpi(12)) 44 | 45 | return rounded_widget 46 | end 47 | 48 | return M 49 | -------------------------------------------------------------------------------- /bar/modules/time.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox.init") 3 | local beautiful = require("beautiful") 4 | 5 | local M = {} 6 | 7 | M.setup = function() 8 | local widget = wibox.widget.textclock( 9 | string.format( 10 | "%%a %%b %%d, %%I:%%M", 11 | beautiful.font, 12 | beautiful.font_size, 13 | beautiful.fg_normal 14 | ) 15 | ) 16 | return widget 17 | end 18 | 19 | return M 20 | -------------------------------------------------------------------------------- /bar/modules/volume.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox.init") 3 | local gears = require("gears") 4 | local beautiful = require("beautiful") 5 | local helper = require("helper") 6 | local dpi = beautiful.xresources.apply_dpi 7 | local naughty = require("naughty") 8 | 9 | local M = {} 10 | 11 | M.widget = wibox.widget({ 12 | { 13 | id = "volume_icon", 14 | markup = string.format("󰕾", beautiful.accent), 15 | widget = wibox.widget.textbox, 16 | }, 17 | { 18 | id = "volume_slider", 19 | forced_height = dpi(1), 20 | forced_width = dpi(59), 21 | color = beautiful.accent, 22 | background_color = beautiful.module_bg, 23 | margins = dpi(2), 24 | paddings = dpi(2), 25 | max_value = 150, 26 | ticks = true, 27 | ticks_size = dpi(6), 28 | shape = gears.shape.rounded_bar, 29 | widget = wibox.widget.progressbar, 30 | }, 31 | { 32 | id = "volume_text", 33 | markup = string.format( 34 | " 0%% ", 35 | beautiful.font, 36 | beautiful.font_size, 37 | beautiful.fg_normal 38 | ), 39 | align = "center", 40 | valign = "center", 41 | widget = wibox.widget.textbox, 42 | }, 43 | forced_width = dpi(148), 44 | layout = wibox.layout.align.horizontal, 45 | }) 46 | 47 | local icons = { 48 | muted = " 󰝟 ", 49 | low = " 󰕿 ", 50 | med = " 󰖀 ", 51 | high = " 󰕾 ", 52 | } 53 | 54 | local critical_bar_color = { 55 | type = "linear", 56 | from = { 0, 0 }, 57 | to = { 96, 0 }, 58 | stops = { 59 | { 0, beautiful.accent }, 60 | { 1, beautiful.bg_urgent }, 61 | }, 62 | } 63 | 64 | local debounce_timer = nil 65 | 66 | local function set_volume_debounced(volume) 67 | if debounce_timer then 68 | debounce_timer:stop() 69 | end 70 | 71 | debounce_timer = gears.timer.start_new(0.2, function() 72 | awful.spawn("wpctl set-volume @DEFAULT_SINK@ " .. volume) 73 | end) 74 | end 75 | 76 | local function update_volume_sys_event(icon, volume) 77 | M.widget.volume_icon:set_markup(string.format("" .. icon .. "", beautiful.accent)) 78 | M.widget.volume_text.markup = string.format( 79 | " " .. volume .. "%% ", 80 | beautiful.font, 81 | beautiful.font_size, 82 | beautiful.fg_normal 83 | ) 84 | M.widget.volume_slider:set_value(volume) 85 | if volume > 100 then 86 | M.widget.volume_slider.color = critical_bar_color 87 | else 88 | M.widget.volume_slider.color = beautiful.accent 89 | end 90 | end 91 | 92 | local function sync_volume_changes() 93 | awful.spawn.easy_async("wpctl get-volume @DEFAULT_SINK@", function(stdout) 94 | local icon 95 | local volume = tonumber(stdout:match("(%d%.%d%d?)")) * 100 96 | if stdout:match("MUTED") then 97 | icon = icons.muted 98 | update_volume_sys_event(icon, volume) 99 | return 100 | end 101 | if volume < 50 then 102 | icon = icons.low 103 | elseif volume < 90 then 104 | icon = icons.med 105 | else 106 | icon = icons.high 107 | end 108 | 109 | update_volume_sys_event(icon, volume) 110 | end) 111 | end 112 | 113 | M.setup = function() 114 | -- kill process if pactl subscribe is already running 115 | awful.spawn.easy_async_with_shell( 116 | "ps x | grep 'pactl subscribe' | grep -v grep | awk '{print $1}' | xargs kill", 117 | function() 118 | -- start new one with callback 119 | awful.spawn.with_line_callback( 120 | [[bash -c "pactl subscribe | grep --line-buffered \"Event 'change' on sink #\""]], 121 | { 122 | stdout = function(_) 123 | sync_volume_changes() 124 | end, 125 | } 126 | ) 127 | end 128 | ) 129 | 130 | M.widget.volume_slider:connect_signal("property::value", function() 131 | set_volume_debounced(M.widget.volume_slider.value / 100) 132 | end) 133 | 134 | M.widget.volume_slider:connect_signal("button::press", function(_, _, _, button) 135 | if button == 4 then 136 | awful.spawn("wpctl set-volume @DEFAULT_SINK@ 5%+") 137 | end 138 | if button == 5 then 139 | awful.spawn("wpctl set-volume @DEFAULT_SINK@ 5%-") 140 | end 141 | end) 142 | 143 | M.widget.volume_icon:connect_signal("button::press", function(_, _, _, button) 144 | if button == 1 then 145 | awful.spawn("wpctl set-mute @DEFAULT_SINK@ toggle") 146 | end 147 | if button == 3 then 148 | awful.spawn("pavucontrol-qt") 149 | end 150 | end) 151 | 152 | helper.hover_hand(M.widget.volume_icon) 153 | helper.hover_hand(M.widget.volume_slider) 154 | 155 | sync_volume_changes() 156 | 157 | return helper.create_rounded_widget(M.widget, dpi(12)) 158 | end 159 | 160 | return M 161 | -------------------------------------------------------------------------------- /external/fancy_taglist.lua: -------------------------------------------------------------------------------- 1 | -- awesomewm fancy_taglist: a taglist that contains a tasklist for each tag. 2 | -- Usage (add s.mytaglist to the wibar): 3 | -- awful.screen.connect_for_each_screen(function(s) 4 | -- ... 5 | -- local fancy_taglist = require("fancy_taglist") 6 | -- s.mytaglist = fancy_taglist.new({ 7 | -- screen = s, 8 | -- taglist = { buttons = mytagbuttons }, 9 | -- tasklist = { buttons = mytasklistbuttons } 10 | -- }) 11 | -- ... 12 | -- end) 13 | -- 14 | -- If you want rounded corners, try this in your theme: 15 | -- theme.taglist_shape = function(cr, w, h) 16 | -- return gears.shape.rounded_rect(cr, w, h, theme.border_radius) 17 | -- end 18 | local awful = require("awful") 19 | local beautiful = require("beautiful") 20 | local gears = require("gears") 21 | local wibox = require("wibox") 22 | 23 | local dpi = beautiful.xresources.apply_dpi 24 | local internal_spacing = dpi(4) 25 | local box_height = dpi(4) 26 | local box_width = dpi(8) 27 | local icon_size = dpi(16) 28 | 29 | local function box_margins(widget) 30 | return { 31 | { widget, widget = wibox.container.place }, 32 | top = box_height, 33 | bottom = box_height, 34 | left = box_width, 35 | right = box_width, 36 | widget = wibox.container.margin, 37 | } 38 | end 39 | 40 | local function constrain_icon(widget) 41 | return { 42 | { 43 | widget, 44 | height = icon_size, 45 | strategy = "exact", 46 | widget = wibox.container.constraint, 47 | }, 48 | widget = wibox.container.place, 49 | } 50 | end 51 | 52 | local function fancy_tasklist(cfg, tag) 53 | local function only_this_tag(c, _) 54 | for _, t in ipairs(c:tags()) do 55 | if t == tag then 56 | return true 57 | end 58 | end 59 | return false 60 | end 61 | 62 | local overrides = { 63 | filter = only_this_tag, 64 | layout = { 65 | spacing = beautiful.taglist_spacing, 66 | layout = wibox.layout.fixed.horizontal, 67 | }, 68 | widget_template = { 69 | id = "clienticon", 70 | widget = awful.widget.clienticon, 71 | create_callback = function(self, c, _, _) 72 | self:get_children_by_id("clienticon")[1].client = c 73 | end, 74 | }, 75 | } 76 | return awful.widget.tasklist(gears.table.join(cfg, overrides)) 77 | end 78 | 79 | local module = {} 80 | 81 | -- @param cfg.screen 82 | -- @param cfg.tasklist -> see awful.widget.tasklist 83 | -- @param cfg.taglist -> see awful.widget.taglist 84 | function module.new(cfg) 85 | cfg = cfg or {} 86 | local taglist_cfg = cfg.taglist or {} 87 | local tasklist_cfg = cfg.tasklist or {} 88 | 89 | local screen = cfg.screen or awful.screen.focused() 90 | taglist_cfg.screen = screen 91 | tasklist_cfg.screen = screen 92 | 93 | local function update_callback(self, tag, _, _) 94 | -- make sure that empty tasklists take up no extra space 95 | local list_separator = self:get_children_by_id("list_separator")[1] 96 | if #tag:clients() == 0 then 97 | list_separator.spacing = 0 98 | else 99 | list_separator.spacing = internal_spacing 100 | end 101 | end 102 | 103 | local function create_callback(self, tag, _index, _tags) 104 | local tasklist = fancy_tasklist(tasklist_cfg, tag) 105 | self:get_children_by_id("tasklist_placeholder")[1]:add(tasklist) 106 | update_callback(self, tag, _index, _tags) 107 | end 108 | 109 | local overrides = { 110 | filter = awful.widget.taglist.filter.all, 111 | widget_template = { 112 | box_margins({ 113 | -- tag 114 | { 115 | id = "text_role", 116 | widget = wibox.widget.textbox, 117 | align = "center", 118 | }, 119 | -- tasklist 120 | constrain_icon({ 121 | id = "tasklist_placeholder", 122 | layout = wibox.layout.fixed.horizontal, 123 | }), 124 | id = "list_separator", 125 | spacing = internal_spacing, 126 | layout = wibox.layout.fixed.horizontal, 127 | }), 128 | id = "background_role", 129 | widget = wibox.container.background, 130 | create_callback = create_callback, 131 | update_callback = update_callback, 132 | }, 133 | } 134 | return awful.widget.taglist(gears.table.join(taglist_cfg, overrides)) 135 | end 136 | 137 | return module 138 | -------------------------------------------------------------------------------- /helper.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local gears = require("gears") 4 | local beautiful = require("beautiful") 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local M = {} 8 | local helpers = {} 9 | helpers.map_table = {} 10 | 11 | --- adds hover properties to a background container 12 | --- to add hover to box_widget use 'widget:get_children_by_id("box_container")\[1\]' as widget name 13 | ---@param t {widget: table, newbg: string, oldbg: string, hover_cursor: string} 14 | M.hover = function(t) 15 | setmetatable(t, { __index = { hover_cursor = "hand1" } }) 16 | local widget = t.widget 17 | local newbg = t.newbg 18 | local oldbg = t.oldbg 19 | local hover_cursor = t.hover_cursor 20 | widget:connect_signal("mouse::enter", function() 21 | widget:set_bg(newbg) 22 | ---@diagnostic disable-next-line: undefined-global 23 | local w = mouse.current_wibox 24 | if w then 25 | w.cursor = hover_cursor 26 | end 27 | end) 28 | widget:connect_signal("mouse::leave", function() 29 | widget:set_bg(oldbg) 30 | ---@diagnostic disable-next-line: undefined-global 31 | local w = mouse.current_wibox 32 | if w then 33 | w.cursor = "left_ptr" 34 | end 35 | end) 36 | end 37 | 38 | --- same as hover but dosent change the background 39 | ---@see M.hover 40 | ---@param widget table 41 | M.hover_hand = function(widget) 42 | widget:connect_signal("mouse::enter", function() 43 | ---@diagnostic disable-next-line: undefined-global 44 | local w = mouse.current_wibox 45 | if w then 46 | w.cursor = "hand1" 47 | end 48 | end) 49 | widget:connect_signal("mouse::leave", function() 50 | ---@diagnostic disable-next-line: undefined-global 51 | local w = mouse.current_wibox 52 | if w then 53 | w.cursor = "left_ptr" 54 | end 55 | end) 56 | end 57 | 58 | M.padding_horizontal = function(dpi) 59 | return wibox.widget({ 60 | forced_width = dpi, 61 | widget = wibox.container.background, 62 | }) 63 | end 64 | 65 | M.create_rounded_widget = function(widget, amount) 66 | local container = wibox.widget({ 67 | { 68 | { 69 | M.padding_horizontal(dpi(6)), 70 | widget, 71 | M.padding_horizontal(dpi(6)), 72 | layout = wibox.layout.fixed.horizontal, 73 | }, 74 | id = "container", 75 | bg = beautiful.module_bg, 76 | shape = function(cr, width, height) 77 | gears.shape.rounded_rect(cr, width, height, amount) 78 | end, 79 | widget = wibox.container.background, 80 | }, 81 | left = dpi(8), 82 | right = dpi(8), 83 | top = dpi(4), 84 | bottom = dpi(4), 85 | widget = wibox.container.margin, 86 | }) 87 | 88 | -- lrtb 89 | return container 90 | end 91 | 92 | M.set_map = function(element, value) 93 | helpers.map_table[element] = value 94 | end 95 | 96 | M.get_map = function(element) 97 | return helpers.map_table[element] 98 | end 99 | 100 | M.async = function(cmd, callback) 101 | return awful.spawn.easy_async(cmd, function(stdout, _, _, exit_code) 102 | callback(stdout, exit_code) 103 | end) 104 | end 105 | 106 | M.newtimer = function(name, timeout, fun, nostart, stoppable) 107 | if not name or #name == 0 then 108 | return 109 | end 110 | local key = stoppable and name or timeout 111 | helpers.timer_table = helpers.timer_table or {} 112 | if not helpers.timer_table[key] then 113 | helpers.timer_table[key] = gears.timer({ timeout = timeout }) 114 | helpers.timer_table[key]:start() 115 | end 116 | helpers.timer_table[key]:connect_signal("timeout", fun) 117 | if not nostart then 118 | helpers.timer_table[key]:emit_signal("timeout") 119 | end 120 | return stoppable and helpers.timer_table[key] 121 | end 122 | 123 | M.first_line = function(path) 124 | local file, first = io.open(path, "rb"), nil 125 | if file then 126 | first = file:read("*l") 127 | file:close() 128 | end 129 | return first 130 | end 131 | 132 | M.line_callback = function(cmd, callback) 133 | return awful.spawn.with_line_callback(cmd, { 134 | stdout = function(line) 135 | callback(line) 136 | end, 137 | }) 138 | end 139 | 140 | return M 141 | -------------------------------------------------------------------------------- /keys.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful.init") 2 | local hotkeys_popup = require("awful.hotkeys_popup") 3 | 4 | local modkey = "Mod4" 5 | local terminal = "ghostty" 6 | 7 | -- General Awesome keys 8 | awful.keyboard.append_global_keybindings({ 9 | awful.key({ modkey }, "F1", hotkeys_popup.show_help, { description = "show help", group = "awesome" }), 10 | 11 | awful.key({ modkey, "Shift" }, "r", awesome.restart, { description = "reload awesome", group = "awesome" }), 12 | 13 | awful.key({ modkey }, "Return", function() 14 | awful.spawn(terminal) 15 | end, { description = "open a terminal", group = "launcher" }), 16 | 17 | awful.key({}, "Print", function() 18 | awful.spawn.with_shell("scrot -s -e 'mv $f ~/Pictures/'") 19 | end, { description = "Print Screen with scrot" }), 20 | 21 | awful.key({ "Mod1" }, "space", function() 22 | awful.spawn("rofi -show drun") 23 | end, { description = "Rofi", group = "launcher" }), 24 | }) 25 | 26 | -- Tags related keybindings 27 | awful.keyboard.append_global_keybindings({ 28 | awful.key({ modkey }, "Tab", function() 29 | local c = awful.client.focus.history.list[2] 30 | client.focus = c 31 | local t = client.focus and client.focus.first_tag or nil 32 | if t then 33 | t:view_only() 34 | end 35 | c:raise() 36 | end, { description = "Toggle Between clients", group = "Tag" }), 37 | }) 38 | 39 | -- Focus related keybindings 40 | awful.keyboard.append_global_keybindings({ 41 | awful.key({ modkey }, "j", function() 42 | awful.client.focus.bydirection("down", client.focused) 43 | end, { description = "focus by direction down", group = "client" }), 44 | 45 | awful.key({ modkey }, "k", function() 46 | awful.client.focus.bydirection("up", client.focused) 47 | end, { description = "focus by direction up", group = "client" }), 48 | 49 | awful.key({ modkey }, "h", function() 50 | awful.client.focus.bydirection("left", client.focused) 51 | end, { description = "focus by direction left", group = "client" }), 52 | 53 | awful.key({ modkey }, "l", function() 54 | awful.client.focus.bydirection("right", client.focused) 55 | end, { description = "focus by direction down", group = "client" }), 56 | }) 57 | 58 | -- Layout related keybindings 59 | awful.keyboard.append_global_keybindings({ 60 | awful.key({ modkey, "Shift" }, "j", function() 61 | awful.client.swap.bydirection("down", client.focused) 62 | end, { description = "swap by direction down", group = "client" }), 63 | 64 | awful.key({ modkey, "Shift" }, "k", function() 65 | awful.client.swap.bydirection("up", client.focused) 66 | end, { description = "swap by direction up", group = "client" }), 67 | 68 | awful.key({ modkey, "Shift" }, "h", function() 69 | awful.client.swap.bydirection("left", client.focused) 70 | end, { description = "swap by direction left", group = "client" }), 71 | 72 | awful.key({ modkey, "Shift" }, "l", function() 73 | awful.client.swap.bydirection("right", client.focused) 74 | end, { description = "swap by direction right", group = "client" }), 75 | 76 | awful.key({ modkey }, "u", awful.client.urgent.jumpto, { description = "jump to urgent client", group = "client" }), 77 | 78 | awful.key({ modkey }, "space", function() 79 | awful.layout.inc(1) 80 | end, { description = "select next", group = "layout" }), 81 | 82 | awful.key({ modkey, "Shift" }, "space", function() 83 | awful.layout.inc(-1) 84 | end, { description = "select previous", group = "layout" }), 85 | 86 | awful.key({ modkey, "Control" }, "k", function() 87 | awful.client.incwfact(-0.05) 88 | end, { description = "Resize by direction up", group = "client" }), 89 | 90 | awful.key({ modkey, "Control" }, "j", function() 91 | awful.client.incwfact(0.05) 92 | end, { description = "Resize by direction down", group = "client" }), 93 | 94 | awful.key({ modkey, "Control" }, "h", function() 95 | awful.tag.incmwfact(-0.05) 96 | end, { description = "Resize by direction left", group = "client" }), 97 | 98 | awful.key({ modkey, "Control" }, "l", function() 99 | awful.tag.incmwfact(0.05) 100 | end, { description = "Resize by direction right", group = "client" }), 101 | }) 102 | 103 | awful.keyboard.append_global_keybindings({ 104 | awful.key({ 105 | modifiers = { modkey }, 106 | keygroup = "numrow", 107 | description = "only view tag", 108 | group = "tag", 109 | on_press = function(index) 110 | local screen = awful.screen.focused() 111 | local tag = screen.tags[index] 112 | if tag then 113 | tag:view_only() 114 | end 115 | end, 116 | }), 117 | awful.key({ 118 | modifiers = { modkey, "Control" }, 119 | keygroup = "numrow", 120 | description = "toggle tag", 121 | group = "tag", 122 | on_press = function(index) 123 | local screen = awful.screen.focused() 124 | local tag = screen.tags[index] 125 | if tag then 126 | awful.tag.viewtoggle(tag) 127 | end 128 | end, 129 | }), 130 | awful.key({ 131 | modifiers = { modkey, "Shift" }, 132 | keygroup = "numrow", 133 | description = "move focused client to tag", 134 | group = "tag", 135 | on_press = function(index) 136 | if client.focus then 137 | local tag = client.focus.screen.tags[index] 138 | if tag then 139 | client.focus:move_to_tag(tag) 140 | end 141 | end 142 | end, 143 | }), 144 | }) 145 | 146 | -- Some Function Keys 147 | awful.keyboard.append_global_keybindings({ 148 | awful.key({}, "XF86MonBrightnessUp", function() 149 | awful.spawn("brightnessctl set +10", false) 150 | end, { description = "increase brightness by 10%", group = "hotkeys" }), 151 | awful.key({}, "XF86MonBrightnessDown", function() 152 | awful.spawn("brightnessctl set -10", false) 153 | end, { description = "decrease brightness by 10%", group = "hotkeys" }), 154 | awful.key({}, "XF86AudioLowerVolume", function() 155 | require("bar.modules.volume.wireplumber").decVol(5) 156 | end, { description = "volume down", group = "hotkeys" }), 157 | awful.key({}, "XF86AudioRaiseVolume", function() 158 | require("bar.modules.volume.wireplumber").incVol(5) 159 | end, { description = "volume up", group = "hotkeys" }), 160 | awful.key({}, "XF86AudioMute", function() 161 | awful.spawn("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", false) 162 | end, { description = "toggle mute", group = "hotkeys" }), 163 | awful.key({}, "XF86AudioNext", function() 164 | awful.spawn("mpc next", false) 165 | end, { description = "next music", group = "hotkeys" }), 166 | awful.key({}, "XF86AudioPrev", function() 167 | awful.spawn("mpc prev", false) 168 | end, { description = "previous music", group = "hotkeys" }), 169 | awful.key({}, "XF86AudioPlay", function() 170 | awful.spawn("mpc toggle", false) 171 | end, { description = "play/pause music", group = "hotkeys" }), 172 | }) 173 | 174 | -- ──────────────────────────────────────────────────────────── 175 | 176 | client.connect_signal("request::default_mousebindings", function() 177 | awful.mouse.append_client_mousebindings({ 178 | awful.button({}, 1, function(c) 179 | c:activate({ context = "mouse_click" }) 180 | end), 181 | awful.button({ modkey }, 1, function(c) 182 | c:activate({ context = "mouse_click", action = "mouse_move" }) 183 | end), 184 | awful.button({ modkey }, 3, function(c) 185 | c:activate({ context = "mouse_click", action = "mouse_resize" }) 186 | end), 187 | }) 188 | end) 189 | 190 | client.connect_signal("request::default_keybindings", function() 191 | awful.keyboard.append_client_keybindings({ 192 | awful.key({ modkey }, "f", function(c) 193 | c.fullscreen = not c.fullscreen 194 | c:raise() 195 | end, { description = "toggle fullscreen", group = "client" }), 196 | 197 | awful.key({ modkey }, "q", function(c) 198 | c:kill() 199 | end, { description = "close", group = "client" }), 200 | 201 | awful.key({ modkey }, "F2", function(c) 202 | awful.spawn.easy_async_with_shell( 203 | '/usr/bin/i3lock -i ~/wallpapers/home/turbo.jpg -nfe --fill --force-clock --indicator --verif-text="" --wrong-text="" --noinput-text=""' 204 | ) 205 | end, { description = "close", group = "client" }), 206 | 207 | awful.key({ modkey }, "t", awful.client.floating.toggle, { description = "toggle floating", group = "client" }), 208 | 209 | awful.key({ modkey }, "t", function(c) 210 | c.ontop = not c.ontop 211 | end, { description = "toggle keep on top", group = "client" }), 212 | }) 213 | end) 214 | -------------------------------------------------------------------------------- /notifications.lua: -------------------------------------------------------------------------------- 1 | local naughty = require("naughty") 2 | 3 | -- Check if awesome encountered an error during startup and fell back to 4 | -- another config (This code will only ever execute for the fallback config) 5 | naughty.connect_signal("request::display_error", function(message, startup) 6 | naughty.notification({ 7 | urgency = "critical", 8 | title = "Oops, an error happened" .. (startup and " during startup!" or "!"), 9 | message = message, 10 | }) 11 | end) 12 | -------------------------------------------------------------------------------- /rc.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: different-requires 2 | local beautiful = require("beautiful") 3 | local theme = require("theme") 4 | 5 | pcall(require, "luarocks.loader") 6 | 7 | beautiful.init(theme) 8 | 9 | local hostname 10 | local f = io.open("/proc/sys/kernel/hostname", "r") 11 | if f then 12 | hostname = f:read("*l") 13 | f:close() 14 | end 15 | 16 | if hostname == "thinkpad" then 17 | _G.laptop = true 18 | else 19 | _G.laptop = false 20 | end 21 | 22 | require("awful.autofocus") 23 | require("awful.hotkeys_popup.keys") 24 | require("keys") 25 | require("rules") 26 | require("notifications") 27 | require("autostart") 28 | require("wallpaper") 29 | 30 | -- vertical or horizontal 31 | require("bar.init").setup({}) 32 | 33 | require("awful").screen.set_auto_dpi_enabled(false) 34 | 35 | -- Enable sloppy focus, so that focus follows mouse. 36 | client.connect_signal("mouse::enter", function(c) 37 | c:activate({ context = "mouse_enter", raise = false }) 38 | end) 39 | 40 | -- dont maximize clients automatically on open 41 | client.connect_signal("request::manage", function(c) 42 | c.maximized = false 43 | c.maximized_horizontal = false 44 | c.maximized_vertical = false 45 | -- make floating windows always ontop 46 | if c.floating then 47 | c.ontop = true 48 | end 49 | end) 50 | 51 | ---@diagnostic disable-next-line: param-type-mismatch 52 | collectgarbage("setpause", 110) 53 | ---@diagnostic disable-next-line: param-type-mismatch 54 | collectgarbage("setstepmul", 1000) 55 | -------------------------------------------------------------------------------- /rules.lua: -------------------------------------------------------------------------------- 1 | local ruled = require("ruled") 2 | local awful = require("awful") 3 | 4 | -- Rules to apply to new clients. 5 | ruled.client.connect_signal("request::rules", function() 6 | -- All clients will match this rule. 7 | ruled.client.append_rule({ 8 | id = "global", 9 | rule = {}, 10 | properties = { 11 | focus = awful.client.focus.filter, 12 | raise = true, 13 | screen = awful.screen.preferred, 14 | placement = awful.placement.no_overlap + awful.placement.no_offscreen, 15 | }, 16 | }) 17 | 18 | -- Floating clients. 19 | ruled.client.append_rule({ 20 | id = "floating", 21 | rule_any = { 22 | instance = { 23 | "copyq", 24 | "pinentry", 25 | "update_installer", 26 | }, 27 | class = { 28 | "Blueman-manager", 29 | "Gpick", 30 | "Sxiv", 31 | "Wpa_gui", 32 | "org.kde.ark", 33 | "veracrypt", 34 | "gnome-calculator", 35 | "pavucontrol", 36 | "gnome-calendar", 37 | "gnome-power-statistics", 38 | "nm-connection-editor", 39 | "Lxappearance", 40 | }, 41 | -- Note that the name property shown in xprop might be set slightly after creation of the client 42 | -- and the name shown there might not match defined rules here. 43 | name = { 44 | "Event Tester", -- xev. 45 | "Steam Guard", 46 | }, 47 | role = { 48 | "AlarmWindow", -- Thunderbird's calendar. 49 | "ConfigManager", -- Thunderbird's about:config. 50 | "pop-up", -- e.g. Google Chrome's (detached) Developer Tools. 51 | "GtkFileChooserDialog", 52 | "dialog", 53 | "menu", 54 | "task_dialog", 55 | "bubble", 56 | "Preferences", 57 | }, 58 | }, 59 | properties = { floating = true }, 60 | }) 61 | 62 | -- Set Firefox to always map on the tag named "2" on screen 1. 63 | ruled.client.append_rule({ 64 | rule = { class = { "krita", "xournalpp" } }, 65 | properties = { tag = " ", switch_to_tags = true }, 66 | }) 67 | 68 | ruled.client.append_rule({ 69 | rule = { 70 | class = { "steamwebhelper", "steam", "Steam" }, 71 | name = { "Steam" }, 72 | }, 73 | properties = { tag = "󰊠 ", switch_to_tags = true }, 74 | }) 75 | 76 | ruled.client.append_rule({ 77 | rule = { class = "dolphin" }, 78 | properties = { tag = " ", switch_to_tags = true }, 79 | }) 80 | end) 81 | -------------------------------------------------------------------------------- /theme/gtk.lua: -------------------------------------------------------------------------------- 1 | local beautiful = require("beautiful") 2 | local gtk = beautiful.gtk.get_theme_variables() 3 | 4 | local colors = { 5 | font_size = gtk.font_size, 6 | font_family = gtk.font_family, 7 | bg_color = gtk.bg_color, 8 | fg_color = gtk.fg_color, 9 | base_color = gtk.base_color, 10 | text_color = gtk.text_color, 11 | button_bg_color = gtk.button_bg_color, 12 | button_fg_color = gtk.button_fg_color, 13 | button_border_color = gtk.button_border_color, 14 | button_border_radius = gtk.button_border_radius, 15 | button_border_width = gtk.button_border_width, 16 | selected_bg_color = gtk.selected_bg_color, 17 | selected_fg_color = gtk.selected_fg_color, 18 | menubar_bg_color = gtk.menubar_bg_color, 19 | menubar_fg_color = gtk.menubar_fg_color, 20 | header_button_bg_color = gtk.header_button_bg_color, 21 | header_button_fg_color = gtk.header_button_fg_color, 22 | header_button_border_color = gtk.header_button_border_color, 23 | error_color = gtk.error_color, 24 | error_bg_color = gtk.error_bg_color, 25 | error_fg_color = gtk.error_fg_color, 26 | warning_color = gtk.warning_color, 27 | warning_bg_color = gtk.warning_bg_color, 28 | warning_fg_color = gtk.warning_fg_color, 29 | success_color = gtk.success_color, 30 | success_bg_color = gtk.success_bg_color, 31 | success_fg_color = gtk.success_fg_color, 32 | tooltip_bg_color = gtk.tooltip_bg_color, 33 | tooltip_fg_color = gtk.tooltip_fg_color, 34 | osd_bg_color = gtk.osd_bg_color, 35 | osd_fg_color = gtk.osd_fg_color, 36 | osd_border_color = gtk.osd_border_color, 37 | wm_bg_color = gtk.wm_bg_color, 38 | wm_border_focused_color = gtk.wm_border_focused_color, 39 | wm_border_unfocused_color = gtk.wm_border_unfocused_color, 40 | wm_title_focused_color = gtk.wm_title_focused_color, 41 | wm_title_unfocused_color = gtk.wm_title_unfocused_color, 42 | wm_icons_focused_color = gtk.wm_icons_focused_color, 43 | wm_icons_unfocused_color = gtk.wm_icons_unfocused_color, 44 | } 45 | 46 | return colors 47 | -------------------------------------------------------------------------------- /theme/init.lua: -------------------------------------------------------------------------------- 1 | --------------------------- 2 | -- Default awesome theme -- 3 | --------------------------- 4 | 5 | local theme_assets = require("beautiful.theme_assets") 6 | local xresources = require("beautiful.xresources") 7 | local rnotification = require("ruled.notification") 8 | local dpi = xresources.apply_dpi 9 | local gears = require("gears") 10 | 11 | local gfs = require("gears.filesystem") 12 | local themes_path = gfs.get_themes_dir() 13 | local pallete = require("theme.gtk") 14 | 15 | local theme = {} 16 | 17 | theme.font = pallete.font_family 18 | theme.font_alt = pallete.font_family 19 | theme.font_size = pallete.font_size 20 | theme.icon_font = "Symbols Nerd Font Mono" 21 | 22 | theme.bg_normal = pallete.bg_color 23 | theme.bg_focus = pallete.selected_bg_color 24 | theme.bg_warning = pallete.warning_bg_color 25 | theme.bg_urgent = pallete.error_bg_color 26 | theme.bg_success = pallete.success_bg_color 27 | theme.bg_minimize = pallete.tooltip_bg_color 28 | 29 | theme.accent = pallete.selected_bg_color 30 | 31 | theme.module_bg = pallete.button_bg_color 32 | theme.module_bg_focused = pallete.selected_bg_color 33 | 34 | theme.bg_systray = pallete.bg_color 35 | theme.systray_icon_spacing = dpi(4) 36 | 37 | theme.fg_normal = pallete.fg_color 38 | theme.fg_focus = pallete.selected_fg_color 39 | theme.fg_warning = pallete.warning_fg_color 40 | theme.fg_urgent = pallete.error_fg_color 41 | theme.fg_success = pallete.success_fg_color 42 | theme.fg_minimize = pallete.tooltip_fg_color 43 | 44 | -- theme.useless_gap = dpi(4) 45 | theme.gap_single_client = true 46 | theme.border_width = dpi(2) 47 | theme.border_color_normal = pallete.wm_border_unfocused_color 48 | theme.border_color_active = pallete.wm_border_focused_color 49 | theme.border_color_marked = theme.accent 50 | 51 | theme.taglist_bg_empty = "#00000000" 52 | theme.taglist_bg_occupied = pallete.button_bg_color 53 | theme.taglist_bg_urgent = "#00000000" 54 | theme.taglist_bg_focus = pallete.selected_bg_color 55 | theme.taglist_font = theme.font .. " " .. pallete.font_size 56 | theme.taglist_fg_focus = pallete.selected_fg_color 57 | theme.taglist_fg_occupied = pallete.osd_fg_color 58 | theme.taglist_fg_urgent = pallete.error_fg_color 59 | theme.taglist_fg_empty = pallete.button_fg_color 60 | theme.taglist_shape = function(cx, width, height) 61 | gears.shape.rounded_rect(cx, width, height, dpi(6)) 62 | end 63 | 64 | theme.tasklist_bg_normal = theme.module_bg 65 | theme.tasklist_bg_focus = theme.accent 66 | theme.tasklist_bg_urgent = theme.module_bg_focused 67 | theme.tasklist_fg_urgent = pallete.error_fg_color 68 | 69 | theme.menu_font = theme.font_alt .. " 9" 70 | theme.menu_height = dpi(20) 71 | theme.menu_width = dpi(100) 72 | theme.menu_border_color = pallete.wm_border_unfocused_color 73 | theme.menu_border_width = dpi(2) 74 | theme.menu_fg_focus = pallete.selected_fg_color 75 | theme.menu_bg_focus = pallete.selected_bg_color 76 | theme.menu_fg_normal = pallete.menubar_fg_color 77 | theme.menu_bg_normal = pallete.menubar_bg_color 78 | 79 | -- Variables set for theming notifications: 80 | -- notification_font 81 | -- notification_[bg|fg] 82 | -- notification_[width|height|margin] 83 | -- notification_[border_color|border_width|shape|opacity] 84 | theme.notification_bg = pallete.tooltip_bg_color 85 | theme.notification_fg = pallete.tooltip_fg_color 86 | theme.notification_width = dpi(360) 87 | theme.notification_margin = dpi(120) 88 | theme.notification_border_width = dpi(2) 89 | theme.notification_border_color = pallete.wm_border_unfocused_color 90 | theme.notification_border_shape = function(cx, w, h) 91 | gears.shape.rounded_rect(cx, w, h, dpi(12)) 92 | end 93 | theme.notification_icon_size = dpi(100) 94 | 95 | -- Variables set for theming the menu: 96 | -- menu_[bg|fg]_[normal|focus] 97 | -- menu_[border_color|border_width] 98 | 99 | -- You can add as many variables as 100 | -- you wish and access them by using 101 | -- beautiful.variable in your rc.lua 102 | --theme.bg_widget = "#cc0000" 103 | 104 | ---@diagnostic disable-next-line: param-type-mismatch 105 | theme.pfp = gfs.get_configuration_dir() .. "assets/pfp.gif" 106 | theme.cover_art = gfs.get_configuration_dir() .. "assets/albumart.jpg" 107 | 108 | local layouts = { 109 | layout_fairh = themes_path .. "default/layouts/fairhw.png", 110 | layout_fairv = themes_path .. "default/layouts/fairvw.png", 111 | layout_floating = themes_path .. "default/layouts/floatingw.png", 112 | layout_magnifier = themes_path .. "default/layouts/magnifierw.png", 113 | layout_max = themes_path .. "default/layouts/maxw.png", 114 | layout_fullscreen = themes_path .. "default/layouts/fullscreenw.png", 115 | layout_tilebottom = themes_path .. "default/layouts/tilebottomw.png", 116 | layout_tileleft = themes_path .. "default/layouts/tileleftw.png", 117 | layout_tile = themes_path .. "default/layouts/tilew.png", 118 | layout_tiletop = themes_path .. "default/layouts/tiletopw.png", 119 | layout_spiral = themes_path .. "default/layouts/spiralw.png", 120 | layout_dwindle = themes_path .. "default/layouts/dwindlew.png", 121 | layout_cornernw = themes_path .. "default/layouts/cornernww.png", 122 | layout_cornerne = themes_path .. "default/layouts/cornernew.png", 123 | layout_cornersw = themes_path .. "default/layouts/cornersww.png", 124 | layout_cornerse = themes_path .. "default/layouts/cornersew.png", 125 | } 126 | 127 | -- You can use your own layout icons like this: 128 | theme.layout_tile = gears.color.recolor_image(layouts.layout_tile, theme.accent) 129 | theme.layout_floating = gears.color.recolor_image(layouts.layout_floating, theme.accent) 130 | theme.layout_tiletop = gears.color.recolor_image(layouts.layout_tiletop, theme.accent) 131 | 132 | -- Generate Awesome icon: 133 | theme.awesome_icon = theme_assets.awesome_icon(theme.menu_height, theme.bg_focus, theme.fg_focus) 134 | 135 | -- Define the icon theme for application icons. If not set then the icons 136 | -- from /usr/share/icons and /usr/share/icons/hicolor will be used. 137 | theme.icon_theme = "Papirus-Dark" 138 | 139 | -- Set different colors for urgent notifications. 140 | rnotification.connect_signal("request::rules", function() 141 | rnotification.append_rule({ 142 | rule = { urgency = "critical" }, 143 | properties = { bg = pallete.error_bg_color, fg = pallete.error_fg_color }, 144 | }) 145 | end) 146 | 147 | return theme 148 | -------------------------------------------------------------------------------- /wallpaper.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | local dpi = require("beautiful").xresources.apply_dpi 5 | 6 | -- Slideshow 7 | ---@diagnostic disable-next-line: undefined-global 8 | screen.connect_signal("request::wallpaper", function(s) 9 | awful.wallpaper({ 10 | screen = s, 11 | widget = { 12 | { 13 | horizontal_fit_policy = "fit", 14 | vertical_fit_policy = "fit", 15 | image = gears.surface.crop_surface({ 16 | surface = gears.surface.load_uncached( 17 | gears.filesystem.get_random_file_from_dir( 18 | os.getenv("HOME") .. "/wallpapers/", 19 | { ".jpg", ".png", ".svg" }, 20 | true 21 | ) 22 | ), 23 | ratio = s.geometry.width / s.geometry.height, 24 | }), 25 | resize = true, 26 | widget = wibox.widget.imagebox, 27 | }, 28 | valign = "center", 29 | halign = "center", 30 | tiled = false, 31 | widget = wibox.container.tile, 32 | }, 33 | }) 34 | end) 35 | 36 | gears.timer({ 37 | timeout = 300, 38 | autostart = true, 39 | callback = function() 40 | ---@diagnostic disable-next-line: undefined-global 41 | for s in screen do 42 | s:emit_signal("request::wallpaper") 43 | end 44 | end, 45 | }) 46 | --------------------------------------------------------------------------------