├── .gitignore ├── .gitmodules ├── README.md ├── assets ├── default.svg ├── music.svg ├── pfp.jpg └── terminal.svg ├── backgrounds ├── abstract_1.jpg ├── abstract_2.jpg ├── abstract_3.jpg ├── abstract_4.jpg ├── abstract_5.png ├── abstract_6.png ├── abstract_7.jpg ├── abstract_8.png ├── abstract_9.png ├── bird_1.jpg ├── bird_2.png ├── blahaj.png ├── charizard.png ├── forest_1.jpg ├── gasstation.jpg ├── goose.jpg ├── macos_1.jpg ├── mountains_1.jpg ├── road_1.jpg ├── shade.png ├── window.jpg └── winterlake.jpg ├── components ├── bar │ ├── init.lua │ └── modules │ │ ├── battery.lua │ │ ├── clock.lua │ │ ├── launcher.lua │ │ ├── layout.lua │ │ ├── systray.lua │ │ ├── taglist.lua │ │ ├── tasklist.lua │ │ ├── volume.lua │ │ └── wifi.lua ├── init.lua ├── lock │ ├── init.lua │ └── modules │ │ ├── circle.lua │ │ └── clock.lua ├── osd │ ├── brightness.lua │ ├── init.lua │ └── volume.lua ├── sidebar │ ├── init.lua │ └── modules │ │ ├── header.lua │ │ ├── launcher.lua │ │ ├── media.lua │ │ ├── notifications.lua │ │ └── status.lua └── switcher │ └── init.lua ├── config.lua ├── daemons ├── battery.lua ├── brightness.lua ├── cpu.lua ├── init.lua ├── memory.lua ├── network.lua ├── playerctl.lua ├── uptime.lua └── volume.lua ├── helpers.lua ├── keys.lua ├── layouts ├── cornerne.png ├── cornernew.png ├── cornernw.png ├── cornernww.png ├── cornerse.png ├── cornersew.png ├── cornersw.png ├── cornersww.png ├── dwindle.png ├── dwindlew.png ├── fairh.png ├── fairhw.png ├── fairv.png ├── fairvw.png ├── floating.png ├── floatingw.png ├── fullscreen.png ├── fullscreenw.png ├── magnifier.png ├── magnifierw.png ├── max.png ├── maxw.png ├── spiral.png ├── spiralw.png ├── tile.png ├── tilebottom.png ├── tilebottomw.png ├── tileleft.png ├── tileleftw.png ├── tiletop.png ├── tiletopw.png └── tilew.png ├── liblua_pam.so ├── notifications.lua ├── preview.png ├── rc.lua ├── rules.lua ├── screenshot ├── signals ├── error.lua ├── init.lua ├── tags.lua └── titlebars.lua ├── themes.lua └── wibox ├── layout ├── init.lua └── overflow.lua └── widget └── hierarchy.lua /.gitignore: -------------------------------------------------------------------------------- 1 | macos/ 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rubato"] 2 | path = rubato 3 | url = https://github.com/andOrlando/rubato 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AwesomeWM Config 2 | ![Preview](./preview.png) 3 | 4 | #### Features 5 | - Tab Switcher (Alt+Tab) 6 | - Lock Screen (Shift+Alt+x) 7 | - Sidebar/App Launcher (Super+p) 8 | 9 | #### Requirements 10 | - [Material Design Iconic Font](https://zavoloklom.github.io/material-design-iconic-font/) 11 | - [Roboto Condensed Font](https://fonts.google.com/specimen/Roboto+Condensed) 12 | - [Rubato](https://github.com/andOrlando/rubato) (Replace the Rubato folder) 13 | - playerctl (Media) 14 | - xbacklight (Brightness) 15 | - NetworkManager (Wifi) 16 | - pamixer & pactl (Audio) 17 | - UPower (Battery) 18 | 19 | #### Post-Installation 20 | - Add your own background in `backgrounds/` and change in `themes.lua` 21 | - Change `assets/pfp.jpg` 22 | - Change `config.lua` based on your own system 23 | - Change the pinned apps for the launcher in `components/sidebar/modules/launcher.lua` 24 | 25 | #### Misc 26 | - Theme is Github Dark, you can change it in `themes.lua` 27 | -------------------------------------------------------------------------------- /assets/default.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/music.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/pfp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/assets/pfp.jpg -------------------------------------------------------------------------------- /assets/terminal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backgrounds/abstract_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_1.jpg -------------------------------------------------------------------------------- /backgrounds/abstract_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_2.jpg -------------------------------------------------------------------------------- /backgrounds/abstract_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_3.jpg -------------------------------------------------------------------------------- /backgrounds/abstract_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_4.jpg -------------------------------------------------------------------------------- /backgrounds/abstract_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_5.png -------------------------------------------------------------------------------- /backgrounds/abstract_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_6.png -------------------------------------------------------------------------------- /backgrounds/abstract_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_7.jpg -------------------------------------------------------------------------------- /backgrounds/abstract_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_8.png -------------------------------------------------------------------------------- /backgrounds/abstract_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/abstract_9.png -------------------------------------------------------------------------------- /backgrounds/bird_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/bird_1.jpg -------------------------------------------------------------------------------- /backgrounds/bird_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/bird_2.png -------------------------------------------------------------------------------- /backgrounds/blahaj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/blahaj.png -------------------------------------------------------------------------------- /backgrounds/charizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/charizard.png -------------------------------------------------------------------------------- /backgrounds/forest_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/forest_1.jpg -------------------------------------------------------------------------------- /backgrounds/gasstation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/gasstation.jpg -------------------------------------------------------------------------------- /backgrounds/goose.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/goose.jpg -------------------------------------------------------------------------------- /backgrounds/macos_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/macos_1.jpg -------------------------------------------------------------------------------- /backgrounds/mountains_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/mountains_1.jpg -------------------------------------------------------------------------------- /backgrounds/road_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/road_1.jpg -------------------------------------------------------------------------------- /backgrounds/shade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/shade.png -------------------------------------------------------------------------------- /backgrounds/window.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/window.jpg -------------------------------------------------------------------------------- /backgrounds/winterlake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/backgrounds/winterlake.jpg -------------------------------------------------------------------------------- /components/bar/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local helpers = require("helpers") 4 | local beautiful = require("beautiful") 5 | 6 | 7 | local function create_bar(s) 8 | s.bar = awful.wibar({ 9 | position = "left", 10 | width = beautiful.bar_width, 11 | screen = s, 12 | height = s.geometry.height 13 | }) 14 | 15 | s.bar.widget = helpers.add_bg0(wibox.widget({ 16 | layout = wibox.layout.align.vertical, 17 | { -- Top widgets 18 | -- Launcher, Taglist 19 | require("components.bar.modules.launcher")(), 20 | require("components.bar.modules.taglist")(s), 21 | spacing = beautiful.margin[1], 22 | layout = wibox.layout.fixed.vertical, 23 | }, 24 | { -- Middle widgets 25 | -- Tasklist 26 | require("components.bar.modules.tasklist")(s), 27 | spacing = beautiful.margin[1], 28 | layout = wibox.layout.fixed.vertical 29 | }, 30 | { -- Bottom widgets 31 | -- Systray, Wifi+Volume, Battery, Clock, Layout 32 | require("components.bar.modules.systray")(), 33 | require("components.bar.modules.volume")(), 34 | require("components.bar.modules.wifi")(), 35 | require("components.bar.modules.battery")(), 36 | require("components.bar.modules.clock")(), 37 | require("components.bar.modules.layout")(), 38 | spacing = beautiful.margin[1], 39 | layout = wibox.layout.fixed.vertical 40 | } 41 | })) 42 | end 43 | 44 | screen.connect_signal("request::desktop_decoration", function(s) 45 | create_bar(s) 46 | end) 47 | -------------------------------------------------------------------------------- /components/bar/modules/battery.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local beautiful = require("beautiful") 4 | local helpers = require("helpers") 5 | local naughty = require("naughty") 6 | 7 | local function update(progressbar, icon, tooltip, device) 8 | if device.percentage < 25 then 9 | progressbar.color = beautiful.red 10 | elseif device.percentage < 50 then 11 | progressbar.color = beautiful.orange 12 | elseif device.percentage < 75 then 13 | progressbar.color = beautiful.blue 14 | else 15 | progressbar.color = beautiful.green 16 | end 17 | progressbar.value = device.percentage 18 | if device.state == 1 or device.state == 4 then 19 | icon.visible = true 20 | else 21 | icon.visible = false 22 | end 23 | tooltip.markup = "Battery: " .. math.floor(device.percentage) .. "%" 24 | end 25 | 26 | local function create_widget() 27 | local progressbar = wibox.widget({ 28 | max_value = 100, 29 | value = 100, 30 | border_width = 0, 31 | color = beautiful.green, 32 | background_color = beautiful.bg2, 33 | widget = wibox.widget.progressbar, 34 | shape = helpers.rrect() 35 | }) 36 | 37 | local icon = wibox.widget({ 38 | font = beautiful.font_icon, 39 | markup = "", 40 | valign = "center", 41 | halign = "center", 42 | widget = wibox.widget.textbox 43 | }) 44 | 45 | local stack = wibox.widget({ 46 | { 47 | progressbar, 48 | forced_height = beautiful.dpi(48), 49 | direction = "east", 50 | layout = wibox.container.rotate 51 | }, 52 | { 53 | icon, 54 | fg = beautiful.bg0, 55 | layout = wibox.container.background, 56 | }, 57 | layout = wibox.layout.stack 58 | }) 59 | 60 | local widget = helpers.add_margin(stack, beautiful.margin[1], beautiful.margin[0]) 61 | 62 | local tooltip = helpers.add_tooltip(widget, "Unknown") 63 | 64 | awesome.connect_signal("battery::update", function(device) 65 | update(progressbar, icon, tooltip, device) 66 | end) 67 | 68 | return widget 69 | end 70 | return create_widget 71 | -------------------------------------------------------------------------------- /components/bar/modules/clock.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | local beautiful = require("beautiful") 5 | local helpers = require("helpers") 6 | 7 | local function create_widget() 8 | local clock = wibox.widget({ 9 | format = '%I\n%M', 10 | valign = "center", 11 | halign = "center", 12 | widget = wibox.widget.textclock, 13 | }) 14 | 15 | local tooltip = helpers.add_tooltip(clock, "Date: "..os.date("%A, %B %e")) 16 | return helpers.add_margin(clock) 17 | end 18 | return create_widget 19 | -------------------------------------------------------------------------------- /components/bar/modules/launcher.lua: -------------------------------------------------------------------------------- 1 | local wibox = require("wibox") 2 | local awful = require("awful") 3 | local beautiful = require("beautiful") 4 | local helpers = require("helpers") 5 | 6 | local function create_widget() 7 | local image = wibox.widget { 8 | image = beautiful.icon_awesome, 9 | resize = true, 10 | widget = wibox.widget.imagebox, 11 | } 12 | 13 | local widget = helpers.add_margin( 14 | helpers.add_click(helpers.add_margin(image, beautiful.margin[1], beautiful.margin[1])), beautiful.margin[0], 15 | beautiful.margin[0]) 16 | 17 | local tooltip = helpers.add_tooltip(widget, "Launch") 18 | 19 | widget:buttons({ 20 | awful.button({}, 1, function() 21 | awesome.emit_signal("launcher::toggle") 22 | end) 23 | }) 24 | 25 | return widget 26 | end 27 | 28 | return create_widget 29 | -------------------------------------------------------------------------------- /components/bar/modules/layout.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local helpers = require("helpers") 3 | 4 | local function create_widget(s) 5 | local layout_box = awful.widget.layoutbox({ 6 | screen = s, 7 | -- Add buttons, allowing you to change the layout 8 | buttons = { 9 | awful.button({}, 1, function() 10 | awful.layout.inc(1) 11 | end), 12 | awful.button({}, 3, function() 13 | awful.layout.inc(-1) 14 | end), 15 | awful.button({}, 4, function() 16 | awful.layout.inc(1) 17 | end), 18 | awful.button({}, 5, function() 19 | awful.layout.inc(-1) 20 | end), 21 | }, 22 | }) 23 | 24 | local widget = helpers.add_margin(helpers.add_click(helpers.add_margin(layout_box))) 25 | return widget 26 | end 27 | return create_widget 28 | -------------------------------------------------------------------------------- /components/bar/modules/systray.lua: -------------------------------------------------------------------------------- 1 | local beautiful = require("beautiful") 2 | local wibox = require("wibox") 3 | local helpers = require("helpers") 4 | 5 | local function create_widget() 6 | local systray = wibox.widget({ 7 | widget = wibox.widget.systray(), 8 | horizontal = false, 9 | }) 10 | 11 | return helpers.add_margin(systray, beautiful.margin[2], beautiful.margin[2]) 12 | end 13 | 14 | return create_widget 15 | -------------------------------------------------------------------------------- /components/bar/modules/taglist.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | local beautiful = require("beautiful") 5 | local helpers = require("helpers") 6 | 7 | local taglist_buttons = gears.table.join(awful.button({}, 1, function(t) 8 | t:view_only() 9 | end), 10 | awful.button({}, 4, function(t) 11 | awful.tag.viewnext(t.screen) 12 | end), 13 | awful.button({}, 5, function(t) 14 | awful.tag.viewprev(t.screen) 15 | end)) 16 | 17 | local function update_tag(self, tag, index, tags) 18 | local background = self:get_children_by_id("background_role")[1] 19 | local text = self:get_children_by_id("icon")[1] 20 | 21 | local urgent = false 22 | for _, client in ipairs(tag:clients()) do 23 | if client.urgent then 24 | urgent = true 25 | break 26 | end 27 | end 28 | 29 | if urgent then 30 | background.fg = beautiful.red 31 | text.text = "" 32 | elseif tag.selected and #tag:clients() > 0 then 33 | background.fg = beautiful.blue 34 | text.text = "" 35 | elseif tag.selected then 36 | background.fg = beautiful.blue 37 | text.text = "" 38 | elseif #tag:clients() > 0 then 39 | background.fg = beautiful.green 40 | text.text = "" 41 | else 42 | background.fg = beautiful.bg2 43 | text.text = "" 44 | end 45 | end 46 | 47 | local function create_widget(s) 48 | local taglist = awful.widget.taglist({ 49 | screen = s, 50 | filter = awful.widget.taglist.filter.all, 51 | layout = { 52 | layout = wibox.layout.fixed.vertical, 53 | spacing = beautiful.margin[1], 54 | }, 55 | style = { 56 | shape = helpers.rrect() 57 | }, 58 | widget_template = { 59 | { 60 | widget = wibox.widget.textbox, 61 | font = beautiful.font_icon, 62 | valign = "center", 63 | halign = "center", 64 | id = "icon" 65 | }, 66 | layout = wibox.container.background, 67 | id = "background_role", 68 | create_callback = update_tag, 69 | update_callback = update_tag 70 | }, 71 | buttons = taglist_buttons 72 | }) 73 | 74 | local widget = helpers.add_margin(taglist); 75 | return widget 76 | end 77 | 78 | return create_widget 79 | -------------------------------------------------------------------------------- /components/bar/modules/tasklist.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | local beautiful = require("beautiful") 5 | local helpers = require("helpers") 6 | 7 | local tasklist_buttons = gears.table.join( 8 | awful.button({}, 1, function(c) 9 | c:activate({ context = "tasklist", action = "toggle_minimization" }) 10 | end)) 11 | 12 | local function update(self, client, index, clients) 13 | local background = self:get_children_by_id("background")[1] 14 | local icon = self:get_children_by_id("icon")[1] 15 | local separator = self:get_children_by_id("separator")[1] 16 | 17 | icon.image = client.icon or beautiful.icon_default 18 | if client.active then 19 | background.bg = beautiful.bg2 20 | separator.visible = true 21 | else 22 | background.bg = beautiful.bg1 23 | separator.visible = false 24 | end 25 | end 26 | 27 | local function init(self, client, index, clients) 28 | local background = self:get_children_by_id("background")[1] 29 | local tooltip = helpers.add_tooltip(background, "" .. (client.class:gsub("^%l", string.upper)) .. "") 30 | update(self, client, index, clients) 31 | end 32 | 33 | local function create_widget(s) 34 | local tasklist = awful.widget.tasklist({ 35 | screen = s, 36 | filter = awful.widget.tasklist.filter.currenttags, 37 | spacing = beautiful.margin[0], 38 | style = { 39 | shape = helpers.rrect() 40 | }, 41 | layout = { 42 | spacing_widget = { 43 | { 44 | forced_width = beautiful.dpi(20), 45 | forced_height = beautiful.dpi(4), 46 | thickness = beautiful.dpi(2), 47 | color = beautiful.fg2, 48 | widget = wibox.widget.separator 49 | }, 50 | valign = "center", 51 | halign = "center", 52 | widget = wibox.container.place, 53 | }, 54 | spacing = beautiful.margin[1], 55 | layout = wibox.layout.fixed.vertical, 56 | }, 57 | widget_template = { 58 | { 59 | { 60 | { 61 | widget = wibox.widget.imagebox, 62 | id = "icon", 63 | }, 64 | layout = wibox.container.margin, 65 | margins = beautiful.margin[0], 66 | }, 67 | layout = wibox.container.background, 68 | shape = helpers.rrect(), 69 | id = "background", 70 | }, 71 | { 72 | { 73 | { 74 | widget = wibox.widget.separator, 75 | id = "separator", 76 | shape = helpers.rrect(), 77 | thickness = beautiful.dpi(2), 78 | forced_width = beautiful.dpi(2), 79 | forced_height = beautiful.dpi(16), 80 | color = beautiful.blue, 81 | }, 82 | margins = beautiful.dpi(1), 83 | layout = wibox.container.margin 84 | }, 85 | valign = "center", 86 | halign = "left", 87 | layout = wibox.container.place 88 | }, 89 | layout = wibox.layout.stack, 90 | create_callback = init, 91 | update_callback = update 92 | }, 93 | buttons = tasklist_buttons 94 | }) 95 | 96 | local widget = helpers.add_margin(tasklist) 97 | return widget 98 | end 99 | 100 | return create_widget 101 | -------------------------------------------------------------------------------- /components/bar/modules/volume.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local helpers = require("helpers") 4 | local beautiful = require("beautiful") 5 | local config = require("config") 6 | 7 | local function update(icon, background, tooltip, mute, vol) 8 | if mute then 9 | tooltip.markup = "Muted" 10 | icon.markup = "" 11 | background.fg = beautiful.red 12 | return 13 | end 14 | 15 | if vol == 0 then 16 | icon.markup = "" 17 | background.fg = beautiful.red 18 | elseif vol < 30 then 19 | icon.markup = "" 20 | background.fg = beautiful.purple 21 | elseif vol < 60 then 22 | icon.markup = "" 23 | background.fg = beautiful.blue 24 | else 25 | icon.markup = "" 26 | background.fg = beautiful.green 27 | end 28 | 29 | tooltip.markup = "Volume: " .. tostring(vol) .. "%" 30 | end 31 | 32 | local function create_widget() 33 | local icon = wibox.widget({ 34 | font = beautiful.font_icon, 35 | markup = "", 36 | valign = "center", 37 | halign = "center", 38 | widget = wibox.widget.textbox 39 | }) 40 | 41 | local background = helpers.add_bg0(icon) 42 | 43 | background:buttons({ 44 | awful.button({}, 1, function() 45 | awful.spawn.with_shell(config.apps.volume_manager) 46 | end), 47 | awful.button({}, 2, function() 48 | awesome.emit_signal("volume::mute") 49 | end), 50 | awful.button({}, 4, function() 51 | awesome.emit_signal("volume::increase", 5) 52 | end), 53 | awful.button({}, 5, function() 54 | awesome.emit_signal("volume::decrease", 5) 55 | end) 56 | }) 57 | 58 | local widget = helpers.add_margin(background, beautiful.margin[1], 0) 59 | 60 | local tooltip = helpers.add_tooltip(widget, "Volume") 61 | 62 | awesome.connect_signal("volume::update", function(mute, vol) 63 | update(icon, background, tooltip, mute, vol) 64 | end) 65 | 66 | return widget 67 | end 68 | 69 | return create_widget 70 | -------------------------------------------------------------------------------- /components/bar/modules/wifi.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local helpers = require("helpers") 4 | local beautiful = require("beautiful") 5 | local config = require("config") 6 | 7 | local function update(icon, background, tooltip, id, eth_state, wifi_state) 8 | if id and (eth_state == "ACTIVATED" or wifi_state == "ACTIVATED") then 9 | icon.markup = "" 10 | background.fg = beautiful.blue 11 | tooltip.markup = "Wifi: " .. id 12 | return 13 | end 14 | icon.markup = "" 15 | background.fg = beautiful.red 16 | tooltip.markup = "Disconnected" 17 | end 18 | 19 | local function create_widget() 20 | local icon = wibox.widget({ 21 | font = beautiful.font_icon, 22 | markup = "", 23 | valign = "center", 24 | halign = "center", 25 | widget = wibox.widget.textbox 26 | }) 27 | 28 | local background = helpers.add_bg0(icon) 29 | 30 | background:buttons({ 31 | awful.button({}, 1, function() 32 | awful.spawn.with_shell(config.apps.network_manager) 33 | end) 34 | }) 35 | 36 | local widget = helpers.add_margin(background, beautiful.margin[1], 0) 37 | 38 | local tooltip = helpers.add_tooltip(widget, "Unknown") 39 | 40 | awesome.connect_signal("network::update", function(id, eth_state, wifi_state) 41 | update(icon, background, tooltip, id, eth_state, wifi_state) 42 | end) 43 | 44 | return widget 45 | end 46 | return create_widget 47 | -------------------------------------------------------------------------------- /components/init.lua: -------------------------------------------------------------------------------- 1 | -- Initialize Components 2 | 3 | require("components.bar") 4 | require("components.switcher") 5 | require("components.sidebar") 6 | require("components.lock") 7 | require("components.osd") 8 | -------------------------------------------------------------------------------- /components/lock/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | local beautiful = require("beautiful") 5 | local helpers = require("helpers") 6 | local pam = require("liblua_pam") 7 | 8 | local clock = require("components.lock.modules.clock") 9 | local circle = require("components.lock.modules.circle") 10 | 11 | local M = {} 12 | M.visible = false 13 | 14 | function M.auth() 15 | return pam.auth_current_user(M.input) 16 | end 17 | 18 | function M.create_wiboxes() 19 | for s in screen do 20 | M.wiboxes[s] = wibox({ 21 | widget = M.widget, 22 | visible = true, 23 | ontop = true, 24 | screen = s, 25 | width = s.geometry.width, 26 | height = s.geometry.height, 27 | x = s.geometry.x, 28 | y = s.geometry.y 29 | }) 30 | end 31 | end 32 | 33 | function M.toggle() 34 | M.visible = not M.visible 35 | if M.visible then 36 | M.keygrabber:start() 37 | M.create_wiboxes() 38 | else 39 | M.input = "" 40 | for _, widget in pairs(M.wiboxes) do 41 | widget.visible = false 42 | end 43 | M.keygrabber:stop() 44 | end 45 | end 46 | 47 | function M.stop() 48 | circle.reset() 49 | M.visible = false 50 | M.input = "" 51 | for _, widget in pairs(M.wiboxes) do 52 | widget.visible = false 53 | end 54 | M.keygrabber:stop() 55 | end 56 | 57 | function M.new() 58 | M.clock = clock.new() 59 | M.circle = circle.new() 60 | 61 | M.background = wibox.widget({ 62 | halign = "center", 63 | valign = "center", 64 | vertical_fit_policy = true, 65 | horizontal_fit_policy = true, 66 | resize = true, 67 | opacity = 0.5, 68 | clip_shape = helpers.rrect(), 69 | widget = wibox.widget.imagebox 70 | }) 71 | 72 | M.widget = wibox.widget({ 73 | M.background, 74 | { 75 | M.clock, 76 | valign = "center", 77 | halign = "center", 78 | layout = wibox.container.place 79 | }, 80 | { 81 | M.circle, 82 | valign = "bottom", 83 | halign = "left", 84 | layout = wibox.container.place 85 | }, 86 | layout = wibox.layout.stack 87 | }) 88 | 89 | M.input = "" 90 | M.wiboxes = {} 91 | 92 | M.keygrabber = awful.keygrabber({ 93 | stop_event = 'release', 94 | mask_event_callback = true, 95 | keybindings = {awful.key { 96 | modifiers = {'Mod1', 'Mod4', 'Shift', 'Control'}, 97 | key = 'Return', 98 | on_press = function(_) 99 | M.input = M.input 100 | circle.random() 101 | end 102 | }}, 103 | keypressed_callback = function(_, _, key, event) 104 | if event == "release" then 105 | return 106 | end 107 | if key == 'Escape' then 108 | M.input = "" 109 | circle.random() 110 | return 111 | end 112 | 113 | if key == "BackSpace" then 114 | if #M.input > 0 then 115 | circle.random() 116 | end 117 | M.input = M.input:sub(1, -2) 118 | end 119 | 120 | if #key == 1 then 121 | circle.random() 122 | if not M.input then 123 | M.input = key 124 | else 125 | M.input = M.input .. key 126 | end 127 | end 128 | end, 129 | keyreleased_callback = function(_, _, key, _) 130 | if key == "Return" then 131 | if M.auth() then 132 | M.stop() 133 | else 134 | M.input = "" 135 | end 136 | end 137 | end 138 | }) 139 | 140 | M.background.image = gears.surface.load_uncached(beautiful.wallpaper) 141 | 142 | screen.connect_signal("list", function() 143 | if M.visible then 144 | for s, wibox in pairs(M.wiboxes) do 145 | wibox.visible = false 146 | end 147 | M.wiboxes = {} 148 | M.create_wiboxes() 149 | end 150 | end) 151 | 152 | awesome.connect_signal("lockscreen::toggle", function() 153 | M.toggle() 154 | end) 155 | 156 | return M 157 | end 158 | 159 | return M.new() 160 | -------------------------------------------------------------------------------- /components/lock/modules/circle.lua: -------------------------------------------------------------------------------- 1 | local wibox = require("wibox") 2 | local beautiful = require("beautiful") 3 | local helpers = require("helpers") 4 | local naughty = require("naughty") 5 | 6 | local M = {} 7 | 8 | function M.random() 9 | local val = math.random(0, 360) 10 | M.arcchart.start_angle = val 11 | M.arcchart.value = 30 12 | end 13 | 14 | function M.reset() 15 | M.arcchart.value = nil 16 | M.background.fg = beautiful.red 17 | end 18 | 19 | function M.validate() 20 | M.background.fg = beautiful.green 21 | end 22 | 23 | function M.new() 24 | math.randomseed(os.time()) 25 | M.icon = wibox.widget({ 26 | markup = "", 27 | valign = "center", 28 | halign = "center", 29 | font = beautiful.font_icon_large, 30 | widget = wibox.widget.textbox 31 | }) 32 | 33 | M.background = wibox.widget({ 34 | M.icon, 35 | fg = beautiful.red, 36 | bg = beautiful.bg1, 37 | shape = helpers.circle(), 38 | layout = wibox.container.background 39 | }) 40 | 41 | M.arcchart = wibox.widget({ 42 | M.background, 43 | colors = { beautiful.blue }, 44 | thickness = beautiful.dpi(8), 45 | rounded_edge = true, 46 | min_value = 0, 47 | max_value = 100, 48 | start_angle = 0, 49 | forced_width = beautiful.icon_size[4], 50 | forced_height = beautiful.icon_size[4], 51 | layout = wibox.container.arcchart 52 | }) 53 | 54 | M.widget = helpers.add_margin(helpers.add_bg0(helpers.add_margin(M.arcchart, beautiful.margin[1], beautiful.margin[1])), beautiful.margin[4], 55 | beautiful.margin[4]) 56 | 57 | return M.widget 58 | end 59 | 60 | return M 61 | -------------------------------------------------------------------------------- /components/lock/modules/clock.lua: -------------------------------------------------------------------------------- 1 | local wibox = require("wibox") 2 | local beautiful = require("beautiful") 3 | 4 | local M = {} 5 | 6 | function M.new() 7 | M.date = wibox.widget({ 8 | valign = "center", 9 | halign = "center", 10 | font = beautiful.font_medium, 11 | format = "%A, %B %e", 12 | widget = wibox.widget.textclock 13 | }) 14 | 15 | M.time = wibox.widget({ 16 | valign = "center", 17 | halign = "center", 18 | font = beautiful.font_large, 19 | format = "%l:%M %p", 20 | widget = wibox.widget.textclock 21 | }) 22 | 23 | M.widget = wibox.widget({ 24 | M.time, 25 | M.date, 26 | layout = wibox.layout.fixed.vertical 27 | }) 28 | 29 | return M.widget 30 | end 31 | 32 | return M 33 | -------------------------------------------------------------------------------- /components/osd/brightness.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | local beautiful = require("beautiful") 5 | local helpers = require("helpers") 6 | 7 | local M = {} 8 | 9 | M.timeout = 2 10 | 11 | function M.update_widget(value) 12 | M.slider.value = value 13 | M.text.markup = tostring(value) .. "%" 14 | end 15 | 16 | function M.display_widget() 17 | M.wibox.screen = awful.screen.focused() 18 | awful.placement.bottom(M.wibox, { 19 | margins = { 20 | bottom = 2 * beautiful.useless_gap, 21 | } 22 | }) 23 | M.wibox.visible = true 24 | M.timer:again() 25 | end 26 | 27 | function M.hide_widget() 28 | M.wibox.visible = false 29 | M.timer:stop() 30 | end 31 | 32 | function M.new() 33 | M.icon = wibox.widget({ 34 | widget = wibox.widget.textbox, 35 | font = beautiful.font_icon_large, 36 | markup = "", 37 | halign = "center", 38 | valign = "center", 39 | forced_width = beautiful.icon_size[2], 40 | forced_height = beautiful.icon_size[2] 41 | }) 42 | 43 | M.background = helpers.add_bg0(M.icon) 44 | 45 | M.text = wibox.widget({ 46 | widget = wibox.widget.textbox, 47 | markup = "0%", 48 | halign = "center", 49 | valign = "center", 50 | forced_width = beautiful.icon_size[2], 51 | forced_height = beautiful.icon_size[2] 52 | }) 53 | 54 | M.slider = wibox.widget({ 55 | background_color = beautiful.bg1, 56 | color = beautiful.blue, 57 | thickness = beautiful.dpi(8), 58 | shape = helpers.rrect(), 59 | bar_shape = helpers.rrect(), 60 | max_value = 100, 61 | start_angle = 0, 62 | forced_width = beautiful.dpi(200), 63 | forced_height = beautiful.dpi(20), 64 | widget = wibox.widget.progressbar 65 | }) 66 | 67 | M.widget = helpers.add_bg0(helpers.add_margin(wibox.widget({ 68 | M.background, 69 | helpers.add_margin(M.slider, beautiful.margin[2], beautiful.margin[2]), 70 | M.text, 71 | layout = wibox.layout.fixed.horizontal 72 | }), beautiful.margin[2], beautiful.margin[2])) 73 | 74 | M.wibox = awful.popup({ 75 | width = beautiful.dpi(400), 76 | height = beautiful.dpi(400), 77 | widget = M.widget, 78 | ontop = true, 79 | visible = false, 80 | border_color = beautiful.border_color_active, 81 | border_width = beautiful.border_width, 82 | shape = helpers.rrect(), 83 | }) 84 | 85 | M.timer = gears.timer({ 86 | timeout = M.timeout, 87 | autostart = false, 88 | call_now = false, 89 | callback = function() 90 | M.wibox.visible = false 91 | end, 92 | single_shot = true 93 | }) 94 | 95 | awesome.connect_signal("brightness::update", function(value) 96 | M.update_widget(value) 97 | end) 98 | 99 | awesome.connect_signal("brightness::osd", function() 100 | M.display_widget() 101 | end) 102 | 103 | awesome.connect_signal("volume::osd", function() 104 | M.hide_widget() 105 | end) 106 | 107 | return M.wibox 108 | end 109 | 110 | return M 111 | -------------------------------------------------------------------------------- /components/osd/init.lua: -------------------------------------------------------------------------------- 1 | require("components.osd.brightness").new() 2 | require("components.osd.volume").new() 3 | -------------------------------------------------------------------------------- /components/osd/volume.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | local beautiful = require("beautiful") 5 | local helpers = require("helpers") 6 | 7 | local M = {} 8 | 9 | M.timeout = 2 10 | 11 | function M.update_widget(mute, vol) 12 | if mute then 13 | M.icon.markup = "" 14 | M.background.fg = beautiful.red 15 | M.slider.color = beautiful.red 16 | return 17 | end 18 | 19 | if vol == 0 then 20 | M.icon.markup = "" 21 | M.background.fg = beautiful.red 22 | M.slider.color = beautiful.red 23 | elseif vol < 30 then 24 | M.icon.markup = "" 25 | M.background.fg = beautiful.purple 26 | M.slider.color = beautiful.purple 27 | elseif vol < 60 then 28 | M.icon.markup = "" 29 | M.background.fg = beautiful.blue 30 | M.slider.color = beautiful.blue 31 | else 32 | M.icon.markup = "" 33 | M.background.fg = beautiful.green 34 | M.slider.color = beautiful.green 35 | end 36 | M.text.markup = tostring(vol) .. "%" 37 | M.slider.value = vol 38 | end 39 | 40 | function M.display_widget() 41 | M.wibox.screen = awful.screen.focused() 42 | awful.placement.bottom(M.wibox, { 43 | margins = { 44 | bottom = 2 * beautiful.useless_gap, 45 | } 46 | }) 47 | M.wibox.visible = true 48 | M.timer:again() 49 | end 50 | 51 | function M.hide_widget() 52 | M.wibox.visible = false 53 | M.timer:stop() 54 | end 55 | 56 | function M.new() 57 | M.icon = wibox.widget({ 58 | widget = wibox.widget.textbox, 59 | font = beautiful.font_icon_large, 60 | markup = "", 61 | halign = "center", 62 | valign = "center", 63 | forced_width = beautiful.icon_size[2], 64 | forced_height = beautiful.icon_size[2] 65 | }) 66 | 67 | M.background = helpers.add_bg0(M.icon) 68 | 69 | M.text = wibox.widget({ 70 | widget = wibox.widget.textbox, 71 | markup = "0%", 72 | halign = "center", 73 | valign = "center", 74 | forced_width = beautiful.icon_size[2], 75 | forced_height = beautiful.icon_size[2] 76 | }) 77 | 78 | M.slider = wibox.widget({ 79 | background_color = beautiful.bg1, 80 | color = beautiful.blue, 81 | thickness = beautiful.dpi(8), 82 | shape = helpers.rrect(), 83 | bar_shape = helpers.rrect(), 84 | max_value = 100, 85 | start_angle = 0, 86 | forced_width = beautiful.dpi(200), 87 | forced_height = beautiful.dpi(20), 88 | widget = wibox.widget.progressbar 89 | }) 90 | 91 | M.widget = helpers.add_bg0(helpers.add_margin(wibox.widget({ 92 | M.background, 93 | helpers.add_margin(M.slider, beautiful.margin[2], beautiful.margin[2]), 94 | M.text, 95 | layout = wibox.layout.fixed.horizontal 96 | }), beautiful.margin[2], beautiful.margin[2])) 97 | 98 | M.wibox = awful.popup({ 99 | width = beautiful.dpi(400), 100 | height = beautiful.dpi(400), 101 | widget = M.widget, 102 | ontop = true, 103 | visible = false, 104 | border_color = beautiful.border_color_active, 105 | border_width = beautiful.border_width, 106 | shape = helpers.rrect(), 107 | }) 108 | 109 | M.timer = gears.timer({ 110 | timeout = M.timeout, 111 | autostart = false, 112 | call_now = false, 113 | callback = function() 114 | M.wibox.visible = false 115 | end, 116 | single_shot = true 117 | }) 118 | 119 | awesome.connect_signal("volume::update", function(mute, value) 120 | M.update_widget(mute, value) 121 | end) 122 | 123 | awesome.connect_signal("volume::osd", function() 124 | M.display_widget() 125 | end) 126 | 127 | awesome.connect_signal("brightness::osd", function() 128 | M.hide_widget() 129 | end) 130 | 131 | return M.wibox 132 | end 133 | 134 | return M 135 | -------------------------------------------------------------------------------- /components/sidebar/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local beautiful = require("beautiful") 4 | local helpers = require("helpers") 5 | 6 | local header = require("components.sidebar.modules.header") 7 | local launcher = require("components.sidebar.modules.launcher") 8 | local media = require("components.sidebar.modules.media") 9 | local status = require("components.sidebar.modules.status") 10 | local notifications = require("components.sidebar.modules.notifications") 11 | 12 | local M = {} 13 | 14 | function M.toggle() 15 | M.wibox.visible = not M.wibox.visible 16 | if M.wibox.visible then 17 | M.wibox.height = awful.screen.focused().geometry.height - 4 * beautiful.useless_gap 18 | M.wibox.screen = awful.screen.focused() 19 | awful.placement.top_left(M.wibox, { 20 | margins = { 21 | top = 2 * beautiful.useless_gap, 22 | left = beautiful.bar_width + 2 * beautiful.useless_gap 23 | } 24 | }) 25 | launcher.input = "" 26 | launcher.prompt.markup = "|" 27 | launcher.get_apps() 28 | launcher.update_apps() 29 | awful.spawn.easy_async_with_shell("echo $USER", function(stdout) 30 | stdout = stdout:gsub("[\n\r]", ""):gsub("^%l", string.upper) 31 | header.name.markup = "" .. stdout .. "" 32 | end) 33 | launcher.keygrabber:start() 34 | else 35 | launcher.keygrabber:stop() 36 | end 37 | end 38 | 39 | function M.stop() 40 | M.wibox.visible = false 41 | launcher.keygrabber:stop() 42 | end 43 | 44 | function M.new() 45 | M.header = header.new() 46 | M.launcher = launcher.new() 47 | M.media = media.new() 48 | M.status = status.new() 49 | M.notifications = notifications.new() 50 | 51 | M.widget = helpers.add_margin(wibox.widget({ 52 | M.header, 53 | M.launcher, 54 | M.media, 55 | M.status, 56 | M.notifications, 57 | fill_space = true, 58 | spacing = beautiful.margin[0], 59 | layout = wibox.layout.fixed.vertical 60 | }), beautiful.margin[2], beautiful.margin[2]) 61 | 62 | M.wibox = wibox({ 63 | widget = M.widget, 64 | shape = helpers.rrect(), 65 | ontop = true, 66 | visible = false, 67 | width = beautiful.dpi(600), 68 | height = awful.screen.focused().geometry.height - 4 * beautiful.useless_gap, 69 | border_color = beautiful.border_color_active, 70 | border_width = beautiful.border_width, 71 | }) 72 | 73 | awesome.connect_signal("launcher::toggle", function() 74 | M.toggle() 75 | end) 76 | 77 | awesome.connect_signal("launcher::stop", function() 78 | M.stop() 79 | end) 80 | end 81 | 82 | M.new() 83 | -------------------------------------------------------------------------------- /components/sidebar/modules/header.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local gears = require("gears") 4 | local beautiful = require("beautiful") 5 | local helpers = require("helpers") 6 | local M = {} 7 | 8 | M.buttons = { 9 | { 10 | icon = "", 11 | cmd = "systemctl poweroff", 12 | text = "Shutdown", 13 | color = beautiful.red, 14 | }, 15 | { 16 | icon = "", 17 | cmd = "systemctl reboot", 18 | text = "Restart", 19 | color = beautiful.orange, 20 | }, 21 | { 22 | icon = "", 23 | cmd = "awesome-client 'awesome.emit_signal(\"lockscreen::toggle\")'", 24 | text = "Lock", 25 | color = beautiful.green, 26 | }, 27 | { 28 | icon = "", 29 | cmd = "awesome-client 'awesome.quit()'", 30 | text = "Exit", 31 | color = beautiful.blue, 32 | } 33 | } 34 | 35 | function M.create_button(icon, cmd, text, color) 36 | local icon_widget = wibox.widget({ 37 | markup = icon, 38 | font = beautiful.font_icon, 39 | forced_width = beautiful.icon_size[2], 40 | forced_height = beautiful.icon_size[2], 41 | valign = "center", 42 | halign = "center", 43 | widget = wibox.widget.textbox 44 | }) 45 | 46 | local widget = helpers.add_bg1(helpers.add_margin(icon_widget)) 47 | widget.fg = color 48 | widget:buttons({ 49 | awful.button({}, 1, function() 50 | awesome.emit_signal("launcher::stop") 51 | awful.spawn.with_shell(cmd) 52 | end) 53 | }) 54 | 55 | widget:connect_signal("mouse::enter", function() 56 | widget.bg = beautiful.bg2 57 | end) 58 | 59 | widget:connect_signal("mouse::leave", function() 60 | widget.bg = beautiful.bg1 61 | end) 62 | 63 | return widget 64 | end 65 | 66 | function M.new() 67 | M.pfp = wibox.widget({ 68 | image = gears.filesystem.get_configuration_dir() .. "assets/pfp.jpg", 69 | halign = "center", 70 | valign = "center", 71 | forced_height = beautiful.icon_size[2], 72 | forced_width = beautiful.icon_size[2], 73 | resize = true, 74 | clip_shape = gears.shape.circle, 75 | widget = wibox.widget.imagebox 76 | }) 77 | 78 | M.uptime_text = wibox.widget({ 79 | valign = "center", 80 | halign = "left", 81 | widget = wibox.widget.textbox 82 | }) 83 | 84 | M.uptime = helpers.add_bg0(M.uptime_text) 85 | M.uptime.fg = beautiful.bg2 86 | 87 | M.name = wibox.widget({ 88 | markup = "", 89 | valign = "center", 90 | halign = "left", 91 | widget = wibox.widget.textbox 92 | }) 93 | 94 | M.button_group = wibox.widget({ 95 | spacing = beautiful.margin[2], 96 | layout = wibox.layout.flex.horizontal 97 | }) 98 | 99 | for _, button in ipairs(M.buttons) do 100 | M.button_group:add(M.create_button(button.icon, button.cmd, button.text, button.color)) 101 | end 102 | 103 | M.widget = helpers.add_margin(wibox.widget({ 104 | { 105 | M.pfp, 106 | { 107 | M.name, 108 | M.uptime, 109 | layout = wibox.layout.fixed.vertical 110 | }, 111 | spacing = beautiful.margin[1], 112 | forced_height = beautiful.icon_size[2], 113 | layout = wibox.layout.fixed.horizontal 114 | }, 115 | nil, 116 | M.button_group, 117 | expand = "none", 118 | layout = wibox.layout.align.horizontal 119 | })) 120 | 121 | awesome.connect_signal("uptime::update", function(stdout) 122 | M.uptime_text.markup = stdout 123 | end) 124 | 125 | return M.widget 126 | end 127 | 128 | return M 129 | -------------------------------------------------------------------------------- /components/sidebar/modules/launcher.lua: -------------------------------------------------------------------------------- 1 | local Gio = require("lgi").Gio 2 | local awful = require("awful") 3 | local wibox = require("wibox") 4 | local beautiful = require("beautiful") 5 | local helpers = require("helpers") 6 | local naughty = require("naughty") 7 | local gears = require("gears") 8 | 9 | local M = {} 10 | 11 | M.pinned = { "Nemo", "Firefox Web Browser", "Vesktop", "Alacritty", "Spotify" } 12 | M.num_visible = 5 -- Number of visible apps in launcher at one time 13 | 14 | function M.get_apps() 15 | M.apps = {} 16 | local app_info = Gio.AppInfo 17 | local apps = app_info.get_all() 18 | 19 | for _, app in pairs(apps) do 20 | local name = app_info.get_display_name(app) 21 | local cmd = app_info.get_commandline(app) 22 | local exec = app_info.get_executable(app) 23 | local icon = helpers.get_gicon_path(app_info.get_icon(app)) or nil 24 | local description = Gio.AppInfo.get_description(app) 25 | local filter = name 26 | 27 | local pinned = false 28 | for _, pin_app in pairs(M.pinned) do 29 | if name == pin_app then 30 | pinned = true 31 | break 32 | end 33 | end 34 | 35 | if pinned then 36 | filter = "aaaaaaaaaa" .. filter 37 | end 38 | 39 | if name and exec and icon then 40 | table.insert(M.apps, { 41 | name = "" .. name .. "", 42 | cmd = cmd, 43 | exec = exec, 44 | icon = icon, 45 | filter = filter, 46 | description = description, 47 | }) 48 | end 49 | end 50 | end 51 | 52 | function M.create_default() 53 | local image = wibox.widget({ 54 | image = beautiful.icon_terminal, 55 | resize = true, 56 | valign = "center", 57 | halign = "center", 58 | forced_height = beautiful.icon_size[2], 59 | forced_width = beautiful.icon_size[2], 60 | widget = wibox.widget.imagebox, 61 | }) 62 | 63 | local name = wibox.widget({ 64 | markup = "Run in terminal", 65 | valign = "center", 66 | halign = "left", 67 | widget = wibox.widget.textbox, 68 | }) 69 | 70 | local description = wibox.widget({ 71 | text = "Run prompt input in terminal", 72 | valign = "center", 73 | halign = "left", 74 | widget = wibox.widget.textbox, 75 | }) 76 | 77 | local background = helpers.add_bg0(helpers.add_margin(wibox.widget({ 78 | image, 79 | { 80 | name, 81 | description, 82 | layout = wibox.layout.fixed.vertical, 83 | }, 84 | spacing = beautiful.margin[1], 85 | layout = wibox.layout.fixed.horizontal, 86 | }))) 87 | 88 | background:buttons(gears.table.join(awful.button({}, 1, function() 89 | M.run_default() 90 | end))) 91 | 92 | background:connect_signal("mouse::enter", function() 93 | background.bg = beautiful.bg1 94 | end) 95 | background:connect_signal("mouse::leave", function() 96 | background.bg = beautiful.bg0 97 | end) 98 | 99 | return background 100 | end 101 | 102 | function M.create_app_widget(app, default_bg) 103 | local image = wibox.widget({ 104 | image = app.icon or beautiful.icon_default, 105 | resize = true, 106 | valign = "center", 107 | halign = "center", 108 | forced_height = beautiful.icon_size[2], 109 | forced_width = beautiful.icon_size[2], 110 | widget = wibox.widget.imagebox, 111 | }) 112 | 113 | local name = wibox.widget({ 114 | markup = app.name or "Unknown", 115 | valign = "center", 116 | halign = "left", 117 | widget = wibox.widget.textbox, 118 | }) 119 | 120 | local description = wibox.widget({ 121 | text = app.description, 122 | valign = "center", 123 | halign = "left", 124 | widget = wibox.widget.textbox, 125 | }) 126 | 127 | local background = helpers.add_bg0(helpers.add_margin(wibox.widget({ 128 | image, 129 | { 130 | name, 131 | description, 132 | layout = wibox.layout.fixed.vertical, 133 | }, 134 | spacing = beautiful.margin[1], 135 | layout = wibox.layout.fixed.horizontal, 136 | }))) 137 | 138 | background.bg = default_bg 139 | 140 | background:buttons(gears.table.join(awful.button({}, 1, function() 141 | M.run_app(app) 142 | end))) 143 | 144 | background:connect_signal("mouse::enter", function() 145 | background.bg = beautiful.blue 146 | background.fg = beautiful.bg0 147 | end) 148 | background:connect_signal("mouse::leave", function() 149 | background.bg = default_bg 150 | background.fg = beautiful.fg0 151 | end) 152 | 153 | return background 154 | end 155 | 156 | function M.update_apps() 157 | M.matches = {} 158 | for _, app in pairs(M.apps) do 159 | if app.filter:lower():find(M.input:lower():gsub("%W", "%%%0")) then 160 | table.insert(M.matches, app) 161 | end 162 | end 163 | 164 | table.sort(M.matches, function(a, b) 165 | return a.filter:lower() < b.filter:lower() 166 | end) 167 | 168 | M.list:reset() 169 | 170 | local selected = false 171 | for i = M.selected, math.min(M.selected + M.num_visible, #M.matches) do 172 | local app = M.matches[i] 173 | local default_bg = beautiful.bg0 174 | if i == M.selected then 175 | default_bg = beautiful.bg1 176 | selected = true 177 | end 178 | local app_widget = M.create_app_widget(app, default_bg) 179 | M.list:add(app_widget) 180 | end 181 | 182 | if M.selected + M.num_visible > #M.matches then 183 | local default = M.create_default() 184 | if not selected then 185 | default.bg = beautiful.bg1 186 | end 187 | M.list:add(default) 188 | end 189 | end 190 | 191 | function M.run_app(app) 192 | awful.spawn(app.exec) 193 | awesome.emit_signal("launcher::stop") 194 | end 195 | 196 | function M.run_default() 197 | awful.spawn(M.prompt.text:sub(1, -2)) 198 | awesome.emit_signal("launcher::stop") 199 | end 200 | 201 | function M.keypressed_callback(_, mod, key, event) 202 | if event == "release" then 203 | return 204 | end 205 | 206 | if key == "BackSpace" then 207 | M.input = M.input:sub(1, -2) 208 | end 209 | 210 | if key == "Escape" then 211 | awesome.emit_signal("launcher::stop") 212 | end 213 | 214 | if key == "Down" then 215 | M.selected = M.selected + 1 216 | if M.selected == #M.matches + 2 then 217 | M.selected = 1 218 | end 219 | end 220 | 221 | if key == "Up" then 222 | M.selected = M.selected - 1 223 | if M.selected == 0 then 224 | M.selected = #M.matches + 1 225 | end 226 | end 227 | 228 | if key == "Return" then 229 | if M.selected <= #M.matches then 230 | M.run_app(M.matches[M.selected]) 231 | else 232 | M.run_default() 233 | end 234 | awesome.emit_signal("launcher::stop") 235 | end 236 | 237 | if #key == 1 then 238 | if not M.input then 239 | M.input = key 240 | else 241 | M.input = M.input .. key 242 | end 243 | end 244 | 245 | if not M.input or M.input == "" then 246 | M.prompt.text = "|" 247 | else 248 | M.prompt.text = M.input .. "|" 249 | end 250 | M.update_apps() 251 | end 252 | 253 | function M.new() 254 | M.apps = {} 255 | M.matches = {} 256 | M.input = "" 257 | M.selected = 1 258 | 259 | M.keygrabber = awful.keygrabber({ 260 | mask_event_callback = true, 261 | keypressed_callback = M.keypressed_callback, 262 | }) 263 | 264 | M.side_text = wibox.widget({ 265 | markup = "", 266 | font = beautiful.font_icon, 267 | valign = "center", 268 | halign = "center", 269 | widget = wibox.widget.textbox, 270 | }) 271 | 272 | M.prompt = wibox.widget({ 273 | text = "|", 274 | valign = "center", 275 | halign = "left", 276 | widget = wibox.widget.textbox, 277 | }) 278 | 279 | M.list = wibox.widget({ 280 | spacing = beautiful.margin[1], 281 | layout = wibox.layout.fixed.vertical, 282 | }) 283 | 284 | M.search = helpers.add_bg1(helpers.add_margin( 285 | wibox.widget({ 286 | M.side_text, 287 | M.prompt, 288 | spacing = beautiful.margin[1], 289 | layout = wibox.layout.fixed.horizontal, 290 | }), 291 | beautiful.margin[3], 292 | beautiful.margin[3] 293 | )) 294 | 295 | M.widget = wibox.widget({ 296 | helpers.add_margin(wibox.widget({ 297 | M.search, 298 | M.list, 299 | spacing = beautiful.margin[1], 300 | layout = wibox.layout.fixed.vertical, 301 | })), 302 | bg = beautiful.bg0, 303 | forced_height = beautiful.dpi(400), 304 | layout = wibox.container.background, 305 | }) 306 | 307 | M.widget:buttons({ 308 | awful.button({}, 4, function() 309 | M.selected = M.selected - 1 310 | if M.selected == 0 then 311 | M.selected = #M.matches + 1 312 | end 313 | M.update_apps() 314 | end), 315 | awful.button({}, 5, function() 316 | M.selected = M.selected + 1 317 | if M.selected == #M.matches + 2 then 318 | M.selected = 1 319 | end 320 | M.update_apps() 321 | end), 322 | }) 323 | 324 | return M.widget 325 | end 326 | 327 | return M 328 | -------------------------------------------------------------------------------- /components/sidebar/modules/media.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local beautiful = require("beautiful") 4 | local gears = require("gears") 5 | local helpers = require("helpers") 6 | 7 | local M = {} 8 | 9 | function M.new() 10 | M.art = wibox.widget({ 11 | image = beautiful.icon_music, 12 | resize = true, 13 | halign = "center", 14 | valign = "center", 15 | forced_height = beautiful.icon_size[3], 16 | forced_width = beautiful.icon_size[3], 17 | clip_shape = helpers.rrect(), 18 | widget = wibox.widget.imagebox 19 | }) 20 | 21 | M.title_text = wibox.widget({ 22 | markup = "Playing", 23 | halign = "left", 24 | valign = "center", 25 | widget = wibox.widget.textbox 26 | }) 27 | 28 | M.title = wibox.widget({ 29 | layout = wibox.container.scroll.horizontal, 30 | step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 31 | fps = 60, 32 | speed = 75, 33 | M.title_text 34 | }) 35 | 36 | M.title:pause() 37 | 38 | M.artist_text = wibox.widget({ 39 | markup = "Nothing", 40 | halign = "left", 41 | valign = "center", 42 | widget = wibox.widget.textbox 43 | }) 44 | 45 | M.artist = wibox.widget({ 46 | layout = wibox.container.scroll.horizontal, 47 | step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 48 | fps = 60, 49 | speed = 75, 50 | M.artist_text 51 | }) 52 | 53 | M.artist:pause() 54 | 55 | M.toggle_icon = wibox.widget({ 56 | markup = "", 57 | widget = wibox.widget.textbox, 58 | font = beautiful.font_icon_large, 59 | forced_height = beautiful.icon_size[1], 60 | forced_width = beautiful.icon_size[1], 61 | halign = "center", 62 | valign = "center" 63 | }) 64 | 65 | M.prev_icon = wibox.widget({ 66 | markup = "", 67 | widget = wibox.widget.textbox, 68 | font = beautiful.font_icon_large, 69 | forced_height = beautiful.icon_size[1], 70 | forced_width = beautiful.icon_size[1], 71 | halign = "center", 72 | valign = "center" 73 | }) 74 | 75 | M.next_icon = wibox.widget({ 76 | markup = "", 77 | widget = wibox.widget.textbox, 78 | font = beautiful.font_icon_large, 79 | forced_height = beautiful.icon_size[1], 80 | forced_width = beautiful.icon_size[1], 81 | halign = "center", 82 | valign = "center" 83 | }) 84 | 85 | M.toggle = helpers.add_bg1(helpers.add_margin(M.toggle_icon)) 86 | M.prev = helpers.add_bg1(helpers.add_margin(M.prev_icon)) 87 | M.next = helpers.add_bg1(helpers.add_margin(M.next_icon)) 88 | 89 | M.prev.fg = beautiful.blue 90 | M.next.fg = beautiful.blue 91 | 92 | M.toggle_icon:buttons(gears.table.join(awful.button({}, 1, function() 93 | awesome.emit_signal("playerctl::toggle") 94 | end))) 95 | 96 | M.next_icon:buttons(gears.table.join(awful.button({}, 1, function() 97 | awesome.emit_signal("playerctl::next") 98 | end))) 99 | 100 | M.prev_icon:buttons(gears.table.join(awful.button({}, 1, function() 101 | awesome.emit_signal("playerctl::prev") 102 | end))) 103 | 104 | M.button_group = helpers.add_margin(wibox.widget({ 105 | M.prev, 106 | M.toggle, 107 | M.next, 108 | layout = wibox.layout.flex.horizontal 109 | })) 110 | 111 | M.text_group = helpers.add_margin(wibox.widget({ 112 | M.artist, 113 | M.title, 114 | layout = wibox.layout.flex.vertical 115 | })) 116 | 117 | M.widget = helpers.add_margin(helpers.add_bg1(helpers.add_margin(wibox.widget({ 118 | helpers.add_margin(M.art, beautiful.margin[1], beautiful.margin[1]), 119 | M.text_group, 120 | M.button_group, 121 | layout = wibox.layout.align.horizontal 122 | })))) 123 | 124 | M.widget:connect_signal("mouse::enter", function() 125 | M.title:continue() 126 | M.artist:continue() 127 | end) 128 | 129 | M.widget:connect_signal("mouse::leave", function() 130 | M.title:pause() 131 | M.artist:pause() 132 | end) 133 | 134 | awesome.connect_signal("playerctl::metadata::update", function(title, artist, player_name, album) 135 | M.artist_text.markup = artist or "Nothing" 136 | M.title_text.markup = title or "Playing" 137 | end) 138 | 139 | awesome.connect_signal("playerctl::art::update", function(art_path) 140 | M.art.image = art_path and gears.surface.load_uncached_silently(art_path) or beautiful.icon_music 141 | end) 142 | 143 | awesome.connect_signal("playerctl::toggle::update", function(playing) 144 | if playing then 145 | M.toggle_icon.markup = "" 146 | else 147 | M.toggle_icon.markup = "" 148 | end 149 | end) 150 | 151 | return M.widget 152 | end 153 | 154 | return M 155 | -------------------------------------------------------------------------------- /components/sidebar/modules/notifications.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local beautiful = require("beautiful") 4 | local naughty = require("naughty") 5 | local helpers = require("helpers") 6 | 7 | local M = {} 8 | 9 | function M.new() 10 | M.erase_icon = wibox.widget({ 11 | markup = "", 12 | font = beautiful.font_icon, 13 | valign = "center", 14 | halign = "right", 15 | widget = wibox.widget.textbox 16 | }) 17 | 18 | M.erase = helpers.add_bg1(M.erase_icon) 19 | M.erase.fg = beautiful.blue 20 | 21 | M.tooltip = helpers.add_tooltip(M.erase, "Clear All") 22 | 23 | M.notifications = wibox.widget({ 24 | spacing = beautiful.margin[0], 25 | layout = wibox.layout.overflow.vertical, 26 | forced_height = beautiful.dpi(1000), 27 | scrollbar_width = 10, 28 | step = 50 29 | }) 30 | 31 | M.notifications:set_scrollbar_widget(wibox.widget({ 32 | shape = helpers.rrect(), 33 | widget = wibox.widget.separator 34 | })) 35 | 36 | M.erase:buttons(awful.util.table.join(awful.button({}, 1, function() 37 | M.notifications:reset() 38 | end))) 39 | 40 | M.widget = helpers.add_margin(helpers.add_bg1(helpers.add_margin(wibox.widget({ 41 | helpers.add_margin(wibox.widget({ 42 | { 43 | markup = "Notifications", 44 | widget = wibox.widget.textbox 45 | }, 46 | nil, 47 | M.erase, 48 | layout = wibox.layout.align.horizontal, 49 | }), beautiful.margin[1], beautiful.margin[1]), 50 | M.notifications, 51 | spacing = beautiful.margin[1], 52 | layout = wibox.layout.fixed.vertical 53 | })))) 54 | 55 | naughty.connect_signal("request::display", function(n) 56 | local close_icon = wibox.widget({ 57 | valign = "center", 58 | halign = "center", 59 | forced_width = beautiful.icon_size[1], 60 | forced_height = beautiful.icon_size[1], 61 | markup = "", 62 | font = beautiful.font_icon, 63 | widget = wibox.widget.textbox 64 | }) 65 | 66 | local close_button = helpers.add_bg0(close_icon) 67 | close_button.fg = beautiful.red 68 | 69 | local icon = nil 70 | if n.clients[1] ~= nil then 71 | icon = wibox.widget({ 72 | client = n.clients[1], 73 | forced_height = beautiful.icon_size[0], 74 | forced_width = beautiful.icon_size[0], 75 | widget = awful.widget.clienticon 76 | }) 77 | elseif n.app_icon then 78 | icon = wibox.widget({ 79 | image = n.app_icon, 80 | resize = true, 81 | forced_height = beautiful.icon_size[0], 82 | forced_width = beautiful.icon_size[0], 83 | clip_shape = helpers.rrect(), 84 | widget = wibox.widget.imagebox 85 | }) 86 | end 87 | 88 | local image = nil 89 | if n.icon then 90 | image = wibox.widget({ 91 | image = n.icon, 92 | resize = true, 93 | forced_height = beautiful.icon_size[3], 94 | forced_width = beautiful.icon_size[3], 95 | halign = "right", 96 | valign = "center", 97 | clip_shape = helpers.rrect(), 98 | widget = wibox.widget.imagebox 99 | }) 100 | end 101 | 102 | local title = wibox.widget({ 103 | layout = wibox.container.scroll.horizontal, 104 | step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 105 | fps = 60, 106 | speed = 75, 107 | { 108 | halign = "left", 109 | valign = "center", 110 | markup = "" .. n.title .. "", 111 | widget = wibox.widget.textbox 112 | } 113 | }) 114 | 115 | title:pause() 116 | 117 | local message = wibox.widget({ 118 | layout = wibox.container.scroll.horizontal, 119 | step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 120 | fps = 60, 121 | speed = 75, 122 | { 123 | halign = "left", 124 | valign = "center", 125 | markup = n.message, 126 | widget = wibox.widget.textbox 127 | } 128 | }) 129 | 130 | message:pause() 131 | 132 | local app_name = wibox.widget({ 133 | valign = "center", 134 | halign = "left", 135 | markup = "" .. n.app_name .. "", 136 | widget = wibox.widget.textbox 137 | }) 138 | 139 | local actions = wibox.widget({ 140 | notification = n, 141 | base_layout = wibox.widget({ 142 | spacing = beautiful.margin[1], 143 | layout = wibox.layout.flex.horizontal 144 | }), 145 | widget_template = { 146 | { 147 | id = "text_role", 148 | valign = "center", 149 | halign = "center", 150 | widget = wibox.widget.textbox 151 | }, 152 | shape = helpers.rrect(), 153 | bg = beautiful.blue, 154 | fg = beautiful.bg0, 155 | forced_height = beautiful.dpi(30), 156 | visible = n.actions and #n.actions > 0, 157 | widget = wibox.container.background 158 | }, 159 | style = { 160 | underline_normal = false, 161 | underline_selected = true 162 | }, 163 | widget = naughty.list.actions 164 | }) 165 | 166 | local time = wibox.widget({ 167 | markup = os.date("%I:%M %p"), 168 | halign = "right", 169 | valign = "center", 170 | widget = wibox.widget.textbox 171 | }) 172 | 173 | local title_bar = helpers.add_margin(wibox.widget({ 174 | { 175 | { 176 | icon, 177 | valign = "center", 178 | halign = "center", 179 | layout = wibox.container.place, 180 | }, 181 | app_name, 182 | spacing = beautiful.margin[0], 183 | layout = wibox.layout.fixed.horizontal 184 | }, 185 | nil, 186 | { 187 | time, 188 | close_button, 189 | spacing = beautiful.margin[0], 190 | layout = wibox.layout.fixed.horizontal 191 | }, 192 | layout = wibox.layout.align.horizontal, 193 | }), beautiful.margin[1], beautiful.margin[0]) 194 | 195 | local body = helpers.add_margin(wibox.widget({ 196 | { 197 | image, 198 | { 199 | title, 200 | message, 201 | spacing = beautiful.margin[1], 202 | layout = wibox.layout.fixed.vertical 203 | }, 204 | spacing = beautiful.margin[1], 205 | fill_space = true, 206 | layout = wibox.layout.fixed.horizontal 207 | }, 208 | actions, 209 | layout = wibox.layout.fixed.vertical 210 | }), beautiful.margin[1], beautiful.margin[1]) 211 | body.shape = nil 212 | 213 | local widget = helpers.add_bg0(wibox.widget({ 214 | title_bar, 215 | body, 216 | forced_height = (n.actions and #n.actions > 0) and beautiful.dpi(150) or beautiful.dpi(120), 217 | layout = wibox.layout.fixed.vertical 218 | })) 219 | 220 | widget:connect_signal("mouse::enter", function() 221 | title:continue() 222 | message:continue() 223 | end) 224 | 225 | widget:connect_signal("mouse::leave", function() 226 | title:pause() 227 | message:pause() 228 | end) 229 | 230 | close_button:buttons({ awful.button({}, 1, function() 231 | M.notifications:remove_widgets(widget, true) 232 | end) }) 233 | 234 | M.notifications:insert(1, widget) 235 | end) 236 | 237 | return M.widget 238 | end 239 | 240 | return M 241 | -------------------------------------------------------------------------------- /components/sidebar/modules/status.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local beautiful = require("beautiful") 4 | local helpers = require("helpers") 5 | local config = require("config") 6 | 7 | local M = {} 8 | 9 | function M.create_status_widget(icon, color, hover_text) 10 | local icon_widget = wibox.widget({ 11 | markup = icon, 12 | font = beautiful.font_icon_large, 13 | valign = "center", 14 | halign = "center", 15 | widget = wibox.widget.textbox 16 | }) 17 | 18 | local progressbar = wibox.widget({ 19 | icon_widget, 20 | colors = { color }, 21 | bg = beautiful.bg0, 22 | thickness = beautiful.dpi(8), 23 | forced_height = beautiful.dpi(74), 24 | paddings = beautiful.dpi(2), 25 | rounded_edge = true, 26 | min_value = 0, 27 | max_value = 100, 28 | widget = wibox.container.arcchart 29 | }) 30 | 31 | local text = wibox.widget({ 32 | markup = "100%", 33 | valign = "center", 34 | halign = "center", 35 | widget = wibox.widget.textbox 36 | }) 37 | 38 | local widget = helpers.add_bg1(helpers.add_margin(wibox.widget({ 39 | helpers.add_margin(progressbar, beautiful.margin[2], beautiful.margin[2]), 40 | text, 41 | spacing = beautiful.margin[1], 42 | layout = wibox.layout.fixed.vertical 43 | }))) 44 | widget.fg = color 45 | 46 | local tooltip = helpers.add_tooltip(widget, hover_text) 47 | 48 | return { widget = widget, icon = icon_widget, text = text, progressbar = progressbar, tooltip = tooltip } 49 | end 50 | 51 | function M.new() 52 | local battery = M.create_status_widget("", beautiful.green, "Battery") 53 | awesome.connect_signal("battery::update", function(d) 54 | local percentage = math.floor(d.percentage) 55 | if percentage < 25 then 56 | battery.progressbar.colors[1] = beautiful.red 57 | battery.widget.fg = beautiful.red 58 | elseif percentage < 50 then 59 | battery.progressbar.colors[1] = beautiful.orange 60 | battery.widget.fg = beautiful.orange 61 | elseif percentage < 75 then 62 | battery.progressbar.colors[1] = beautiful.blue 63 | battery.widget.fg = beautiful.blue 64 | else 65 | battery.progressbar.colors[1] = beautiful.green 66 | battery.widget.fg = beautiful.green 67 | end 68 | if d.state == 1 or d.state == 4 then 69 | battery.icon.markup = "" 70 | end 71 | battery.tooltip.markup = "Battery: " .. tostring(percentage) .. "%" 72 | battery.text.markup = tostring(percentage) .. "%" 73 | battery.progressbar.value = percentage 74 | end) 75 | 76 | local volume = M.create_status_widget("", beautiful.green, "Volume") 77 | awesome.connect_signal("volume::update", function(mute, vol) 78 | if mute then 79 | volume.tooltip.markup = "Muted" 80 | volume.icon.markup = "" 81 | volume.widget.fg = beautiful.red 82 | volume.progressbar.colors[1] = beautiful.red 83 | return 84 | end 85 | 86 | if vol == 0 then 87 | volume.icon.markup = "" 88 | volume.widget.fg = beautiful.red 89 | volume.progressbar.colors[1] = beautiful.red 90 | elseif vol < 30 then 91 | volume.icon.markup = "" 92 | volume.widget.fg = beautiful.purple 93 | volume.progressbar.colors[1] = beautiful.purple 94 | elseif vol < 60 then 95 | volume.icon.markup = "" 96 | volume.widget.fg = beautiful.blue 97 | volume.progressbar.colors[1] = beautiful.blue 98 | else 99 | volume.icon.markup = "" 100 | volume.widget.fg = beautiful.green 101 | volume.progressbar.colors[1] = beautiful.green 102 | end 103 | volume.text.markup = tostring(vol).."%" 104 | volume.progressbar.value = vol 105 | volume.tooltip.markup = "Volume: " .. tostring(vol) .. "%" 106 | end) 107 | volume.widget:buttons({ 108 | awful.button({}, 1, function() 109 | awful.spawn.with_shell(config.apps.volume_manager) 110 | end), 111 | awful.button({}, 2, function() 112 | awesome.emit_signal("volume::mute") 113 | end), 114 | awful.button({}, 4, function() 115 | awesome.emit_signal("volume::increase", 5) 116 | end), 117 | awful.button({}, 5, function() 118 | awesome.emit_signal("volume::decrease", 5) 119 | end) 120 | }) 121 | 122 | local brightness = M.create_status_widget("", beautiful.green, "Brightness") 123 | awesome.connect_signal("brightness::update", function(value) 124 | if value < 25 then 125 | brightness.widget.fg = beautiful.red 126 | brightness.progressbar.colors[1] = beautiful.red 127 | elseif value < 50 then 128 | brightness.widget.fg = beautiful.purple 129 | brightness.progressbar.colors[1] = beautiful.purple 130 | elseif value < 75 then 131 | brightness.widget.fg = beautiful.blue 132 | brightness.progressbar.colors[1] = beautiful.blue 133 | else 134 | brightness.widget.fg = beautiful.green 135 | brightness.progressbar.colors[1] = beautiful.green 136 | end 137 | brightness.text.markup = tostring(value).."%" 138 | brightness.progressbar.value = value 139 | brightness.tooltip.markup = "Brightness: "..tostring(value).."%" 140 | end) 141 | brightness.widget:buttons({ 142 | awful.button({}, 4, function() 143 | awesome.emit_signal("brightness::increase", 5) 144 | end), 145 | awful.button({}, 5, function() 146 | awesome.emit_signal("brightness::decrease", 5) 147 | end) 148 | }) 149 | 150 | local cpu = M.create_status_widget("", beautiful.green, "CPU") 151 | awesome.connect_signal("cpu::update", function(value) 152 | if value < 25 then 153 | cpu.widget.fg = beautiful.green 154 | cpu.progressbar.colors[1] = beautiful.green 155 | elseif value < 50 then 156 | cpu.widget.fg = beautiful.blue 157 | cpu.progressbar.colors[1] = beautiful.blue 158 | elseif value < 75 then 159 | cpu.widget.fg = beautiful.purple 160 | cpu.progressbar.colors[1] = beautiful.purple 161 | else 162 | cpu.widget.fg = beautiful.red 163 | cpu.progressbar.colors[1] = beautiful.red 164 | end 165 | cpu.text.markup = tostring(value).."%" 166 | cpu.progressbar.value = value 167 | cpu.tooltip.markup = "CPU: "..tostring(value).."%" 168 | end) 169 | 170 | local memory = M.create_status_widget("", beautiful.green, "Memory") 171 | awesome.connect_signal("memory::update", function(value) 172 | if value < 25 then 173 | memory.widget.fg = beautiful.green 174 | memory.progressbar.colors[1] = beautiful.green 175 | elseif value < 50 then 176 | memory.widget.fg = beautiful.blue 177 | memory.progressbar.colors[1] = beautiful.blue 178 | elseif value < 75 then 179 | memory.widget.fg = beautiful.purple 180 | memory.progressbar.colors[1] = beautiful.purple 181 | else 182 | memory.widget.fg = beautiful.red 183 | memory.progressbar.colors[1] = beautiful.red 184 | end 185 | memory.text.markup = tostring(value).."%" 186 | memory.progressbar.value = value 187 | memory.tooltip.markup = "Memory: "..tostring(value).."%" 188 | end) 189 | 190 | M.widget = helpers.add_margin(wibox.widget({ 191 | battery.widget, 192 | volume.widget, 193 | brightness.widget, 194 | cpu.widget, 195 | memory.widget, 196 | spacing = beautiful.margin[2], 197 | layout = wibox.layout.flex.horizontal 198 | })) 199 | 200 | return M.widget 201 | end 202 | 203 | return M 204 | -------------------------------------------------------------------------------- /components/switcher/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local beautiful = require("beautiful") 4 | local helpers = require("helpers") 5 | 6 | local M = {} 7 | 8 | function M.create_default() 9 | local image = wibox.widget({ 10 | markup = "", 11 | font = beautiful.font_icon_large, 12 | valign = "center", 13 | halign = "center", 14 | widget = wibox.widget.textbox 15 | }) 16 | 17 | local name = wibox.widget({ 18 | markup = "Desktop", 19 | valign = "center", 20 | halign = "center", 21 | forced_width = beautiful.dpi(150), 22 | forced_height = beautiful.dpi(50), 23 | widget = wibox.widget.textbox 24 | }) 25 | 26 | local background = wibox.widget({ 27 | bg = beautiful.bg0, 28 | shape = helpers.rrect(), 29 | widget = wibox.container.background 30 | }) 31 | 32 | local widget = wibox.widget({ 33 | background, 34 | { 35 | image, 36 | valign = "center", 37 | halign = "center", 38 | layout = wibox.container.place 39 | }, 40 | { 41 | name, 42 | valign = "bottom", 43 | halign = "center", 44 | layout = wibox.container.place 45 | }, 46 | layout = wibox.layout.stack 47 | }) 48 | 49 | M.list:add(widget) 50 | end 51 | 52 | function M.create_widget(c) 53 | local icon = c.icon or helpers.get_icon(c.class) or beautiful.icon_default 54 | 55 | local image = wibox.widget({ 56 | image = icon, 57 | resize = true, 58 | valign = "center", 59 | halign = "center", 60 | forced_width = beautiful.icon_size[3], 61 | forced_height = beautiful.icon_size[3], 62 | widget = wibox.widget.imagebox 63 | }) 64 | 65 | local name = wibox.widget({ 66 | markup = c.class and c.class:gsub("^%l", string.upper) or "Unknown", 67 | valign = "center", 68 | halign = "center", 69 | forced_width = beautiful.dpi(150), 70 | forced_height = beautiful.dpi(50), 71 | widget = wibox.widget.textbox 72 | }) 73 | 74 | local background = wibox.widget({ 75 | bg = beautiful.bg0, 76 | shape = helpers.rrect(), 77 | widget = wibox.container.background 78 | }) 79 | 80 | local widget = wibox.widget({ 81 | background, 82 | { 83 | image, 84 | valign = "center", 85 | halign = "center", 86 | layout = wibox.container.place 87 | }, 88 | { 89 | name, 90 | valign = "bottom", 91 | halign = "center", 92 | layout = wibox.container.place 93 | }, 94 | layout = wibox.layout.stack 95 | }) 96 | 97 | M.list:add(widget) 98 | 99 | table.insert(M.clients, { 100 | background = background, 101 | client = c 102 | }) 103 | end 104 | 105 | function M.place() 106 | M.wibox.screen = awful.screen.focused() 107 | awful.placement.centered(M.wibox) 108 | end 109 | 110 | function M.update_clients() 111 | M.list:reset() 112 | M.clients = {} 113 | for _, c in ipairs(M.history) do 114 | M.create_widget(c) 115 | end 116 | 117 | for _, i in ipairs(awful.screen.focused().selected_tag:clients()) do 118 | local in_history = false 119 | for _, j in ipairs(M.history) do 120 | if i == j then 121 | in_history = true 122 | break 123 | end 124 | end 125 | if not in_history then 126 | M.create_widget(i) 127 | end 128 | end 129 | 130 | if #M.clients == 0 then 131 | M.create_default() 132 | M.wibox.width = beautiful.switcher_width 133 | else 134 | M.wibox.width = #M.clients * beautiful.switcher_width 135 | end 136 | M.wibox.height = beautiful.switcher_height 137 | M.place() 138 | end 139 | 140 | function M.cycle() 141 | local cur = -1 142 | for i, c in ipairs(M.clients) do 143 | if c.client == client.focus then 144 | cur = i 145 | break 146 | end 147 | end 148 | 149 | if cur ~= -1 then 150 | M.clients[cur].background.bg = beautiful.bg0 151 | M.clients[cur].client.minimized = M.client_minimized 152 | else 153 | cur = 1 154 | end 155 | 156 | if cur == #M.clients then 157 | cur = 1 158 | else 159 | cur = cur + 1 160 | end 161 | 162 | if M.clients[cur] then 163 | M.clients[cur].background.bg = beautiful.bg2 164 | 165 | M.client_minimized = M.clients[cur].client.minimized 166 | M.clients[cur].client:jump_to() 167 | for i, c in ipairs(M.history) do 168 | if c == M.clients[cur].client then 169 | table.remove(M.history, i) 170 | end 171 | end 172 | table.insert(M.history, 1, M.clients[cur].client) 173 | end 174 | end 175 | 176 | function M.keypressed_callback(_, mod, key, event) 177 | if key == "Tab" then 178 | M.cycle() 179 | end 180 | end 181 | 182 | function M.keyreleased_callback(_, mod, key, event) 183 | if key:match("Alt") then 184 | M.stop() 185 | end 186 | end 187 | 188 | function M.new() 189 | M.clients = {} 190 | M.history = {} 191 | 192 | M.keygrabber = awful.keygrabber({ 193 | keypressed_callback = M.keypressed_callback, 194 | keyreleased_callback = M.keyreleased_callback 195 | }) 196 | 197 | M.list = wibox.widget({ 198 | spacing = beautiful.margin[0], 199 | layout = wibox.layout.flex.horizontal 200 | }) 201 | 202 | M.widget = helpers.add_bg0(helpers.add_margin(M.list)) 203 | 204 | M.wibox = wibox({ 205 | widget = M.widget, 206 | screen = awful.screen.focused(), 207 | ontop = true, 208 | visible = false, 209 | shape = helpers.rrect(), 210 | border_color = beautiful.border_color_active, 211 | border_width = beautiful.border_width, 212 | }) 213 | 214 | awesome.connect_signal("switcher::toggle", function() 215 | M.toggle() 216 | end) 217 | 218 | tag.connect_signal("property::selected", function(t) 219 | M.history = {} 220 | end) 221 | 222 | return M 223 | end 224 | 225 | function M.toggle() 226 | M.wibox.visible = not M.wibox.visible 227 | if M.wibox.visible then 228 | M.client_minimized = false 229 | M.place() 230 | M.update_clients() 231 | M.cycle() 232 | M.keygrabber:start() 233 | else 234 | M.keygrabber:stop() 235 | end 236 | end 237 | 238 | function M.stop() 239 | M.wibox.visible = false 240 | M.keygrabber:stop() 241 | end 242 | 243 | return M.new() 244 | -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | -- User specific config here 2 | local M = {} 3 | 4 | M.modkey = "Mod4" 5 | 6 | M.apps = { 7 | terminal = "alacritty", 8 | editor = "nvim", 9 | browser = "firefox", 10 | file_manager = "nemo", 11 | volume_manager = "pavucontrol", 12 | network_manager = "nm-connection-editor", 13 | power_manager = "xfce4-power-manager-settings", 14 | bluetooth_manager = "blueman-manager", 15 | settings = "xfce4-settings-manager", 16 | } 17 | 18 | M.editor_cmd = M.apps.terminal.." -e "..M.apps.editor 19 | 20 | return M 21 | -------------------------------------------------------------------------------- /daemons/battery.lua: -------------------------------------------------------------------------------- 1 | local upower = require('lgi').require('UPowerGlib') 2 | local gears = require("gears") 3 | 4 | local M = {} 5 | 6 | M.interval = 5 7 | M.device = upower.Client():get_display_device() 8 | 9 | function M.update() 10 | awesome.emit_signal("battery::update", M.device) 11 | end 12 | 13 | function M.start() 14 | gears.timer { 15 | timeout = M.interval, 16 | autostart = true, 17 | call_now = true, 18 | callback = M.update 19 | } 20 | end 21 | 22 | M.start() 23 | -------------------------------------------------------------------------------- /daemons/brightness.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | 4 | local M = {} 5 | 6 | M.interval = 5 7 | M.script = "xbacklight -get" 8 | 9 | function M.update() 10 | awful.spawn.easy_async_with_shell(M.script, function(stdout) 11 | stdout = stdout:gsub("[\n\r]", "") 12 | local bright = tonumber(stdout) 13 | if bright then 14 | awesome.emit_signal("brightness::update", bright) 15 | end 16 | end) 17 | end 18 | 19 | function M.start() 20 | gears.timer { 21 | timeout = M.interval, 22 | autostart = true, 23 | call_now = true, 24 | callback = M.update 25 | } 26 | 27 | awesome.connect_signal("brightness::increase", function(i) 28 | awful.spawn.with_shell("xbacklight -inc "..tostring(i)) 29 | M.update() 30 | end) 31 | awesome.connect_signal("brightness::decrease", function(d) 32 | awful.spawn.with_shell("xbacklight -dec "..tostring(d)) 33 | M.update() 34 | end) 35 | end 36 | 37 | M.start() 38 | -------------------------------------------------------------------------------- /daemons/cpu.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local naughty = require("naughty") 4 | 5 | local M = {} 6 | 7 | M.interval = 5 8 | M.script = "top -bn1 | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' | awk '{print 100 - $1\"%\"}'" 9 | 10 | function M.update() 11 | awful.spawn.easy_async_with_shell(M.script, function(stdout) 12 | stdout = stdout:gsub("[\n\r]", "") 13 | local cpu = tonumber(stdout:sub(1, -2)) 14 | if cpu then 15 | awesome.emit_signal("cpu::update", math.floor(cpu)) 16 | end 17 | end) 18 | end 19 | 20 | function M.start() 21 | gears.timer { 22 | timeout = M.interval, 23 | autostart = true, 24 | call_now = true, 25 | callback = M.update 26 | } 27 | end 28 | 29 | M.start() 30 | -------------------------------------------------------------------------------- /daemons/init.lua: -------------------------------------------------------------------------------- 1 | require("daemons.battery") 2 | require("daemons.network") 3 | require("daemons.volume") 4 | require("daemons.playerctl") 5 | require("daemons.cpu") 6 | require("daemons.memory") 7 | require("daemons.brightness") 8 | require("daemons.uptime") 9 | -------------------------------------------------------------------------------- /daemons/memory.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | 4 | local M = {} 5 | 6 | M.interval = 5 7 | M.script = "free -m | grep Mem | awk '{print ($3/$2)*100}'" 8 | 9 | function M.update() 10 | awful.spawn.easy_async_with_shell(M.script, function(stdout) 11 | local memory = tonumber(stdout) 12 | awesome.emit_signal("memory::update", math.floor(memory)) 13 | end) 14 | end 15 | 16 | function M.start() 17 | gears.timer { 18 | timeout = M.interval, 19 | autostart = true, 20 | call_now = true, 21 | callback = M.update 22 | } 23 | end 24 | 25 | M.start() 26 | -------------------------------------------------------------------------------- /daemons/network.lua: -------------------------------------------------------------------------------- 1 | local naughty = require("naughty") 2 | local gtimer = require("gears.timer") 3 | 4 | local lgi = require("lgi") 5 | local nm = lgi.NM 6 | 7 | local M = {} 8 | 9 | function M.start() 10 | M.client = nm.Client.new() 11 | M.devices = M.client:get_devices() 12 | for i, d in ipairs(M.devices) do 13 | local info = d:get_device_type() 14 | if (info == "ETHERNET") then 15 | M.eth = d 16 | end 17 | if (info == "WIFI") then 18 | M.wifi = d 19 | end 20 | end 21 | 22 | if M.eth then 23 | M.eth.on_state_changed = function() 24 | local id = nil 25 | local eth_state = nil 26 | local wifi_state = nil 27 | if M.client.primary_connection and M.client.primary_connection.id then 28 | id = M.client.primary_connection.id 29 | end 30 | if M.eth and M.eth.state then 31 | eth_state = M.eth.state 32 | end 33 | if M.wifi and M.wifi.state then 34 | wifi_state = M.wifi.state 35 | end 36 | awesome.emit_signal("network::update", id, eth_state, wifi_state) 37 | end 38 | end 39 | if M.wifi then 40 | M.wifi.on_state_changed = function() 41 | local id = nil 42 | local eth_state = nil 43 | local wifi_state = nil 44 | if M.client.primary_connection and M.client.primary_connection.id then 45 | id = M.client.primary_connection.id 46 | end 47 | if M.eth and M.eth.state then 48 | eth_state = M.eth.state 49 | end 50 | if M.wifi and M.wifi.state then 51 | wifi_state = M.wifi.state 52 | end 53 | awesome.emit_signal("network::update", id, eth_state, wifi_state) 54 | end 55 | end 56 | local id = nil 57 | local eth_state = nil 58 | local wifi_state = nil 59 | if M.client.primary_connection and M.client.primary_connection.id then 60 | id = M.client.primary_connection.id 61 | end 62 | if M.eth and M.eth.state then 63 | eth_state = M.eth.state 64 | end 65 | if M.wifi and M.wifi.state then 66 | wifi_state = M.wifi.state 67 | end 68 | gtimer.delayed_call(awesome.emit_signal, 'network::update', id, eth_state, wifi_state) 69 | end 70 | 71 | M.start() 72 | -------------------------------------------------------------------------------- /daemons/playerctl.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local naughty = require("naughty") 4 | 5 | local M = {} 6 | 7 | function M.update_toggle() 8 | awful.spawn.with_line_callback("playerctl -F status", { 9 | stdout = function(stdout) 10 | stdout = stdout:gsub("[\n\r]", "") 11 | if stdout == "Playing" then 12 | awesome.emit_signal("playerctl::toggle::update", true) 13 | else 14 | awesome.emit_signal("playerctl::toggle::update", false) 15 | end 16 | end 17 | }) 18 | end 19 | 20 | function M.update_art() 21 | awful.spawn.with_line_callback( 22 | "playerctl -F metadata --format '{{mpris:artUrl}}'", 23 | { 24 | stdout = function(stdout) 25 | local art_url = stdout or "" 26 | 27 | art_url = art_url:gsub('%\n', '') 28 | art_url = art_url:gsub("open.spotify.com", "i.scdn.co") 29 | local art_path = "" 30 | if art_url ~= nil then 31 | art_path = os.tmpname() 32 | awful.spawn.easy_async_with_shell("curl -L -s " .. art_url .. " -o " .. art_path, function() 33 | awesome.emit_signal("playerctl::art::update", art_path) 34 | end) 35 | end 36 | end 37 | }) 38 | end 39 | 40 | function M.update_metadata() 41 | awful.spawn.with_line_callback( 42 | "playerctl -F metadata --format 'title_{{title}}artist_{{artist}}player_name_{{playerName}}album_{{album}}'", 43 | { 44 | stdout = function(stdout) 45 | local title = gears.string.xml_escape(stdout:match('title_(.*)artist_')) or nil 46 | local artist = gears.string.xml_escape(stdout:match('artist_(.*)player_name_')) or nil 47 | local player_name = stdout:match('player_name_(.*)album_') or nil 48 | local album = gears.string.xml_escape(stdout:match('album_(.*)')) or nil 49 | awesome.emit_signal("playerctl::metadata::update", title, artist, player_name, album) 50 | end 51 | }) 52 | end 53 | 54 | function M.start() 55 | awful.spawn.easy_async({ 'pkill', '--full', '--uid', os.getenv('USER'), '^playerctl -F' }, 56 | function() 57 | M.update_toggle() 58 | M.update_art() 59 | M.update_metadata() 60 | end) 61 | 62 | awesome.connect_signal("playerctl::next", function() 63 | awful.spawn.with_shell("playerctl next") 64 | end) 65 | 66 | awesome.connect_signal("playerctl::prev", function() 67 | awful.spawn.with_shell("playerctl previous") 68 | end) 69 | 70 | awesome.connect_signal("playerctl::toggle", function() 71 | awful.spawn.with_shell("playerctl play-pause") 72 | end) 73 | end 74 | 75 | M.start() 76 | -------------------------------------------------------------------------------- /daemons/uptime.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | 4 | local M = {} 5 | 6 | M.interval = 5 7 | M.script = "uptime -p" 8 | 9 | function M.update() 10 | awful.spawn.easy_async_with_shell(M.script, function(stdout) 11 | awesome.emit_signal("uptime::update", stdout) 12 | end) 13 | end 14 | 15 | function M.start() 16 | gears.timer { 17 | timeout = M.interval, 18 | autostart = true, 19 | call_now = true, 20 | callback = M.update 21 | } 22 | end 23 | 24 | M.start() 25 | -------------------------------------------------------------------------------- /daemons/volume.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local naughty = require("naughty") 3 | 4 | local M = {} 5 | M.mute = false 6 | M.vol = 0 7 | 8 | function M.update() 9 | awful.spawn.easy_async_with_shell("pamixer --get-mute && pamixer --get-volume", function(stdout) 10 | local info = {} 11 | for line in stdout:gmatch("[^\r\n]+") do 12 | table.insert(info, line) 13 | end 14 | if info[1] == "false" then 15 | M.mute = false 16 | else 17 | M.mute = true 18 | end 19 | M.vol = tonumber(info[2]) 20 | awesome.emit_signal("volume::update", M.mute, M.vol) 21 | end) 22 | end 23 | 24 | function M.start() 25 | -- Initial values 26 | M.update() 27 | 28 | awful.spawn.easy_async({ 29 | 'pkill', '--full', '--uid', os.getenv('USER'), '^pactl subscribe' 30 | }, function() 31 | awful.spawn.with_line_callback([[ 32 | bash -c " 33 | LANG=C pactl subscribe 2> /dev/null | grep --line-buffered \"Event 'change' on sink\" 34 | "]], { 35 | stdout = function(line) M.update() end 36 | }) 37 | end) 38 | 39 | awesome.connect_signal("volume::increase", function(i) 40 | awful.spawn.with_shell("pamixer -i " .. tostring(i)) 41 | end) 42 | 43 | awesome.connect_signal("volume::decrease", function(d) 44 | awful.spawn.with_shell("pamixer -d " .. tostring(d)) 45 | end) 46 | 47 | awesome.connect_signal("volume::mute", function() 48 | awful.spawn.with_shell("pamixer -t") 49 | end) 50 | end 51 | 52 | M.start() 53 | -------------------------------------------------------------------------------- /helpers.lua: -------------------------------------------------------------------------------- 1 | -- Contains a bunch of useful helper functions 2 | 3 | local awful = require("awful") 4 | local gears = require("gears") 5 | local wibox = require("wibox") 6 | local beautiful = require("beautiful") 7 | local lgi = require("lgi") 8 | local Gio = lgi.Gio 9 | local Gtk = lgi.require("Gtk", "3.0") 10 | 11 | local M = {} 12 | 13 | function M.rrect() 14 | return function(cr, width, height) 15 | gears.shape.rounded_rect(cr, width, height, beautiful.radius) 16 | end 17 | end 18 | 19 | function M.circle() 20 | return function(cr, width, height) 21 | gears.shape.circle(cr, width, height) 22 | end 23 | end 24 | 25 | -- Makes widget change color on hover 26 | function M.add_click(widget, color) 27 | color = color or beautiful.blue 28 | local background = wibox.widget({ 29 | widget, 30 | shape = M.rrect(), 31 | layout = wibox.container.background, 32 | }) 33 | 34 | background:connect_signal("mouse::enter", function() 35 | background.fg = beautiful.bg0 36 | background.bg = color 37 | end) 38 | 39 | background:connect_signal("mouse::leave", function() 40 | background.fg = beautiful.fg0 41 | background.bg = "#00000000" 42 | end) 43 | 44 | return background 45 | end 46 | 47 | -- Adds background to widget 48 | function M.add_bg0(widget) 49 | local background = wibox.widget({ 50 | widget, 51 | layout = wibox.container.background, 52 | bg = beautiful.bg0, 53 | fg = beautiful.fg0, 54 | shape = M.rrect() 55 | }) 56 | return background 57 | end 58 | 59 | function M.add_bg1(widget) 60 | local background = wibox.widget({ 61 | widget, 62 | layout = wibox.container.background, 63 | bg = beautiful.bg1, 64 | fg = beautiful.fg0, 65 | shape = M.rrect() 66 | }) 67 | return background 68 | end 69 | 70 | function M.add_bg(widget) 71 | return wibox.widget({ 72 | widget, 73 | shape = M.rrect(), 74 | layout = wibox.container.background 75 | }) 76 | end 77 | 78 | -- Creates tooltip 79 | function M.add_tooltip(widget, markup) 80 | return awful.tooltip({ 81 | objects = { widget }, 82 | markup = markup, 83 | shape = M.rrect(), 84 | margins_leftright = beautiful.margin[1], 85 | margins_topbottom = beautiful.margin[1], 86 | border_width = beautiful.border_width, 87 | border_color = beautiful.border_color_active, 88 | }) 89 | end 90 | 91 | -- Adds margin to widget 92 | function M.add_margin(widget, h, v) 93 | h = h or beautiful.margin[0] 94 | v = v or beautiful.margin[0] 95 | return wibox.container.margin(widget, h, h, v, v) 96 | end 97 | 98 | -- Icons 99 | M.gtk_theme = Gtk.IconTheme.get_default() 100 | M.apps = Gio.AppInfo.get_all() 101 | 102 | function M.get_icon(client_name) 103 | if not client_name then 104 | return nil 105 | end 106 | 107 | local icon_info = M.gtk_theme:lookup_icon(client_name, beautiful.icon_size[3], 0) 108 | if icon_info then 109 | local icon_path = icon_info:get_filename() 110 | if icon_path then 111 | return icon_path 112 | end 113 | end 114 | 115 | return nil 116 | end 117 | 118 | function M.get_gicon_path(gicon) 119 | if not gicon then 120 | return nil 121 | end 122 | 123 | local info = M.gtk_theme:lookup_by_gicon(gicon, beautiful.icon_size[3], 0) 124 | if info then 125 | return info:get_filename() 126 | end 127 | end 128 | 129 | return M 130 | -------------------------------------------------------------------------------- /keys.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local hotkeys_popup = require("awful.hotkeys_popup") 3 | local gears = require("gears") 4 | local config = require("config") 5 | 6 | local modkey = config.modkey 7 | local apps = config.apps 8 | 9 | -- General Awesome keys 10 | awful.keyboard.append_global_keybindings({ 11 | awful.key({ modkey }, "i", hotkeys_popup.show_help, { description = "show help", group = "awesome" }), 12 | awful.key({ modkey, "Control" }, "r", awesome.restart, { description = "reload awesome", group = "awesome" }), 13 | awful.key({ modkey, "Shift" }, "q", awesome.quit, { description = "quit awesome", group = "awesome" }), 14 | awful.key({ modkey }, "Return", function() 15 | awful.spawn(apps.terminal) 16 | end, { description = "open a terminal", group = "launcher" }), 17 | awful.key({ modkey }, "p", function() 18 | awesome.emit_signal("launcher::toggle") 19 | end, { description = "show the menubar", group = "launcher" }), 20 | awful.key({ modkey, "Shift" }, "s", function() 21 | awful.spawn.with_shell("sh " .. gears.filesystem.get_configuration_dir() .. "screenshot area") 22 | end, { description = "screenshot selection", group = "launcher" }), 23 | awful.key({}, "Print", function() 24 | awful.spawn.with_shell("sh " .. gears.filesystem.get_configuration_dir() .. "screenshot full") 25 | end, { description = "screenshot screen", group = "launcher" }), 26 | awful.key({ "Mod1", "Shift" }, "x", function() 27 | awesome.emit_signal("lockscreen::toggle") 28 | end, { description = "lock screen", group = "launcher" }), 29 | awful.key({ 30 | modifiers = {}, 31 | key = "XF86MonBrightnessUp", 32 | description = "brightness up", 33 | group = "launcher", 34 | on_press = function() 35 | awesome.emit_signal("brightness::osd") 36 | awesome.emit_signal("brightness::increase", 5) 37 | end, 38 | }), 39 | awful.key({ 40 | modifiers = {}, 41 | key = "XF86MonBrightnessDown", 42 | description = "brightness down", 43 | group = "launcher", 44 | on_press = function() 45 | awesome.emit_signal("brightness::osd") 46 | awesome.emit_signal("brightness::decrease", 5) 47 | end, 48 | }), 49 | awful.key({ 50 | modifiers = {}, 51 | key = "XF86AudioRaiseVolume", 52 | description = "volume up", 53 | group = "launcher", 54 | on_press = function() 55 | awesome.emit_signal("volume::osd") 56 | awesome.emit_signal("volume::increase", 5) 57 | end, 58 | }), 59 | 60 | awful.key({ 61 | modifiers = {}, 62 | key = "XF86AudioLowerVolume", 63 | description = "volume down", 64 | group = "launcher", 65 | on_press = function() 66 | awesome.emit_signal("volume::osd") 67 | awesome.emit_signal("volume::decrease", 5) 68 | end, 69 | }), 70 | 71 | awful.key({ 72 | modifiers = {}, 73 | key = "XF86AudioMute", 74 | description = "toggle mute", 75 | group = "launcher", 76 | on_press = function() 77 | awesome.emit_signal("volume::osd") 78 | awesome.emit_signal("volume::mute") 79 | end, 80 | }), 81 | }) 82 | 83 | -- Focus related keybindings 84 | awful.keyboard.append_global_keybindings({ 85 | awful.key({ modkey }, "j", function() 86 | awful.client.focus.byidx(1) 87 | end, { description = "focus next by index", group = "client" }), 88 | awful.key({ modkey }, "k", function() 89 | awful.client.focus.byidx(-1) 90 | end, { description = "focus previous by index", group = "client" }), 91 | awful.key({ "Mod1" }, "Tab", function() 92 | awesome.emit_signal("switcher::toggle") 93 | end, { description = "window switcher", group = "client" }), 94 | awful.key({ modkey }, "Left", function() 95 | awful.screen.focus_relative(1) 96 | end, { description = "focus the next screen", group = "screen" }), 97 | awful.key({ modkey }, "Right", function() 98 | awful.screen.focus_relative(-1) 99 | end, { description = "focus the previous screen", group = "screen" }), 100 | }) 101 | 102 | -- Layout related keybindings 103 | awful.keyboard.append_global_keybindings({ 104 | awful.key({ modkey, "Shift" }, "j", function() 105 | awful.client.swap.byidx(1) 106 | end, { description = "swap with next client by index", group = "client" }), 107 | awful.key({ modkey, "Shift" }, "k", function() 108 | awful.client.swap.byidx(-1) 109 | end, { 110 | description = "swap with previous client by index", 111 | group = "client", 112 | }), 113 | awful.key({ modkey }, "u", awful.client.urgent.jumpto, { description = "jump to urgent client", group = "client" }), 114 | awful.key({ modkey }, "l", function() 115 | awful.tag.incmwfact(0.05) 116 | end, { description = "increase master width factor", group = "layout" }), 117 | awful.key({ modkey }, "h", function() 118 | awful.tag.incmwfact(-0.05) 119 | end, { description = "decrease master width factor", group = "layout" }), 120 | awful.key({ modkey, "Shift" }, "h", function() 121 | awful.tag.incnmaster(1, nil, true) 122 | end, { 123 | description = "increase the number of master clients", 124 | group = "layout", 125 | }), 126 | awful.key({ modkey, "Shift" }, "l", function() 127 | awful.tag.incnmaster(-1, nil, true) 128 | end, { 129 | description = "decrease the number of master clients", 130 | group = "layout", 131 | }), 132 | awful.key({ modkey, "Control" }, "h", function() 133 | awful.tag.incncol(1, nil, true) 134 | end, { 135 | description = "increase the number of columns", 136 | group = "layout", 137 | }), 138 | awful.key({ modkey, "Control" }, "l", function() 139 | awful.tag.incncol(-1, nil, true) 140 | end, { 141 | description = "decrease the number of columns", 142 | group = "layout", 143 | }), 144 | awful.key({ modkey }, "space", function() 145 | awful.layout.inc(1, awful.screen.focused()) 146 | end, { 147 | description = "cycle layouts", 148 | group = "layout", 149 | }), 150 | }) 151 | 152 | awful.keyboard.append_global_keybindings({ 153 | awful.key({ 154 | modifiers = { modkey }, 155 | keygroup = "numrow", 156 | description = "only view tag", 157 | group = "tag", 158 | on_press = function(index) 159 | local screen = awful.screen.focused() 160 | local tag = screen.tags[index] 161 | if tag then 162 | tag:view_only() 163 | end 164 | end, 165 | }), 166 | awful.key({ 167 | modifiers = { modkey, "Control" }, 168 | keygroup = "numrow", 169 | description = "toggle tag", 170 | group = "tag", 171 | on_press = function(index) 172 | local screen = awful.screen.focused() 173 | local tag = screen.tags[index] 174 | if tag then 175 | awful.tag.viewtoggle(tag) 176 | end 177 | end, 178 | }), 179 | awful.key({ 180 | modifiers = { modkey, "Shift" }, 181 | keygroup = "numrow", 182 | description = "move focused client to tag", 183 | group = "tag", 184 | on_press = function(index) 185 | if client.focus then 186 | local tag = client.focus.screen.tags[index] 187 | if tag then 188 | client.focus:move_to_tag(tag) 189 | end 190 | end 191 | end, 192 | }), 193 | awful.key({ 194 | modifiers = { modkey, "Control", "Shift" }, 195 | keygroup = "numrow", 196 | description = "toggle focused client on tag", 197 | group = "tag", 198 | on_press = function(index) 199 | if client.focus then 200 | local tag = client.focus.screen.tags[index] 201 | if tag then 202 | client.focus:toggle_tag(tag) 203 | end 204 | end 205 | end, 206 | }), 207 | awful.key({ 208 | modifiers = { modkey }, 209 | keygroup = "numpad", 210 | description = "select layout directly", 211 | group = "layout", 212 | on_press = function(index) 213 | local t = awful.screen.focused().selected_tag 214 | if t then 215 | t.layout = t.layouts[index] or t.layout 216 | end 217 | end, 218 | }), 219 | }) 220 | 221 | awful.mouse.append_global_mousebindings({ 222 | awful.button({}, 4, awful.tag.viewprev), 223 | awful.button({}, 5, awful.tag.viewnext), 224 | }) 225 | client.connect_signal("request::default_mousebindings", function() 226 | awful.mouse.append_client_mousebindings({ 227 | awful.button({}, 1, function(c) 228 | c:activate({ context = "mouse_click" }) 229 | end), 230 | awful.button({ modkey }, 1, function(c) 231 | c:activate({ context = "mouse_click", action = "mouse_move" }) 232 | end), 233 | awful.button({ modkey }, 3, function(c) 234 | c:activate({ context = "mouse_click", action = "mouse_resize" }) 235 | end), 236 | }) 237 | end) 238 | 239 | client.connect_signal("request::default_keybindings", function() 240 | awful.keyboard.append_client_keybindings({ 241 | awful.key({ modkey }, "f", function(c) 242 | c.fullscreen = not c.fullscreen 243 | c:raise() 244 | end, { description = "toggle fullscreen", group = "client" }), 245 | awful.key({ modkey, "Shift" }, "c", function(c) 246 | c:kill() 247 | end, { description = "close", group = "client" }), 248 | awful.key( 249 | { modkey, "Shift" }, 250 | "space", 251 | awful.client.floating.toggle, 252 | { description = "toggle floating", group = "client" } 253 | ), 254 | awful.key({ modkey }, "z", function(c) 255 | local master = awful.client.getmaster() 256 | c:swap(master) 257 | master:activate() 258 | end, { description = "move to master", group = "client" }), 259 | awful.key({ modkey }, "o", function(c) 260 | c:move_to_screen() 261 | end, { description = "move to screen", group = "client" }), 262 | awful.key({ modkey }, "t", function(c) 263 | c.ontop = not c.ontop 264 | end, { description = "toggle keep on top", group = "client" }), 265 | awful.key({ modkey }, "m", function(c) 266 | c.maximized = not c.maximized 267 | c:raise() 268 | end, { description = "(un)maximize", group = "client" }), 269 | awful.key({ modkey, "Control" }, "m", function(c) 270 | c.maximized_vertical = not c.maximized_vertical 271 | c:raise() 272 | end, { description = "(un)maximize vertically", group = "client" }), 273 | awful.key({ modkey, "Shift" }, "m", function(c) 274 | c.maximized_horizontal = not c.maximized_horizontal 275 | c:raise() 276 | end, { description = "(un)maximize horizontally", group = "client" }), 277 | }) 278 | end) 279 | -------------------------------------------------------------------------------- /layouts/cornerne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/cornerne.png -------------------------------------------------------------------------------- /layouts/cornernew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/cornernew.png -------------------------------------------------------------------------------- /layouts/cornernw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/cornernw.png -------------------------------------------------------------------------------- /layouts/cornernww.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/cornernww.png -------------------------------------------------------------------------------- /layouts/cornerse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/cornerse.png -------------------------------------------------------------------------------- /layouts/cornersew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/cornersew.png -------------------------------------------------------------------------------- /layouts/cornersw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/cornersw.png -------------------------------------------------------------------------------- /layouts/cornersww.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/cornersww.png -------------------------------------------------------------------------------- /layouts/dwindle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/dwindle.png -------------------------------------------------------------------------------- /layouts/dwindlew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/dwindlew.png -------------------------------------------------------------------------------- /layouts/fairh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/fairh.png -------------------------------------------------------------------------------- /layouts/fairhw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/fairhw.png -------------------------------------------------------------------------------- /layouts/fairv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/fairv.png -------------------------------------------------------------------------------- /layouts/fairvw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/fairvw.png -------------------------------------------------------------------------------- /layouts/floating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/floating.png -------------------------------------------------------------------------------- /layouts/floatingw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/floatingw.png -------------------------------------------------------------------------------- /layouts/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/fullscreen.png -------------------------------------------------------------------------------- /layouts/fullscreenw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/fullscreenw.png -------------------------------------------------------------------------------- /layouts/magnifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/magnifier.png -------------------------------------------------------------------------------- /layouts/magnifierw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/magnifierw.png -------------------------------------------------------------------------------- /layouts/max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/max.png -------------------------------------------------------------------------------- /layouts/maxw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/maxw.png -------------------------------------------------------------------------------- /layouts/spiral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/spiral.png -------------------------------------------------------------------------------- /layouts/spiralw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/spiralw.png -------------------------------------------------------------------------------- /layouts/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/tile.png -------------------------------------------------------------------------------- /layouts/tilebottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/tilebottom.png -------------------------------------------------------------------------------- /layouts/tilebottomw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/tilebottomw.png -------------------------------------------------------------------------------- /layouts/tileleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/tileleft.png -------------------------------------------------------------------------------- /layouts/tileleftw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/tileleftw.png -------------------------------------------------------------------------------- /layouts/tiletop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/tiletop.png -------------------------------------------------------------------------------- /layouts/tiletopw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/tiletopw.png -------------------------------------------------------------------------------- /layouts/tilew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/layouts/tilew.png -------------------------------------------------------------------------------- /liblua_pam.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/liblua_pam.so -------------------------------------------------------------------------------- /notifications.lua: -------------------------------------------------------------------------------- 1 | local naughty = require("naughty") 2 | local awful = require("awful") 3 | local wibox = require("wibox") 4 | local gears = require("gears") 5 | local beautiful = require("beautiful") 6 | local rubato = require("rubato") 7 | local helpers = require("helpers") 8 | 9 | naughty.connect_signal("request::display", function(n) 10 | n.resident = true 11 | n.position = "top_right" 12 | 13 | local timeout = n.timeout or 20 14 | n.timeout = nil 15 | 16 | local close_icon = wibox.widget({ 17 | valign = "center", 18 | halign = "center", 19 | forced_width = beautiful.icon_size[1], 20 | forced_height = beautiful.icon_size[1], 21 | markup = "", 22 | font = beautiful.font_icon, 23 | widget = wibox.widget.textbox 24 | }) 25 | 26 | local close_button = helpers.add_bg1(close_icon) 27 | close_button.fg = beautiful.red 28 | 29 | local icon = nil 30 | if n.clients[1] ~= nil then 31 | icon = wibox.widget({ 32 | client = n.clients[1], 33 | forced_height = beautiful.icon_size[0], 34 | forced_width = beautiful.icon_size[0], 35 | widget = awful.widget.clienticon 36 | }) 37 | elseif n.app_icon then 38 | icon = wibox.widget({ 39 | image = n.app_icon, 40 | resize = true, 41 | forced_height = beautiful.icon_size[0], 42 | forced_width = beautiful.icon_size[0], 43 | clip_shape = helpers.rrect(), 44 | widget = wibox.widget.imagebox 45 | }) 46 | end 47 | 48 | local image = nil 49 | if n.icon then 50 | image = wibox.widget({ 51 | image = n.icon, 52 | resize = true, 53 | forced_height = beautiful.icon_size[3], 54 | forced_width = beautiful.icon_size[3], 55 | halign = "right", 56 | valign = "center", 57 | clip_shape = helpers.rrect(), 58 | widget = wibox.widget.imagebox 59 | }) 60 | end 61 | 62 | local title = wibox.widget({ 63 | layout = wibox.container.scroll.horizontal, 64 | step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 65 | fps = 60, 66 | speed = 75, 67 | { 68 | halign = "left", 69 | valign = "center", 70 | markup = "" .. n.title .. "", 71 | widget = wibox.widget.textbox 72 | } 73 | }) 74 | 75 | title:pause() 76 | 77 | local message = wibox.widget({ 78 | layout = wibox.container.scroll.horizontal, 79 | step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 80 | fps = 60, 81 | speed = 75, 82 | { 83 | halign = "left", 84 | valign = "center", 85 | markup = n.message, 86 | widget = wibox.widget.textbox 87 | } 88 | }) 89 | 90 | message:pause() 91 | 92 | local app_name = wibox.widget({ 93 | valign = "center", 94 | halign = "left", 95 | markup = "" .. n.app_name .. "", 96 | widget = wibox.widget.textbox 97 | }) 98 | 99 | local progressbar = wibox.widget({ 100 | value = 0.5, 101 | max_value = 1, 102 | color = beautiful.blue, 103 | background_color = beautiful.bg1, 104 | forced_height = beautiful.dpi(3), 105 | shape = helpers.rrect(), 106 | widget = wibox.widget.progressbar 107 | }) 108 | 109 | local timed = rubato.timed({ 110 | duration = timeout, 111 | pos = 0, 112 | rate = 20, 113 | clamp_position = true, 114 | subscribed = function(pos) 115 | progressbar.value = pos 116 | if pos == 1 then 117 | n:destroy(naughty.notification_closed_reason.dismissed_by_user) 118 | end 119 | end 120 | }) 121 | 122 | timed.target = 1 123 | 124 | close_button:buttons(gears.table.join(awful.button({}, 1, function() 125 | n:destroy(naughty.notification_closed_reason.dismissed_by_user) 126 | end))) 127 | 128 | local actions = wibox.widget({ 129 | notification = n, 130 | base_layout = wibox.widget({ 131 | spacing = beautiful.margin[1], 132 | layout = wibox.layout.flex.horizontal 133 | }), 134 | widget_template = { 135 | { 136 | id = "text_role", 137 | valign = "center", 138 | halign = "center", 139 | widget = wibox.widget.textbox 140 | }, 141 | shape = helpers.rrect(), 142 | bg = beautiful.blue, 143 | fg = beautiful.bg0, 144 | forced_height = beautiful.dpi(30), 145 | visible = n.actions and #n.actions > 0, 146 | widget = wibox.container.background 147 | }, 148 | style = { 149 | underline_normal = false, 150 | underline_selected = true 151 | }, 152 | widget = naughty.list.actions 153 | }) 154 | local time = wibox.widget({ 155 | markup = os.date("%I:%M %p"), 156 | halign = "right", 157 | valign = "center", 158 | widget = wibox.widget.textbox 159 | }) 160 | 161 | local title_bar = helpers.add_bg1(helpers.add_margin(wibox.widget({ 162 | { 163 | { 164 | icon, 165 | valign = "center", 166 | halign = "center", 167 | layout = wibox.container.place, 168 | }, 169 | app_name, 170 | spacing = beautiful.margin[0], 171 | layout = wibox.layout.fixed.horizontal 172 | }, 173 | nil, 174 | { 175 | time, 176 | close_button, 177 | spacing = beautiful.margin[0], 178 | layout = wibox.layout.fixed.horizontal 179 | }, 180 | layout = wibox.layout.align.horizontal 181 | }), beautiful.margin[1], beautiful.margin[0])) 182 | title_bar.shape = nil 183 | 184 | local body = helpers.add_bg0(helpers.add_margin(wibox.widget({ 185 | { 186 | image, 187 | { 188 | title, 189 | message, 190 | spacing = beautiful.margin[1], 191 | layout = wibox.layout.fixed.vertical 192 | }, 193 | spacing = beautiful.margin[1], 194 | fill_space = true, 195 | layout = wibox.layout.fixed.horizontal 196 | }, 197 | actions, 198 | layout = wibox.layout.fixed.vertical 199 | }), beautiful.margin[1], beautiful.margin[1])) 200 | body.shape = nil 201 | 202 | local widget = naughty.layout.box({ 203 | notification = n, 204 | type = "notification", 205 | shape = helpers.rrect(), 206 | minimum_width = beautiful.dpi(400), 207 | maximum_width = beautiful.dpi(400), 208 | maximum_height = beautiful.dpi(150), 209 | widget_template = { 210 | title_bar, 211 | progressbar, 212 | body, 213 | layout = wibox.layout.fixed.vertical 214 | }, 215 | bg = beautiful.bg0, 216 | border_color = beautiful.border_color_active, 217 | border_width = beautiful.border_width, 218 | layout = naughty.container.background 219 | }) 220 | 221 | widget.buttons = {} 222 | 223 | widget:connect_signal("mouse::enter", function() 224 | title:continue() 225 | message:continue() 226 | timed.pause = true 227 | end) 228 | 229 | widget:connect_signal("mouse::leave", function() 230 | title:pause() 231 | message:pause() 232 | timed.pause = false 233 | end) 234 | end) 235 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danxliu/awm/d2d01e39107a9f5ae585108502dae4f490bee79e/preview.png -------------------------------------------------------------------------------- /rc.lua: -------------------------------------------------------------------------------- 1 | pcall(require, "luarocks.loader") 2 | 3 | local awful = require("awful") 4 | local gears = require("gears") 5 | require("awful.autofocus") 6 | 7 | -- Beautiful 8 | require("themes") 9 | 10 | -- Signals 11 | require("signals") 12 | 13 | --Daemons 14 | require("daemons") 15 | 16 | -- Components 17 | require("components") 18 | 19 | -- Keys 20 | require("keys") 21 | 22 | -- Client rules 23 | require("rules") 24 | 25 | -- Notifications 26 | require("notifications") 27 | 28 | -- Autostart Applications 29 | awful.spawn.single_instance("xfsettingsd", false) 30 | awful.spawn.single_instance("picom", false) 31 | awful.spawn.single_instance("/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1", false) 32 | 33 | -- Run garbage collector regularly to prevent memory leaks 34 | gears.timer { 35 | timeout = 30, 36 | autostart = true, 37 | callback = function() collectgarbage("collect") end 38 | } 39 | -------------------------------------------------------------------------------- /rules.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local ruled = require("ruled") 3 | ruled.client.connect_signal("request::rules", function() 4 | -- All clients will match this rule. 5 | ruled.client.append_rule({ 6 | id = "global", 7 | rule = {}, 8 | properties = { 9 | focus = awful.client.focus.filter, 10 | raise = true, 11 | screen = awful.screen.preferred, 12 | placement = awful.placement.no_overlap + awful.placement.no_offscreen, 13 | }, 14 | }) 15 | 16 | -- Floating clients. 17 | ruled.client.append_rule({ 18 | id = "floating", 19 | rule_any = { 20 | instance = { "copyq", "pinentry" }, 21 | class = { 22 | "Nemo", 23 | "Blueman-manager", 24 | "Gpick", 25 | }, 26 | name = { 27 | "Event Tester", 28 | }, 29 | role = { 30 | "AlarmWindow", 31 | "ConfigManager", 32 | "pop-up", 33 | }, 34 | }, 35 | properties = { floating = true }, 36 | }) 37 | 38 | -- Add titlebars to normal clients and dialogs 39 | ruled.client.append_rule({ 40 | id = "titlebars", 41 | rule_any = { 42 | type = { "normal", "dialog" }, 43 | }, 44 | properties = { titlebars_enabled = true }, 45 | }) 46 | end) 47 | -------------------------------------------------------------------------------- /screenshot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o pipefail 3 | 4 | <> $_LOG_FILE_ 2>&1 50 | exit 1 51 | fi 52 | } 53 | 54 | # Check save directory 55 | # Create it if it doesn't exist 56 | function check_dir() { 57 | if [[ ! -d "$_SCREENSHOT_DIR_" || ! -d "$_ORIGINAL_DIR_" ]]; then 58 | mkdir -p "$_SCREENSHOT_DIR_" 59 | mkdir -p "$_ORIGINAL_DIR_" 60 | fi 61 | } 62 | 63 | function get_latest_img() { 64 | _LATEST_IMAGE_=$(/bin/ls -th $_SCREENSHOT_DIR_ | grep -vE '.screensht.png$' | grep -E '.png$' | head -n 1) 65 | 66 | if [[ $( echo "$_LATEST_IMAGE_" | wc -w ) -eq 0 ]]; then 67 | exit 1 68 | else 69 | _LATEST_IMAGE_="$_SCREENSHOT_DIR_/$_LATEST_IMAGE_" 70 | fi 71 | } 72 | 73 | function convert() { 74 | _target_file_=$( echo "$_LATEST_IMAGE_" | sed 's/.png/.screensht.png/g' ) 75 | 76 | if [[ $_BORDER_SIZE_ -ge 3 ]]; then 77 | magick convert "$_LATEST_IMAGE_" \ 78 | -format 'roundrectangle 1,1 %[fx:w+4],%[fx:h+4] '"$_ROUNDED_CORNER_"','"$_ROUNDED_CORNER_"''\ 79 | info: > $_SCREENSHOT_DIR_/_rounded_.mvg 80 | check 81 | 82 | magick convert "$_LATEST_IMAGE_" -border $_BORDER_SIZE_ -alpha transparent \ 83 | -background none -fill white -stroke none -strokewidth 0 \ 84 | -draw "@"$_SCREENSHOT_DIR_"/_rounded_.mvg" $_SCREENSHOT_DIR_/_rounded_mask_.png >> $_LOG_FILE_ 2>&1 85 | check 86 | 87 | magick convert "$_LATEST_IMAGE_" -border $_BORDER_SIZE_ -alpha transparent \ 88 | -background none -fill none -stroke $_FG_COLOR_ -strokewidth $_BORDER_SIZE_ \ 89 | -draw "@"$_SCREENSHOT_DIR_"/_rounded_.mvg" $_SCREENSHOT_DIR_/_rounded_overlay_.png >> $_LOG_FILE_ 2>&1 90 | check 91 | 92 | magick convert "$_LATEST_IMAGE_" -alpha set -bordercolor none -border $_BORDER_SIZE_ \ 93 | $_SCREENSHOT_DIR_/_rounded_mask_.png -compose DstIn -composite \ 94 | $_SCREENSHOT_DIR_/_rounded_overlay_.png -compose Over -composite \ 95 | "$_target_file_" >> $_LOG_FILE_ 2>&1 && \ 96 | rm -f $_SCREENSHOT_DIR_/_rounded_* 97 | check 98 | else 99 | magick convert "$_LATEST_IMAGE_" \( +clone -alpha extract -draw 'fill black polygon 0,0 0,'"$_ROUNDED_CORNER_"' '"$_ROUNDED_CORNER_"',0 fill white circle '"$_ROUNDED_CORNER_"','"$_ROUNDED_CORNER_"' '"$_ROUNDED_CORNER_"',0' \ 100 | \( +clone -flip \) -compose Multiply -composite \ 101 | \( +clone -flop \) -compose Multiply -composite \ 102 | \) -alpha off -compose CopyOpacity -composite -compose over "$_target_file_" >> $_LOG_FILE_ 2>&1 103 | check 104 | fi 105 | 106 | magick convert "$_target_file_" \( +clone -background black -shadow $_SHADOW_SIZE_ \) +swap -background none -layers merge +repage "$_target_file_" >> $_LOG_FILE_ 2>&1 \ 107 | && magick convert "$_target_file_" -bordercolor $_BG_COLOR_ -border $_BG_SIZE_ "$_target_file_" >> $_LOG_FILE_ 2>&1 108 | check 109 | 110 | echo -en " $_AUTHOR_NAME_ " | magick convert "$_target_file_" -gravity ${_AUTHOR_POST_[0]} -pointsize $_FONT_SIZE_ -fill $_AUTHOR_COLOR_ -undercolor none -font $_FONT_ -annotate ${_AUTHOR_POST_[1]} @- "$_target_file_" \ 111 | >> $_LOG_FILE_ 2>&1 && magick convert "$_target_file_" -gravity South -chop 0x$(( $_BG_SIZE_ / 2 )) "$_target_file_" >> $_LOG_FILE_ 2>&1 112 | check 113 | 114 | magick convert "$_target_file_" -gravity North -background $_BG_COLOR_ -splice 0x$(( $_BG_SIZE_ / 2 )) "$_target_file_" >> $_LOG_FILE_ 2>&1 115 | check 116 | 117 | magick convert "$_target_file_" -profile /usr/share/color/icc/colord/sRGB.icc "$_target_file_" >> $_LOG_FILE_ 2>&1 118 | check 119 | } 120 | 121 | function summary() { 122 | _runtime_job_=$(($2-$1)) 123 | hours=$((_runtime_job_ / 3600)); minutes=$(( (_runtime_job_ % 3600) / 60 )); seconds=$(( (_runtime_job_ % 3600) % 60 )) 124 | 125 | if [[ $3 != "failed" ]]; then 126 | xclip -selection clipboard -t image/png -i $_target_file_ >> $_LOG_FILE_ 2>&1 127 | 128 | awesome-client " 129 | -- IMPORTANT NOTE: THIS PART OF THE SCRIPT IS LUA! 130 | naughty = require('naughty') 131 | awful = require('awful') 132 | beautiful = require('beautiful') 133 | dpi = beautiful.xresources.apply_dpi 134 | 135 | local open_image = naughty.action { 136 | name = 'Open', 137 | icon_only = false, 138 | } 139 | 140 | local delete_image = naughty.action { 141 | name = 'Delete', 142 | icon_only = false, 143 | } 144 | 145 | -- Execute the callback when 'Open' is pressed 146 | open_image:connect_signal('invoked', function() 147 | awful.spawn('xdg-open ' .. '${_target_file_}', false) 148 | end) 149 | 150 | -- Execute the callback when 'Delete' is pressed 151 | delete_image:connect_signal('invoked', function() 152 | awful.spawn('gio trash ' .. '${_target_file_}', false) 153 | end) 154 | 155 | -- Show notification 156 | naughty.notification ({ 157 | app_name = 'Screenshot Tool', 158 | icon = '${_target_file_}', 159 | timeout = 10, 160 | title = 'Screenshot Captured', 161 | message = '${_notif_message_}', 162 | actions = { open_image, delete_image } 163 | }) 164 | " 165 | fi 166 | } 167 | 168 | function main() { 169 | check_dir 170 | 171 | rm -f $_LOG_FILE_ 172 | _start_job_=$(date +%s) 173 | 174 | _screenshot_command_="$1" 175 | _notif_message_="$2" 176 | 177 | $_screenshot_command_ $_SCREENSHOT_DIR_\/$_start_job_.png> /dev/null 2>&1 178 | check 179 | 180 | get_latest_img 181 | convert 182 | 183 | mv $_LATEST_IMAGE_ $_ORIGINAL_DIR_ 184 | 185 | _end_job_=$(date +%s) 186 | summary $_start_job_ $_end_job_ 187 | } 188 | 189 | # Check the args passed 190 | if [ -z "$1" ] || ([ "$1" != 'full' ] && [ "$1" != 'area' ]); 191 | then 192 | echo " 193 | Requires an argument: 194 | area - Area screenshot 195 | full - Fullscreen screenshot 196 | 197 | Example: 198 | ./screensht area 199 | ./screensht full 200 | " 201 | elif [ "$1" = 'full' ]; 202 | then 203 | msg="Full screenshot saved and copied to clipboard!" 204 | main 'maim -u -m 10' "${msg}" 205 | elif [ "$1" = 'area' ]; 206 | then 207 | msg='Area screenshot saved and copied to clipboard!' 208 | main 'maim -u -m 10 -s -b 2' "${msg}" 209 | fi 210 | -------------------------------------------------------------------------------- /signals/error.lua: -------------------------------------------------------------------------------- 1 | local naughty = require("naughty") 2 | 3 | naughty.connect_signal("request::display_error", function(message, startup) 4 | naughty.notification({ 5 | urgency = "critical", 6 | title = "An error occured" .. (startup and " during startup." or "."), 7 | message = message 8 | }) 9 | end) 10 | -------------------------------------------------------------------------------- /signals/init.lua: -------------------------------------------------------------------------------- 1 | require("signals.tags") 2 | require("signals.titlebars") 3 | require("signals.error") 4 | -------------------------------------------------------------------------------- /signals/tags.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | 3 | -- Tags 4 | tag.connect_signal("request::default_layouts", function() 5 | awful.layout.append_default_layouts({ awful.layout.suit.tile, awful.layout.suit.floating, 6 | awful.layout.suit.spiral, awful.layout.suit.spiral.dwindle -- awful.layout.suit.max, 7 | }) 8 | end) 9 | 10 | screen.connect_signal("request::desktop_decoration", function(s) 11 | awful.tag({ "1", "2", "3", "4", "5" }, s, awful.layout.layouts[1]) 12 | end) 13 | -------------------------------------------------------------------------------- /signals/titlebars.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local wibox = require("wibox") 3 | local beautiful = require("beautiful") 4 | local helpers = require("helpers") 5 | local naughty = require("naughty") 6 | 7 | -- Titlebars 8 | client.connect_signal("request::titlebars", function(c) 9 | local buttons = { awful.button({}, 1, function() 10 | c:activate({ 11 | context = "titlebar", 12 | action = "mouse_move" 13 | }) 14 | end), awful.button({}, 3, function() 15 | c:activate({ 16 | context = "titlebar", 17 | action = "mouse_resize" 18 | }) 19 | end) } 20 | 21 | local close_icon = wibox.widget({ 22 | valign = "center", 23 | halign = "center", 24 | markup = "", 25 | forced_width = beautiful.icon_size[1], 26 | forced_height = beautiful.icon_size[1], 27 | font = beautiful.font_icon, 28 | widget = wibox.widget.textbox 29 | }) 30 | local close_button = helpers.add_bg(close_icon) 31 | close_button:buttons({ awful.button({}, 1, function() 32 | c:kill() 33 | end) }) 34 | 35 | local minimize_icon = wibox.widget({ 36 | valign = "center", 37 | halign = "center", 38 | markup = "", 39 | forced_width = beautiful.icon_size[1], 40 | forced_height = beautiful.icon_size[1], 41 | font = beautiful.font_icon, 42 | widget = wibox.widget.textbox 43 | }) 44 | 45 | -- Ok this doesn't work properly, see https://github.com/awesomeWM/awesome/issues/3716 :( 46 | 47 | local minimize_button = helpers.add_bg(minimize_icon) 48 | minimize_button:buttons({ awful.button({}, 1, function() 49 | c.minimized = true 50 | end) }) 51 | 52 | local maximize_icon = wibox.widget({ 53 | valign = "center", 54 | halign = "center", 55 | markup = "", 56 | forced_width = beautiful.icon_size[1], 57 | forced_height = beautiful.icon_size[1], 58 | font = beautiful.font_icon, 59 | widget = wibox.widget.textbox 60 | }) 61 | 62 | local maximize_button = helpers.add_bg(maximize_icon) 63 | maximize_button:buttons({ awful.button({}, 1, function() 64 | c.maximized = not c.maximized 65 | end) }) 66 | 67 | local left_widget = helpers.add_margin(wibox.widget({ 68 | { 69 | image = c.icon or helpers.get_icon(c.class) or beautiful.icon_default, 70 | resize = true, 71 | valign = "center", 72 | halign = "center", 73 | widget = wibox.widget.imagebox, 74 | }, 75 | { 76 | halign = "left", 77 | valign = "center", 78 | markup = "" .. (c.class or c.name or "unknown"):gsub("^%l", string.upper) .. "", 79 | widget = wibox.widget.textbox, 80 | font = beautiful.font_small 81 | }, 82 | buttons = buttons, 83 | spacing = beautiful.margin[1], 84 | layout = wibox.layout.fixed.horizontal 85 | })) 86 | 87 | local right_widget = helpers.add_margin(wibox.widget({ 88 | -- minimize_button, 89 | maximize_button, 90 | close_button, 91 | spacing = beautiful.margin[0], 92 | layout = wibox.layout.fixed.horizontal 93 | })) 94 | 95 | local titlebar_widget = helpers.add_margin(wibox.widget({ 96 | left_widget, 97 | nil, 98 | right_widget, 99 | layout = wibox.layout.align.horizontal 100 | })) 101 | 102 | local titlebar = awful.titlebar(c, { 103 | size = beautiful.titlebar_height 104 | }) 105 | 106 | titlebar.widget = titlebar_widget 107 | 108 | c.shape = helpers.rrect() 109 | 110 | client.connect_signal("focus", function() 111 | if c.active then 112 | minimize_button.fg = beautiful.orange 113 | maximize_button.fg = beautiful.green 114 | close_button.fg = beautiful.red 115 | else 116 | minimize_button.fg = beautiful.bg1 117 | maximize_button.fg = beautiful.bg1 118 | close_button.fg = beautiful.bg1 119 | end 120 | end) 121 | end) 122 | 123 | client.connect_signal("manage", function(c) 124 | if not awesome.startup then 125 | awful.client.setslave(c) 126 | end 127 | 128 | if awesome.startup and not c.size_hints.user_position and not c.size_hints.program_position then 129 | awful.placement.no_offscreen(c) 130 | end 131 | end) 132 | 133 | client.connect_signal("property::maximized", function(c) 134 | if c.maximized then 135 | c.shape = nil 136 | else 137 | c.shape = helpers.rrect() 138 | end 139 | end) 140 | 141 | client.connect_signal("property::fullscreen", function(c) 142 | if c.fullscreen then 143 | c.shape = nil 144 | else 145 | c.shape = helpers.rrect() 146 | end 147 | end) 148 | -------------------------------------------------------------------------------- /themes.lua: -------------------------------------------------------------------------------- 1 | local beautiful = require("beautiful") 2 | local gears = require("gears") 3 | local dpi = beautiful.xresources.apply_dpi 4 | local helpers = require("helpers") 5 | 6 | awesome.set_preferred_icon_size(64) 7 | 8 | local M = {} 9 | 10 | M.themes_path = gears.filesystem.get_configuration_dir() 11 | 12 | M.wallpaper = M.themes_path .. "backgrounds/mountains_1.jpg" 13 | screen.connect_signal("request::desktop_decoration", function(s) 14 | gears.wallpaper.maximized(M.wallpaper, s) 15 | end) 16 | 17 | -- Converts the input to dpi 18 | function M.dpi(i) 19 | return dpi(i) 20 | end 21 | 22 | M.font = "RobotoCondensed 14" 23 | M.font_medium = "RobotoCondensed 48" 24 | M.font_large = "RobotoCondensed 64" 25 | M.font_icon = "Material-Design-Iconic-Font 14" 26 | M.font_icon_large = "Material-Design-Iconic-Font 24" 27 | M.radius = M.dpi(8) 28 | 29 | M.margin = {} 30 | M.margin[0] = M.dpi(4) 31 | M.margin[1] = M.dpi(8) 32 | M.margin[2] = M.dpi(12) 33 | M.margin[3] = M.dpi(16) 34 | M.margin[4] = M.dpi(32) 35 | 36 | M.icon_size = {} 37 | M.icon_size[0] = M.dpi(16) 38 | M.icon_size[1] = M.dpi(32) 39 | M.icon_size[2] = M.dpi(48) 40 | M.icon_size[3] = M.dpi(64) 41 | M.icon_size[4] = M.dpi(128) 42 | 43 | -- Github Dark Theme 44 | M.bg0 = "#0d1117" 45 | M.bg1 = "#161b22" 46 | M.bg2 = "#21262d" 47 | M.fg0 = "#ecf2f8" 48 | M.fg1 = "#c6cdd5" 49 | M.fg2 = "#89929b" 50 | 51 | M.red = "#fa7970" 52 | M.orange = "#faa356" 53 | M.green = "#7ce38b" 54 | M.cyan = "#a2d2fb" 55 | M.blue = "#77bdfb" 56 | M.purple = "#cea5fb" 57 | 58 | -- Default colors 59 | M.bg_normal = M.bg0 60 | M.bg_focus = M.bg1 61 | M.bg_urgent = M.bg0 62 | M.bg_minimize = M.bg0 63 | 64 | M.fg_normal = M.fg2 65 | M.fg_focus = M.fg0 66 | M.fg_urgent = M.red 67 | M.fg_minimize = M.fg2 68 | 69 | M.border_color_normal = M.bg0 70 | M.border_color_active = M.bg1 71 | M.border_width = dpi(2) 72 | 73 | -- Configs 74 | M.bar_width = M.dpi(42) 75 | 76 | M.titlebar_height = M.dpi(42) 77 | M.useless_gap = M.dpi(4) 78 | 79 | M.switcher_height = M.dpi(200) 80 | M.switcher_width = M.dpi(200) 81 | 82 | M.notification_bg_normal = M.bg2 83 | M.notification_bg_selected = M.blue 84 | 85 | M.hotkeys_bg = M.bg0 86 | M.hotkeys_fg = M.fg0 87 | M.hotkeys_shape = helpers.rrect() 88 | M.hotkeys_modifiers_fg = M.blue 89 | M.hotkeys_border_width = M.margin[1] 90 | M.hotkeys_border_color = M.bg0 91 | M.hotkeys_label_bg = M.red 92 | M.hotkeys_label_fg = M.bg0 93 | M.hotkeys_group_margin = M.margin[3] 94 | M.hotkeys_font = M.font 95 | M.hotkeys_description_font = M.font 96 | 97 | -- AwesomeWM icon 98 | M.icon_awesome = beautiful.theme_assets.awesome_icon(M.dpi(64), M.fg0, M.bg0) 99 | 100 | M.icon_default = M.themes_path .. "assets/default.svg" 101 | M.icon_music = M.themes_path .. "assets/music.svg" 102 | M.icon_terminal = M.themes_path .. "assets/terminal.svg" 103 | 104 | -- Layout icons 105 | M.layout_fairh = M.themes_path .. "layouts/fairhw.png" 106 | M.layout_fairv = M.themes_path .. "layouts/fairvw.png" 107 | M.layout_floating = M.themes_path .. "layouts/floatingw.png" 108 | M.layout_magnifier = M.themes_path .. "layouts/magnifierw.png" 109 | M.layout_max = M.themes_path .. "layouts/maxw.png" 110 | M.layout_fullscreen = M.themes_path .. "layouts/fullscreenw.png" 111 | M.layout_tilebottom = M.themes_path .. "layouts/tilebottomw.png" 112 | M.layout_tileleft = M.themes_path .. "layouts/tileleftw.png" 113 | M.layout_tile = M.themes_path .. "layouts/tilew.png" 114 | M.layout_tiletop = M.themes_path .. "layouts/tiletopw.png" 115 | M.layout_spiral = M.themes_path .. "layouts/spiralw.png" 116 | M.layout_dwindle = M.themes_path .. "layouts/dwindlew.png" 117 | M.layout_cornernw = M.themes_path .. "layouts/cornernww.png" 118 | M.layout_cornerne = M.themes_path .. "layouts/cornernew.png" 119 | M.layout_cornersw = M.themes_path .. "layouts/cornersww.png" 120 | M.layout_cornerse = M.themes_path .. "layouts/cornersew.png" 121 | 122 | beautiful.init(M) 123 | 124 | return M 125 | -------------------------------------------------------------------------------- /wibox/layout/init.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------- 2 | --- Collection of layouts that can be used in widget boxes. 3 | -- 4 | -- @author Uli Schlachter 5 | -- @copyright 2010 Uli Schlachter 6 | -- @classmod wibox.layout 7 | --------------------------------------------------------------------------- 8 | local base = require("wibox.widget.base") 9 | 10 | return setmetatable({ 11 | fixed = require("wibox.layout.fixed"); 12 | align = require("wibox.layout.align"); 13 | flex = require("wibox.layout.flex"); 14 | rotate = require("wibox.layout.rotate"); 15 | manual = require("wibox.layout.manual"); 16 | margin = require("wibox.layout.margin"); 17 | mirror = require("wibox.layout.mirror"); 18 | constraint = require("wibox.layout.constraint"); 19 | scroll = require("wibox.layout.scroll"); 20 | ratio = require("wibox.layout.ratio"); 21 | stack = require("wibox.layout.stack"); 22 | grid = require("wibox.layout.grid"); 23 | overflow = require("wibox.layout.overflow"); 24 | }, {__call = function(_, args) return base.make_widget_declarative(args) end}) 25 | -------------------------------------------------------------------------------- /wibox/layout/overflow.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------- 2 | -- A layout that allows its children to take more space than what's available 3 | -- in the surrounding container. If the content does exceed the available 4 | -- size, a scrollbar is added and scrolling behavior enabled. 5 | -- 6 | -- @DOC_wibox_layout_defaults_overflow_EXAMPLE@ 7 | -- @author Lucas Schwiderski 8 | -- @copyright 2021 Lucas Schwiderski 9 | -- @layoutmod wibox.layout.overflow 10 | -- @supermodule wibox.layout.fixed 11 | --------------------------------------------------------------------------- 12 | local base = require('wibox.widget.base') 13 | local fixed = require('wibox.layout.fixed') 14 | local separator = require('wibox.widget.separator') 15 | local gtable = require('gears.table') 16 | local gshape = require('gears.shape') 17 | local gobject = require('gears.object') 18 | local beautiful = require('beautiful') 19 | local dpi = beautiful.xresources.apply_dpi 20 | local mousegrabber = mousegrabber 21 | 22 | local overflow = { 23 | mt = {} 24 | } 25 | 26 | -- Determine the required space to draw the layout's children and, if necessary, 27 | -- the scrollbar. 28 | function overflow:fit(context, orig_width, orig_height) 29 | local widgets = self._private.widgets 30 | local num_widgets = #widgets 31 | if num_widgets < 1 then 32 | return 0, 0 33 | end 34 | 35 | local width, height = orig_width, orig_height 36 | local scrollbar_width = self._private.scrollbar_width 37 | local scrollbar_enabled = self._private.scrollbar_enabled 38 | local used_in_dir, used_max = 0, 0 39 | local is_y = self._private.dir == "y" 40 | local avail_in_dir = is_y and orig_height or orig_width 41 | 42 | -- Set the direction covered by scrolling to the maximum value 43 | -- to allow widgets to take as much space as they want. 44 | if is_y then 45 | height = math.huge 46 | else 47 | width = math.huge 48 | end 49 | 50 | -- First, determine widget sizes. 51 | -- Only when the content doesn't fit and needs scrolling should 52 | -- we reduce content size to make space for a scrollbar. 53 | for _, widget in ipairs(widgets) do 54 | local w, h = base.fit_widget(self, context, widget, width, height) 55 | 56 | if is_y then 57 | used_max = math.max(used_max, w) 58 | used_in_dir = used_in_dir + h 59 | else 60 | used_max = math.max(used_max, h) 61 | used_in_dir = used_in_dir + w 62 | end 63 | end 64 | 65 | local spacing = self._private.spacing * (num_widgets - 1) 66 | used_in_dir = used_in_dir + spacing 67 | 68 | local need_scrollbar = scrollbar_enabled and used_in_dir > avail_in_dir 69 | 70 | -- Even if `used_max == orig_(width|height)` already, `base.fit_widget` 71 | -- will clamp return values, so we can "overextend" here. 72 | if need_scrollbar then 73 | used_max = used_max + scrollbar_width 74 | end 75 | 76 | if is_y then 77 | return used_max, used_in_dir 78 | else 79 | return used_in_dir, used_max 80 | end 81 | end 82 | 83 | -- Layout children, scrollbar and spacing widgets. 84 | -- Only those widgets that are currently visible will be placed. 85 | function overflow:layout(context, orig_width, orig_height) 86 | local result = {} 87 | local is_y = self._private.dir == "y" 88 | local widgets = self._private.widgets 89 | local avail_in_dir = is_y and orig_height or orig_width 90 | local scrollbar_width = self._private.scrollbar_width 91 | local scrollbar_enabled = self._private.scrollbar_enabled 92 | local scrollbar_position = self._private.scrollbar_position 93 | local width, height = orig_width, orig_height 94 | local widget_x, widget_y = 0, 0 95 | local used_in_dir, used_max = 0, 0 96 | 97 | -- Set the direction covered by scrolling to the maximum value 98 | -- to allow widgets to take as much space as they want. 99 | if is_y then 100 | height = math.huge 101 | else 102 | width = math.huge 103 | end 104 | 105 | -- First, determine widget sizes. 106 | -- Only when the content doesn't fit and needs scrolling should 107 | -- we reduce content size to make space for a scrollbar. 108 | for _, widget in pairs(widgets) do 109 | local w, h = base.fit_widget(self, context, widget, width, height) 110 | 111 | if is_y then 112 | used_max = math.max(used_max, w) 113 | used_in_dir = used_in_dir + h 114 | else 115 | used_max = math.max(used_max, h) 116 | used_in_dir = used_in_dir + w 117 | end 118 | end 119 | 120 | used_in_dir = used_in_dir + self._private.spacing * (#widgets - 1) 121 | 122 | -- Save size for scrolling behavior 123 | self._private.avail_in_dir = avail_in_dir 124 | self._private.used_in_dir = used_in_dir 125 | 126 | local need_scrollbar = used_in_dir > avail_in_dir and scrollbar_enabled 127 | 128 | local scroll_position = self._private.scroll_factor 129 | 130 | if need_scrollbar then 131 | local scrollbar_widget = self._private.scrollbar_widget 132 | local bar_x, bar_y = 0, 0 133 | local bar_w, bar_h 134 | -- The percentage of how much of the content can be visible within 135 | -- the available space 136 | local visible_percent = avail_in_dir / used_in_dir 137 | -- Make scrollbar length reflect `visible_percent` 138 | -- TODO: Apply a default minimum length 139 | local bar_length = math.floor(visible_percent * avail_in_dir) 140 | local bar_pos = (avail_in_dir - bar_length) * self._private.scroll_factor 141 | 142 | if is_y then 143 | bar_w, bar_h = base.fit_widget(self, context, scrollbar_widget, scrollbar_width, bar_length) 144 | bar_y = bar_pos 145 | 146 | if scrollbar_position == "left" then 147 | widget_x = widget_x + bar_w 148 | elseif scrollbar_position == "right" then 149 | bar_x = orig_width - bar_w 150 | end 151 | 152 | self._private.bar_length = bar_h 153 | 154 | width = width - bar_w 155 | else 156 | bar_w, bar_h = base.fit_widget(self, context, scrollbar_widget, bar_length, scrollbar_width) 157 | bar_x = bar_pos 158 | 159 | if scrollbar_position == "top" then 160 | widget_y = widget_y + bar_h 161 | elseif scrollbar_position == "bottom" then 162 | bar_y = orig_height - bar_h 163 | end 164 | 165 | self._private.bar_length = bar_w 166 | 167 | height = height - bar_h 168 | end 169 | 170 | table.insert(result, base.place_widget_at(scrollbar_widget, math.floor(bar_x), math.floor(bar_y), 171 | math.floor(bar_w), math.floor(bar_h))) 172 | end 173 | 174 | local pos, spacing = 0, self._private.spacing 175 | local interval = used_in_dir - avail_in_dir 176 | 177 | local spacing_widget = self._private.spacing_widget 178 | if spacing_widget then 179 | if is_y then 180 | local _ 181 | _, spacing = base.fit_widget(self, context, spacing_widget, width, spacing) 182 | else 183 | spacing = base.fit_widget(self, context, spacing_widget, spacing, height) 184 | end 185 | end 186 | 187 | for i, w in ipairs(widgets) do 188 | local content_x, content_y 189 | local content_w, content_h = base.fit_widget(self, context, w, need_scrollbar and width - self._private.scrollbar_spacing or width, height) 190 | 191 | -- When scrolling down, the content itself moves up -> substract 192 | local scrolled_pos = pos - (scroll_position * interval) 193 | 194 | -- Stop processing completely once we're passed the visible portion 195 | if scrolled_pos > avail_in_dir then 196 | break 197 | end 198 | 199 | if is_y then 200 | content_x, content_y = widget_x, scrolled_pos 201 | pos = pos + content_h + spacing 202 | 203 | if self._private.fill_space then 204 | content_w = width 205 | end 206 | else 207 | content_x, content_y = scrolled_pos, widget_y 208 | pos = pos + content_w + spacing 209 | 210 | if self._private.fill_space then 211 | content_h = height 212 | end 213 | end 214 | 215 | local is_in_view = is_y and (scrolled_pos + content_h > 0) or (scrolled_pos + content_w > 0) 216 | 217 | if is_in_view then 218 | -- Add the spacing widget, but not before the first widget 219 | if i > 1 and spacing_widget then 220 | table.insert(result, 221 | base.place_widget_at(spacing_widget, -- The way how spacing is added for regular widgets 222 | -- and the `spacing_widget` is disconnected: 223 | -- The offset for regular widgets is added to `pos` one 224 | -- iteration _before_ the one where the widget is actually 225 | -- placed. 226 | -- Because of that, the placement for the spacing widget 227 | -- needs to substract that offset to be placed right after 228 | -- the previous regular widget. 229 | math.floor(is_y and content_x or (content_x - spacing)), 230 | math.floor(is_y and (content_y - spacing) or content_y), 231 | math.floor(is_y and content_w or spacing), math.floor(is_y and spacing or content_h))) 232 | end 233 | table.insert(result, base.place_widget_at(w, math.floor(content_x), math.floor(content_y), 234 | math.floor(need_scrollbar and content_w - self._private.scrollbar_spacing or content_w), math.floor(content_h))) 235 | end 236 | end 237 | 238 | return result 239 | end 240 | 241 | function overflow:before_draw_children(_, cr, width, height) 242 | -- Clip drawing for children to the space we're allowed to draw in 243 | cr:rectangle(0, 0, width, height) 244 | cr:clip() 245 | end 246 | 247 | --- The amount of units to advance per scroll event. 248 | -- 249 | -- This affects calls to `scroll` and the default mouse wheel handler. 250 | -- 251 | -- The default is `10`. 252 | -- 253 | -- @property step 254 | -- @tparam number step The step size. 255 | function overflow:set_step(step) 256 | self._private.step = step 257 | -- We don't need to emit enything here, since changing step only really 258 | -- takes effect the next time the user scrolls 259 | end 260 | 261 | --- Scroll the layout's content by `amount * step`. 262 | -- 263 | -- A positive amount scroll down/right, a negative amount scrolls up/left. 264 | -- 265 | -- The amount of units scrolled is affected by `step`. 266 | -- 267 | -- @method overflow:scroll 268 | -- @tparam number amount The amount to scroll by. 269 | -- @emits property::overflow::scroll_factor 270 | -- @emitstparam property::overflow::scroll_factor number scroll_factor The new 271 | -- scroll factor. 272 | -- @emits widget::layout_changed 273 | -- @emits widget::redraw_needed 274 | function overflow:scroll(amount) 275 | if amount == 0 then 276 | return 277 | end 278 | local interval = self._private.used_in_dir 279 | local delta = self._private.step / interval 280 | 281 | local factor = self._private.scroll_factor + (delta * amount) 282 | self:set_scroll_factor(factor) 283 | end 284 | 285 | --- The scroll factor. 286 | -- 287 | -- The scroll factor represents how far the layout's content is currently 288 | -- scrolled. It is represented as a fraction from `0` to `1`, where `0` is the 289 | -- start of the content and `1` is the end. 290 | -- 291 | -- @property scroll_factor 292 | -- @tparam number scroll_factor The scroll factor. 293 | -- @propemits true false 294 | 295 | function overflow:set_scroll_factor(factor) 296 | local current = self._private.scroll_factor 297 | local interval = self._private.used_in_dir - self._private.avail_in_dir 298 | if current == factor -- the content takes less space than what is available, i.e. everything 299 | -- is already visible 300 | or interval <= 0 -- the scroll factor is out of range 301 | or (current <= 0 and factor < 0) or (current >= 1 and factor > 1) then 302 | return 303 | end 304 | 305 | self._private.scroll_factor = math.min(1, math.max(factor, 0)) 306 | 307 | self:emit_signal("widget::layout_changed") 308 | self:emit_signal("property::scroll_factor", factor) 309 | end 310 | 311 | function overflow:get_scroll_factor() 312 | return self._private.scroll_factor 313 | end 314 | 315 | --- The scrollbar width. 316 | -- 317 | -- For horizontal scrollbars, this is the scrollbar height 318 | -- 319 | -- The default is `5`. 320 | -- 321 | -- @DOC_wibox_layout_overflow_scrollbar_width_EXAMPLE@ 322 | -- 323 | -- @property scrollbar_width 324 | -- @tparam number scrollbar_width The scrollbar width. 325 | -- @propemits true false 326 | 327 | function overflow:set_scrollbar_width(width) 328 | if self._private.scrollbar_width == width then 329 | return 330 | end 331 | 332 | self._private.scrollbar_width = width 333 | 334 | self:emit_signal("widget::layout_changed") 335 | self:emit_signal("property::scrollbar_width", width) 336 | end 337 | 338 | --- The scrollbar position. 339 | -- 340 | -- For horizontal scrollbars, this can be `"top"` or `"bottom"`, 341 | -- for vertical scrollbars this can be `"left"` or `"right"`. 342 | -- The default is `"right"`/`"bottom"`. 343 | -- 344 | -- @DOC_wibox_layout_overflow_scrollbar_position_EXAMPLE@ 345 | -- 346 | -- @property scrollbar_position 347 | -- @tparam string scrollbar_position The scrollbar position. 348 | -- @propemits true false 349 | 350 | function overflow:set_scrollbar_position(position) 351 | if self._private.scrollbar_position == position then 352 | return 353 | end 354 | 355 | self._private.scrollbar_position = position 356 | 357 | self:emit_signal("widget::layout_changed") 358 | self:emit_signal("property::scrollbar_position", position) 359 | end 360 | 361 | function overflow:get_scrollbar_position() 362 | return self._private.scrollbar_position 363 | end 364 | 365 | --- The scrollbar visibility. 366 | -- 367 | -- If this is set to `false`, no scrollbar will be rendered, even if the layout's 368 | -- content overflows. Mouse wheel scrolling will work regardless. 369 | -- 370 | -- The default is `true`. 371 | -- 372 | -- @property scrollbar_enabled 373 | -- @tparam boolean scrollbar_enabled The scrollbar visibility. 374 | -- @propemits true false 375 | 376 | function overflow:set_scrollbar_enabled(enabled) 377 | if self._private.scrollbar_enabled == enabled then 378 | return 379 | end 380 | 381 | self._private.scrollbar_enabled = enabled 382 | 383 | self:emit_signal("widget::layout_changed") 384 | self:emit_signal("property::scrollbar_enabled", enabled) 385 | end 386 | 387 | function overflow:get_scrollbar_enabled() 388 | return self._private.scrollbar_enabled 389 | end 390 | 391 | -- Wraps a callback function for `mousegrabber` that is capable of 392 | -- updating the scroll factor. 393 | local function build_grabber(container, initial_x, initial_y, geo) 394 | local is_y = container._private.dir == "y" 395 | local bar_interval = container._private.avail_in_dir - container._private.bar_length 396 | local start_pos = container._private.scroll_factor * bar_interval 397 | local start = is_y and initial_y or initial_x 398 | 399 | -- Calculate a matrix transforming from screen coordinates into widget 400 | -- coordinates. 401 | -- This is required for mouse movement to work when the widget has been 402 | -- transformed by something like `wibox.container.rotate`. 403 | local matrix_from_device = geo.hierarchy:get_matrix_from_device() 404 | local wgeo = geo.drawable.drawable:geometry() 405 | local matrix = matrix_from_device:translate(-wgeo.x, -wgeo.y) 406 | 407 | return function(mouse) 408 | if not mouse.buttons[1] then 409 | return false 410 | end 411 | 412 | local x, y = matrix:transform_point(mouse.x, mouse.y) 413 | local pos = is_y and y or x 414 | container:set_scroll_factor((start_pos + (pos - start)) / bar_interval) 415 | 416 | return true 417 | end 418 | end 419 | 420 | -- Applies a mouse button signal using `build_grabber` to a scrollbar widget. 421 | local function apply_scrollbar_mouse_signal(container, w) 422 | w:connect_signal('button::press', function(_, x, y, button_id, _, geo) 423 | if button_id ~= 1 then 424 | return 425 | end 426 | mousegrabber.run(build_grabber(container, x, y, geo), "fleur") 427 | end) 428 | end 429 | 430 | --- The scrollbar widget. 431 | -- This widget is rendered as the scrollbar element. 432 | -- 433 | -- The default is `wibox.widget.separator{ shape = gears.shape.rectangle }`. 434 | -- 435 | -- @DOC_wibox_layout_overflow_scrollbar_widget_EXAMPLE@ 436 | -- 437 | -- @property scrollbar_widget 438 | -- @tparam widget scrollbar_widget The scrollbar widget. 439 | -- @propemits true false 440 | 441 | function overflow:set_scrollbar_widget(widget) 442 | local w = base.make_widget_from_value(widget) 443 | 444 | apply_scrollbar_mouse_signal(self, w) 445 | 446 | self._private.scrollbar_widget = w 447 | 448 | self:emit_signal("widget::layout_changed") 449 | self:emit_signal("property::scrollbar_widget", widget) 450 | end 451 | 452 | function overflow:get_scrollbar_widget() 453 | return self._private.scrollbar_widget 454 | end 455 | 456 | 457 | function overflow:set_scrollbar_spacing(spacing) 458 | self._private.scrollbar_spacing = spacing 459 | 460 | self:emit_signal("widget::layout_changed") 461 | self:emit_signal("property::scrollbar_spacing", spacing) 462 | end 463 | 464 | function overflow:get_scrollbar_spacing() 465 | return self._private.scrollbar_spacing 466 | end 467 | 468 | local function new(dir, ...) 469 | local ret = fixed[dir](...) 470 | 471 | gtable.crush(ret, overflow, true) 472 | ret.widget_name = gobject.modulename(2) 473 | -- Tell the widget system to prevent clicks outside the layout's extends 474 | -- to register with child widgets, even if they actually extend that far. 475 | -- This prevents triggering button presses on hidden/clipped widgets. 476 | ret.clip_child_extends = true 477 | 478 | -- Manually set the scroll factor here. We don't know the bounding size yet. 479 | ret._private.scroll_factor = 0 480 | 481 | -- Apply defaults. Bypass setters to avoid signals. 482 | ret._private.step = 10 483 | ret._private.fill_space = true 484 | ret._private.scrollbar_width = 5 485 | ret._private.scrollbar_enabled = true 486 | ret._private.scrollbar_position = dir == "vertical" and "right" or "bottom" 487 | ret._private.scrollbar_spacing = dpi(15) 488 | 489 | local scrollbar_widget = separator({ 490 | shape = gshape.rectangle 491 | }) 492 | apply_scrollbar_mouse_signal(ret, scrollbar_widget) 493 | ret._private.scrollbar_widget = scrollbar_widget 494 | 495 | ret:connect_signal('button::press', function(self, _, _, button) 496 | if button == 4 then 497 | self:scroll(-1) 498 | elseif button == 5 then 499 | self:scroll(1) 500 | end 501 | end) 502 | 503 | return ret 504 | end 505 | 506 | --- Returns a new horizontal overflow layout. 507 | -- Child widgets are placed similar to `wibox.layout.fixed`, except that 508 | -- they may take as much width as they want. If the total width of all child 509 | -- widgets exceeds the width available whithin the layout's outer container 510 | -- a scrollbar will be added and scrolling behavior enabled. 511 | -- @tparam widget ... Widgets that should be added to the layout. 512 | -- @constructorfct wibox.layout.overflow.horizontal 513 | function overflow.horizontal(...) 514 | return new("horizontal", ...) 515 | end 516 | 517 | --- Returns a new vertical overflow layout. 518 | -- Child widgets are placed similar to `wibox.layout.fixed`, except that 519 | -- they may take as much height as they want. If the total height of all child 520 | -- widgets exceeds the height available whithin the layout's outer container 521 | -- a scrollbar will be added and scrolling behavior enabled. 522 | -- @tparam widget ... Widgets that should be added to the layout. 523 | -- @constructorfct wibox.layout.overflow.horizontal 524 | function overflow.vertical(...) 525 | return new("vertical", ...) 526 | end 527 | 528 | return setmetatable(overflow, overflow.mt) 529 | -------------------------------------------------------------------------------- /wibox/widget/hierarchy.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------- 2 | -- Management of widget hierarchies. Each widget hierarchy object has a widget 3 | -- for which it saves e.g. size and transformation in its parent. Also, each 4 | -- widget has a number of children. 5 | -- 6 | -- @author Uli Schlachter 7 | -- @copyright 2015 Uli Schlachter 8 | -- @classmod wibox.hierarchy 9 | --------------------------------------------------------------------------- 10 | 11 | local matrix = require("gears.matrix") 12 | local protected_call = require("gears.protected_call") 13 | local cairo = require("lgi").cairo 14 | local base = require("wibox.widget.base") 15 | local no_parent = base.no_parent_I_know_what_I_am_doing 16 | 17 | local hierarchy = {} 18 | 19 | local widgets_to_count = setmetatable({}, { __mode = "k" }) 20 | 21 | --- Add a widget to the list of widgets for which hierarchies should count their 22 | -- occurrences. Note that for correct operations, the widget must not yet be 23 | -- visible in any hierarchy. 24 | -- @param widget The widget that should be counted. 25 | -- @staticfct wibox.hierarchy.count_widget 26 | function hierarchy.count_widget(widget) 27 | widgets_to_count[widget] = true 28 | end 29 | 30 | local function hierarchy_new(redraw_callback, layout_callback, callback_arg) 31 | local result = { 32 | _matrix = matrix.identity, 33 | _matrix_to_device = matrix.identity, 34 | _need_update = true, 35 | _widget = nil, 36 | _context = nil, 37 | _redraw_callback = redraw_callback, 38 | _layout_callback = layout_callback, 39 | _callback_arg = callback_arg, 40 | _size = { 41 | width = nil, 42 | height = nil 43 | }, 44 | _draw_extents = { 45 | x = 0, 46 | y = 0, 47 | width = 0, 48 | height = 0 49 | }, 50 | _parent = nil, 51 | _children = {}, 52 | _widget_counts = {}, 53 | } 54 | 55 | function result._redraw() 56 | redraw_callback(result, callback_arg) 57 | end 58 | function result._layout() 59 | local h = result 60 | while h do 61 | h._need_update = true 62 | h = h._parent 63 | end 64 | layout_callback(result, callback_arg) 65 | end 66 | function result._emit_recursive(widget, name, ...) 67 | local cur = result 68 | assert(widget == cur._widget) 69 | while cur do 70 | if cur._widget then 71 | cur._widget:emit_signal(name, ...) 72 | end 73 | cur = cur._parent 74 | end 75 | end 76 | 77 | for k, f in pairs(hierarchy) do 78 | if type(f) == "function" then 79 | result[k] = f 80 | end 81 | end 82 | return result 83 | end 84 | 85 | local hierarchy_update 86 | function hierarchy_update(self, context, widget, width, height, region, matrix_to_parent, matrix_to_device) 87 | if (not self._need_update) and self._widget == widget and 88 | self._context == context and 89 | self._size.width == width and self._size.height == height and 90 | matrix.equals(self._matrix, matrix_to_parent) and 91 | matrix.equals(self._matrix_to_device, matrix_to_device) then 92 | -- Nothing changed 93 | return 94 | end 95 | 96 | self._need_update = false 97 | 98 | local old_x, old_y, old_width, old_height 99 | local old_widget = self._widget 100 | if self._size.width and self._size.height then 101 | local x, y, w, h = matrix.transform_rectangle(self._matrix_to_device, 0, 0, self._size.width, self._size.height) 102 | old_x, old_y = math.floor(x), math.floor(y) 103 | old_width, old_height = math.ceil(x + w) - old_x, math.ceil(y + h) - old_y 104 | else 105 | old_x, old_y, old_width, old_height = 0, 0, 0, 0 106 | end 107 | 108 | -- Disconnect old signals 109 | if old_widget and old_widget ~= widget then 110 | self._widget:disconnect_signal("widget::redraw_needed", self._redraw) 111 | self._widget:disconnect_signal("widget::layout_changed", self._layout) 112 | self._widget:disconnect_signal("widget::emit_recursive", self._emit_recursive) 113 | end 114 | 115 | -- Save the arguments we need to save 116 | self._widget = widget 117 | self._context = context 118 | self._size.width = width 119 | self._size.height = height 120 | self._matrix = matrix_to_parent 121 | self._matrix_to_device = matrix_to_device 122 | 123 | -- Connect signals 124 | if old_widget ~= widget then 125 | widget:weak_connect_signal("widget::redraw_needed", self._redraw) 126 | widget:weak_connect_signal("widget::layout_changed", self._layout) 127 | widget:weak_connect_signal("widget::emit_recursive", self._emit_recursive) 128 | end 129 | 130 | -- Update children 131 | local old_children = self._children 132 | local layout_result = base.layout_widget(no_parent, context, widget, width, height) 133 | self._children = {} 134 | for _, w in ipairs(layout_result or {}) do 135 | local r = table.remove(old_children, 1) 136 | if not r then 137 | r = hierarchy_new(self._redraw_callback, self._layout_callback, self._callback_arg) 138 | r._parent = self 139 | end 140 | hierarchy_update(r, context, w._widget, w._width, w._height, region, w._matrix, w._matrix * matrix_to_device) 141 | table.insert(self._children, r) 142 | end 143 | 144 | -- Calculate the draw extents 145 | local x1, y1, x2, y2 = 0, 0, width, height 146 | if not widget.clip_child_extends then 147 | for _, h in ipairs(self._children) do 148 | local px, py, pwidth, pheight = matrix.transform_rectangle(h._matrix, h:get_draw_extents()) 149 | x1 = math.min(x1, px) 150 | y1 = math.min(y1, py) 151 | x2 = math.max(x2, px + pwidth) 152 | y2 = math.max(y2, py + pheight) 153 | end 154 | end 155 | self._draw_extents = { 156 | x = x1, y = y1, 157 | width = x2 - x1, 158 | height = y2 - y1 159 | } 160 | 161 | -- Update widget counts 162 | self._widget_counts = {} 163 | if widgets_to_count[widget] and width > 0 and height > 0 then 164 | self._widget_counts[widget] = 1 165 | end 166 | for _, h in ipairs(self._children) do 167 | for w, count in pairs(h._widget_counts) do 168 | self._widget_counts[w] = (self._widget_counts[w] or 0) + count 169 | end 170 | end 171 | 172 | -- Check which part needs to be redrawn 173 | 174 | -- Are there any children which were removed? Their area needs a redraw. 175 | for _, child in ipairs(old_children) do 176 | local x, y, w, h = matrix.transform_rectangle(child._matrix_to_device, child:get_draw_extents()) 177 | region:union_rectangle(cairo.RectangleInt{ 178 | x = x, y = y, width = w, height = h 179 | }) 180 | child._parent = nil 181 | end 182 | 183 | -- Did we change and need to be redrawn? 184 | local x, y, w, h = matrix.transform_rectangle(self._matrix_to_device, 0, 0, self._size.width, self._size.height) 185 | local new_x, new_y = math.floor(x), math.floor(y) 186 | local new_width, new_height = math.ceil(x + w) - new_x, math.ceil(y + h) - new_y 187 | if new_x ~= old_x or new_y ~= old_y or new_width ~= old_width or new_height ~= old_height or 188 | widget ~= old_widget then 189 | region:union_rectangle(cairo.RectangleInt{ 190 | x = old_x, y = old_y, width = old_width, height = old_height 191 | }) 192 | region:union_rectangle(cairo.RectangleInt{ 193 | x = new_x, y = new_y, width = new_width, height = new_height 194 | }) 195 | end 196 | end 197 | 198 | --- Create a new widget hierarchy that has no parent. 199 | -- @param context The context in which we are laid out. 200 | -- @param widget The widget that is at the base of the hierarchy. 201 | -- @param width The available width for this hierarchy. 202 | -- @param height The available height for this hierarchy. 203 | -- @param redraw_callback Callback that is called with the corresponding widget 204 | -- hierarchy on widget::redraw_needed on some widget. 205 | -- @param layout_callback Callback that is called with the corresponding widget 206 | -- hierarchy on widget::layout_changed on some widget. 207 | -- @param callback_arg A second argument that is given to the above callbacks. 208 | -- @return A new widget hierarchy 209 | -- @constructorfct wibox.hierarchy.new 210 | function hierarchy.new(context, widget, width, height, redraw_callback, layout_callback, callback_arg) 211 | local result = hierarchy_new(redraw_callback, layout_callback, callback_arg) 212 | result:update(context, widget, width, height) 213 | return result 214 | end 215 | 216 | --- Update a widget hierarchy with some new state. 217 | -- @param context The context in which we are laid out. 218 | -- @param widget The widget that is at the base of the hierarchy. 219 | -- @param width The available width for this hierarchy. 220 | -- @param height The available height for this hierarchy. 221 | -- @param[opt] region A region to use for accumulating changed parts 222 | -- @return A cairo region describing the changed parts (either the `region` 223 | -- argument or a new, internally created region). 224 | -- @method update 225 | function hierarchy:update(context, widget, width, height, region) 226 | region = region or cairo.Region.create() 227 | hierarchy_update(self, context, widget, width, height, region, self._matrix, self._matrix_to_device) 228 | return region 229 | end 230 | 231 | --- Get the widget that this hierarchy manages. 232 | -- @method get_widget 233 | function hierarchy:get_widget() 234 | return self._widget 235 | end 236 | 237 | --- Get a matrix that transforms to the parent's coordinate space from this 238 | -- hierarchy's coordinate system. 239 | -- @return A matrix describing the transformation. 240 | -- @method get_matrix_to_parent 241 | function hierarchy:get_matrix_to_parent() 242 | return self._matrix 243 | end 244 | 245 | --- Get a matrix that transforms to the base of this hierarchy's coordinate 246 | -- system (aka the coordinate system of the device that this 247 | -- hierarchy is applied upon) from this hierarchy's coordinate system. 248 | -- @return A matrix describing the transformation. 249 | -- @method get_matrix_to_device 250 | function hierarchy:get_matrix_to_device() 251 | return self._matrix_to_device 252 | end 253 | 254 | --- Get a matrix that transforms from the parent's coordinate space into this 255 | -- hierarchy's coordinate system. 256 | -- @return A matrix describing the transformation. 257 | -- @method get_matrix_from_parent 258 | function hierarchy:get_matrix_from_parent() 259 | local m = self:get_matrix_to_parent() 260 | return m:invert() 261 | end 262 | 263 | --- Get a matrix that transforms from the base of this hierarchy's coordinate 264 | -- system (aka the coordinate system of the device that this 265 | -- hierarchy is applied upon) into this hierarchy's coordinate system. 266 | -- @return A matrix describing the transformation. 267 | -- @method get_matrix_from_device 268 | function hierarchy:get_matrix_from_device() 269 | local m = self:get_matrix_to_device() 270 | return m:invert() 271 | end 272 | 273 | --- Get the extents that this hierarchy possibly draws to (in the current coordinate space). 274 | -- This includes the size of this element plus the size of all children 275 | -- (after applying the corresponding transformation). 276 | -- @return x, y, width, height 277 | -- @method get_draw_extents 278 | function hierarchy:get_draw_extents() 279 | local ext = self._draw_extents 280 | return ext.x, ext.y, ext.width, ext.height 281 | end 282 | 283 | --- Get the size that this hierarchy logically covers (in the current coordinate space). 284 | -- @return width, height 285 | -- @method get_size 286 | function hierarchy:get_size() 287 | local ext = self._size 288 | return ext.width, ext.height 289 | end 290 | 291 | --- Get a list of all children. 292 | -- @return List of all children hierarchies. 293 | -- @method get_children 294 | function hierarchy:get_children() 295 | return self._children 296 | end 297 | 298 | --- Count how often this widget is visible inside this hierarchy. This function 299 | -- only works with widgets registered via `count_widget`. 300 | -- @param widget The widget that should be counted 301 | -- @return The number of times that this widget is contained in this hierarchy. 302 | -- @method get_count 303 | function hierarchy:get_count(widget) 304 | return self._widget_counts[widget] or 0 305 | end 306 | 307 | --- Does the given cairo context have an empty clip (aka "no drawing possible")? 308 | local function empty_clip(cr) 309 | local x1, y1, x2, y2 = cr:clip_extents() 310 | return x2 - x1 == 0 or y2 - y1 == 0 311 | end 312 | 313 | --- Draw a hierarchy to some cairo context. 314 | -- This function draws the widgets in this widget hierarchy to the given cairo 315 | -- context. The context's clip is used to skip parts that aren't visible. 316 | -- @param context The context in which widgets are drawn. 317 | -- @param cr The cairo context that is used for drawing. 318 | -- @method draw 319 | function hierarchy:draw(context, cr) 320 | local widget = self:get_widget() 321 | if not widget._private.visible then 322 | return 323 | end 324 | 325 | cr:save() 326 | cr:transform(self:get_matrix_to_parent():to_cairo_matrix()) 327 | 328 | -- Clip to the draw extents 329 | cr:rectangle(self:get_draw_extents()) 330 | cr:clip() 331 | 332 | -- Draw if needed 333 | if not empty_clip(cr) then 334 | local opacity = widget:get_opacity() 335 | local function call(func, extra_arg1, extra_arg2) 336 | if not func then return end 337 | if not extra_arg2 then 338 | protected_call(func, widget, context, cr, self:get_size()) 339 | else 340 | protected_call(func, widget, context, extra_arg1, extra_arg2, cr, self:get_size()) 341 | end 342 | end 343 | 344 | -- Prepare opacity handling 345 | if opacity ~= 1 then 346 | cr:push_group() 347 | end 348 | 349 | -- Draw the widget 350 | cr:save() 351 | cr:rectangle(0, 0, self:get_size()) 352 | cr:clip() 353 | call(widget.draw) 354 | cr:restore() 355 | -- Clear any path that the widget might have left 356 | cr:new_path() 357 | 358 | -- Draw its children (We already clipped to the draw extents above) 359 | call(widget.before_draw_children) 360 | for i, wi in ipairs(self:get_children()) do 361 | call(widget.before_draw_child, i, wi:get_widget()) 362 | wi:draw(context, cr) 363 | call(widget.after_draw_child, i, wi:get_widget()) 364 | end 365 | call(widget.after_draw_children) 366 | -- Clear any path that the widget might have left 367 | cr:new_path() 368 | 369 | -- Apply opacity 370 | if opacity ~= 1 then 371 | cr:pop_group_to_source() 372 | cr.operator = cairo.Operator.OVER 373 | cr:paint_with_alpha(opacity) 374 | end 375 | end 376 | 377 | cr:restore() 378 | end 379 | 380 | return hierarchy 381 | --------------------------------------------------------------------------------