diff --git a/README.md b/README.md
index bde1163..907f61b 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,11 @@ The installation process is not much different from the previous iteration, exce
3. Install AwesomeWM (version 4.3 as of right now)
4. (Optional) Read additional installation steps in `extra/README.md`
+## Keybindings and user guide
+- press win+s
+- read extra/README.md
+- enjoy
+
## Roadmap
- [x] Port widgets from original config to AWMTK2
diff --git a/desktop.conf b/desktop.conf
index b2b2b00..8ad24b8 100644
--- a/desktop.conf
+++ b/desktop.conf
@@ -26,11 +26,20 @@ modkey+b = ":client.below"
modkey+f = ":client.fullscreen"
modkey+n = ":client.minimize"
modkey+m = ":client.maximize"
+modkey+p = ":client.pin"
+modkey+y = ":client.toggle_titlebars"
# Widget keys
modkey+r = ":dismal.run"
modkey+s = ":help.show"
-
+Control+XF86AudioRaiseVolume = ":client.volume_up"
+Control+XF86AudioLowerVolume = ":client.volume_down"
+Control+XF86AudioMute = ":client.volume_mute"
+XF86MonBrightnessUp = ":battery.brightness_up"
+XF86MonBrightnessDown = ":battery.brightness_down"
+XF86AudioPlay = ":mpc.play"
+XF86AudioPrev = ":mpc.prev"
+XF86AudioNext = ":mpc.next"
# Custom keys
Print = "flameshot gui"
Shift+Print = "flameshot launcher"
diff --git a/libs/asckey.lua b/libs/asckey.lua
index 24b9830..e9146ef 100644
--- a/libs/asckey.lua
+++ b/libs/asckey.lua
@@ -12,6 +12,12 @@ local asckey = {
}
local awful = require("awful")
+asckey.set_keymap = function(keymap)
+ for k,v in pairs(keymap) do
+ asckey.keymap[v] = k
+ end
+end
+
asckey.get_keycomb = function(name)
local modifiers = {}
name = name:gsub("[^%s%+]+%+",function(v)
diff --git a/libs/awmtk2.lua b/libs/awmtk2.lua
index 51b7a8c..839e158 100644
--- a/libs/awmtk2.lua
+++ b/libs/awmtk2.lua
@@ -162,6 +162,9 @@ awmtk.proto_style.center = awmtk.create_delta("center", {
awmtk.proto_style.slider = awmtk.create_delta("slider", {
margins = 1
}, awmtk.proto_style,awmtk.proto_style.base)
+
+awmtk.proto_style.checkbox = awmtk.create_delta("checkbox", {
+}, awmtk.proto_style,awmtk.proto_style.base)
-- }}}
-- {{{ Generic templates
@@ -407,6 +410,19 @@ awmtk.proto_templates = {
widget = wibox.widget.slider
},args or {})
end
+ end,
+
+ checkbox = function(style)
+ return function(args)
+ return awmtk.merge({
+ color = style.checkbox.bg_focus,
+ padding = 2,
+ shape = style.checkbox.shape,
+ border_width = style.checkbox.shape_border_width,
+ bg = style.checkbox.shape_border_color,
+ widget = wibox.widget.checkbox
+ },args or {})
+ end
end
}
diff --git a/libs/parser_test.lua b/libs/parser_test.lua
index ce5c28c..caa453e 100644
--- a/libs/parser_test.lua
+++ b/libs/parser_test.lua
@@ -7,6 +7,15 @@
-- You should have received a copy of the GNU General Public License along with Reno desktop. If not, see .
local parsers = require("parsers")
+function rprint(t,ident)
+ ident = ident or 0
+ for k,v in pairs(t) do
+ print((" "):rep(ident)..k,v)
+ if type(v) == "table" then
+ rprint(v,ident+4)
+ end
+ end
+end
-- Conf parser
local data = [[
# Global variables
@@ -45,3 +54,163 @@ for k,v in pairs(parsers.conf(data)) do
print(kk,vv)
end
end
+
+-- yaml-like (pactl) parser
+data = [[
+Sink Input #75
+ Driver: PipeWire
+ Owner Module: n/a
+ Client: 58
+ Sink: 46
+ Sample Specification: float32le 2ch 44100Hz
+ Channel Map: front-left,front-right
+ Format: pcm, format.sample_format = "\"float32le\"" format.rate = "44100" format.channels = "2" format.channel_map = "\"front-left,front-right\""
+ Corked: no
+ Mute: no
+ Volume: front-left: 38326 / 58% / -13.98 dB, front-right: 38326 / 58% / -13.98 dB
+ balance 0.00
+ Buffer Latency: 0 usec
+ Sink Latency: 0 usec
+ Resample method: PipeWire
+ Properties:
+ client.api = "pipewire-pulse"
+ pulse.server.type = "unix"
+ application.name = "LibreWolf"
+ application.process.id = "1756"
+ application.process.user = "yessiest"
+ application.process.host = "architect"
+ application.process.binary = "librewolf"
+ application.language = "C.UTF-8"
+ window.x11.display = ":0"
+ application.process.machine_id = "a8099c2d12fe88c3df940ed562adbe8c"
+ application.process.session_id = "1"
+ media.name = "AudioStream"
+ node.rate = "1/44100"
+ node.latency = "3307/44100"
+ stream.is-live = "true"
+ node.name = "LibreWolf"
+ node.autoconnect = "true"
+ node.want-driver = "true"
+ media.class = "Stream/Output/Audio"
+ adapt.follower.spa-node = ""
+ object.register = "false"
+ factory.id = "6"
+ clock.quantum-limit = "8192"
+ factory.mode = "split"
+ audio.adapt.follower = ""
+ library.name = "audioconvert/libspa-audioconvert"
+ client.id = "56"
+ object.id = "57"
+ object.serial = "75"
+ pulse.attr.maxlength = "4194304"
+ pulse.attr.tlength = "44104"
+ pulse.attr.prebuf = "35296"
+ pulse.attr.minreq = "8816"
+ module-stream-restore.id = "sink-input-by-application-name:LibreWolf"
+]]
+rprint(parsers.yaml_pseudo(data))
+-- Fast incomplete yaml parser
+data = [[
+Sink Input #73
+ Driver: PipeWire
+ Owner Module: n/a
+ Client: 58
+ Sink: 46
+ Sample Specification: float32le 2ch 44100Hz
+ Channel Map: front-left,front-right
+ Format: pcm, format.sample_format = "\"float32le\"" format.rate = "44100" format.channels = "2" format.channel_map = "\"front-left,front-right\""
+ Corked: no
+ Mute: no
+ Volume: front-left: 38326 / 58% / -13.98 dB, front-right: 38326 / 58% / -13.98 dB
+ balance 0.00
+ Buffer Latency: 0 usec
+ Sink Latency: 0 usec
+ Resample method: PipeWire
+ Properties:
+ client.api = "pipewire-pulse"
+ pulse.server.type = "unix"
+ application.name = "LibreWolf"
+ application.process.id = "1756"
+ application.process.user = "yessiest"
+ application.process.host = "architect"
+ application.process.binary = "librewolf"
+ application.language = "C.UTF-8"
+ window.x11.display = ":0"
+ application.process.machine_id = "a8099c2d12fe88c3df940ed562adbe8c"
+ application.process.session_id = "1"
+ media.name = "AudioStream"
+ node.rate = "1/44100"
+ node.latency = "3307/44100"
+ stream.is-live = "true"
+ node.name = "LibreWolf"
+ node.autoconnect = "true"
+ node.want-driver = "true"
+ media.class = "Stream/Output/Audio"
+ adapt.follower.spa-node = ""
+ object.register = "false"
+ factory.id = "6"
+ clock.quantum-limit = "8192"
+ factory.mode = "split"
+ audio.adapt.follower = ""
+ library.name = "audioconvert/libspa-audioconvert"
+ client.id = "56"
+ object.id = "57"
+ object.serial = "75"
+ pulse.attr.maxlength = "4194304"
+ pulse.attr.tlength = "44104"
+ pulse.attr.prebuf = "35296"
+ pulse.attr.minreq = "8816"
+ module-stream-restore.id = "sink-input-by-application-name:LibreWolf"
+
+Sink Input #75
+ Driver: PipeWire
+ Owner Module: n/a
+ Client: 58
+ Sink: 46
+ Sample Specification: float32le 2ch 44100Hz
+ Channel Map: front-left,front-right
+ Format: pcm, format.sample_format = "\"float32le\"" format.rate = "44100" format.channels = "2" format.channel_map = "\"front-left,front-right\""
+ Corked: no
+ Mute: no
+ Volume: front-left: 38326 / 58% / -13.98 dB, front-right: 38326 / 58% / -13.98 dB
+ balance 0.00
+ Buffer Latency: 0 usec
+ Sink Latency: 0 usec
+ Resample method: PipeWire
+ Properties:
+ client.api = "pipewire-pulse"
+ pulse.server.type = "unix"
+ application.name = "LibreWolf"
+ application.process.id = "1756"
+ application.process.user = "yessiest"
+ application.process.host = "architect"
+ application.process.binary = "librewolf"
+ application.language = "C.UTF-8"
+ window.x11.display = ":0"
+ application.process.machine_id = "a8099c2d12fe88c3df940ed562adbe8c"
+ application.process.session_id = "1"
+ media.name = "AudioStream"
+ node.rate = "1/44100"
+ node.latency = "3307/44100"
+ stream.is-live = "true"
+ node.name = "LibreWolf"
+ node.autoconnect = "true"
+ node.want-driver = "true"
+ media.class = "Stream/Output/Audio"
+ adapt.follower.spa-node = ""
+ object.register = "false"
+ factory.id = "6"
+ clock.quantum-limit = "8192"
+ factory.mode = "split"
+ audio.adapt.follower = ""
+ library.name = "audioconvert/libspa-audioconvert"
+ client.id = "56"
+ object.id = "57"
+ object.serial = "75"
+ pulse.attr.maxlength = "4194304"
+ pulse.attr.tlength = "44104"
+ pulse.attr.prebuf = "35296"
+ pulse.attr.minreq = "8816"
+ module-stream-restore.id = "sink-input-by-application-name:LibreWolf"
+]]
+rprint(parsers.fast_split_yaml(data))
diff --git a/libs/parsers.lua b/libs/parsers.lua
index e739c85..275c790 100644
--- a/libs/parsers.lua
+++ b/libs/parsers.lua
@@ -34,7 +34,60 @@ local function split_strings(text)
return split
end
+parsers.fast_split_yaml = function(cfgtext)
+ -- Fast yaml splitter - incomplete parsing, only first layer is parsed
+ -- Used within timers to find objects while decreasing CPU usage
+ local items = {}
+ local replacements = 1
+ cfgtext = cfgtext:gsub("^%s*","")
+ while replacements > 0 do
+ cfgtext,replacements = cfgtext:gsub("^(.-\n)(%S+)",function(struct,n)
+ table.insert(items,struct)
+ return ""..n
+ end)
+ end
+ table.insert(items,cfgtext)
+ return items
+end
+parsers.yaml_pseudo = function(cfgtext)
+ -- Somewhat yaml-like structure used by pactl
+ local struct = {}
+ local lines = {}
+ cfgtext:gsub("(%s*)([^\n]*)",function(spacing,line)
+ table.insert(lines,
+ {
+ spacing:len(),
+ -- key
+ line:match("^([^:=]-)%s*[:=]") or line,
+ -- value
+ line:match(":%s*(.-)%s*$") or
+ line:match("=%s*(.-)%s*$")
+ }
+ )
+ end)
+ local history = {struct}
+ local spacing_width = 0
+ for k,v in pairs(lines) do
+ if v[1] > spacing_width then
+ history[#history][lines[k-1][2]] = {
+ [lines[k-1][2]] = lines[k-1][3]
+ }
+ history[#history+1] = history[#history][lines[k-1][2]]
+ elseif v[1] < spacing_width then
+ history[#history] = nil
+ end
+ if v[3] and v[3]:match("^%s*\".*\"%s*$") then
+ history[#history][v[2]] = v[3]:match("^%s*\"(.*)\"%s*$")
+ else
+ history[#history][v[2]] = v[3]
+ end
+ spacing_width = v[1]
+ end
+ return struct
+end
+
parsers.conf = function(cfgtext)
+ -- Conf style parser (not exactly TOML)
cfgtext = cfgtext:gsub("#[^\n]*","")
local split_by_strings,err = split_strings(cfgtext)
if not split_by_strings then
diff --git a/libs/syscontrol.lua b/libs/syscontrol.lua
index f79a539..83a3ccb 100644
--- a/libs/syscontrol.lua
+++ b/libs/syscontrol.lua
@@ -10,7 +10,8 @@
local syscontrol = {
power_supply = {},
backlight = {},
- hwmon = {}
+ hwmon = {},
+ pulse = {}
}
syscontrol.backlight.enumerate = function()
local lshandler = io.popen("ls -1 /sys/class/backlight","r")
diff --git a/modules/binds.lua b/modules/binds.lua
index e11b001..3221a35 100644
--- a/modules/binds.lua
+++ b/modules/binds.lua
@@ -11,7 +11,7 @@ local gears = require("gears")
local ask = require("asckey")
global.modkey = global.modkey or "Mod4"
-ask.keymap = keybindings
+ask.set_keymap(config.keys)
local custom_keys = ask.custom_binds()
local k = ask.k
@@ -101,7 +101,22 @@ local clientkeys = gears.table.join(
c.maximized = not c.maximized
c:raise()
end ,
- {description = "(un)maximize", group = "client"}))
+ {description = "(un)maximize", group = "client"}),
+ k(":client.pin",
+ function (c)
+ c.sticky = not c.sticky
+ end ,
+ {description = "(un)pin", group = "client"}),
+ k(":client.toggle_titlebars",
+ function (c)
+ if (not c.titlebar_top.visible) then
+ c:emit_signal("titlebar::unhide")
+ else
+ c:emit_signal("titlebar::hide")
+ end
+ end ,
+ {description = "(un)hide titlebars", group = "client"}))
+
awful.rules.rules[1].properties.keys = clientkeys
local clientbuttons = gears.table.join(
diff --git a/modules/desktop.lua b/modules/desktop.lua
index 9cfd9a2..b4dc53d 100644
--- a/modules/desktop.lua
+++ b/modules/desktop.lua
@@ -156,7 +156,7 @@ client.connect_signal("request::titlebars",function(c)
fg_focus = style[v].fg_focus,
font = style[v].font
})
- titlebar:setup(t.titlebar(contents))
+ c[v] = titlebar:setup(t.titlebar(contents))
awful.rules.rules[1].properties.placement(c)
if style[v].onfocus then
c:connect_signal("focus",function()
@@ -168,6 +168,12 @@ client.connect_signal("request::titlebars",function(c)
style[v].onunfocus(titlebar)
end)
end
+ c:connect_signal("titlebar::hide",function(c)
+ c[v].visible = false
+ end)
+ c:connect_signal("titlebar::unhide",function(c)
+ c[v].visible = true
+ end)
end
end)
end --}}}
diff --git a/modules/global.lua b/modules/global.lua
index d194eab..f521e3e 100644
--- a/modules/global.lua
+++ b/modules/global.lua
@@ -12,16 +12,8 @@ local conf_file = io.open(root_path.."/desktop.conf","r")
if not conf_file then
error("desktop.conf is missing or not readable")
end
-local config = conf(conf_file:read("*a"))
+config = conf(conf_file:read("*a"))
conf_file:close()
global = config.global
global.terminal = envsub(global.terminal)
global.browser = envsub(global.browser)
-local function invert(t)
- local new_t = {}
- for k,v in pairs(t) do
- new_t[v] = k
- end
- return new_t
-end
-keybindings = invert(config.keys)
diff --git a/modules/powermanX.lua b/modules/powermanX.lua
new file mode 100644
index 0000000..2cdd528
--- /dev/null
+++ b/modules/powermanX.lua
@@ -0,0 +1,63 @@
+-- This file is part of Reno desktop.
+--
+-- Reno desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+--
+-- Reno desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with Reno desktop. If not, see .
+-- Powerman X - second generation of the power management daemon
+local sysctl = require("syscontrol")
+local naughty = require("naughty")
+local gears = require("gears")
+local awful = require("awful")
+local batteries = sysctl.power_supply.enumerate()
+local state_tracking = {}
+-- Configuration variables
+local cfg = config.powerman or {}
+local quality_min = cfg.battery_quality_min or 33
+local capacity_min = cfg.battery_capacity_min or 15
+-- Main loop
+local update_loop = gears.timer({
+ timeout = 2,
+ autostart = true,
+ callback = function()
+ for k,v in pairs(batteries) do
+ local data,err = sysctl.power_supply.read_attribs(v)
+ state_tracking[v] = state_tracking[v] or {}
+ if data.type == "Battery" then
+ if (tonumber(data.quality) < quality_min) and
+ (not state_tracking[v].quality_notification) then
+ naughty.notify({
+ title = "Critical battery condition",
+ text = "Battery "..data.name.." has reached critically low condition, seek a suitable replacement"
+ })
+ state_tracking[v].quality_notification = true
+ end
+ if (tonumber(data.capacity) <= capacity_min) and
+ (not data.charging) and
+ (not state_tracking[v].capacity_notification) then
+ naughty.notify({
+ title = "Battery capacity low",
+ text = "Battery "..data.name.." capacity is at "..tostring(data.capacity).."%"
+ })
+ state_tracking[v].capacity_notification = true
+ end
+ if (tonumber(data.capacity) > capacity_min) then
+ state_tracking[v].capacity_notification = false
+ end
+ if (data.capacity == "100") and
+ (data.charging) and
+ (not state_tracking[v].charged_notification) then
+ naughty.notify({
+ title = "Battery is completely charged",
+ text = "Disconnect the charger from the power grid to avoid passive electricity usage."
+ })
+ end
+ if (not data.charging) then
+ state_tracking[v].charged_notification = false
+ end
+ end
+ end
+ end
+})
+
diff --git a/rc.lua b/rc.lua
index 031924c..dec2a95 100644
--- a/rc.lua
+++ b/rc.lua
@@ -16,6 +16,7 @@ package.cpath = package.cpath
-- Modules list
require("modules.collect_garbage")
require("modules.global")
+require("modules.powermanX")
require("modules.errorlog")
require("modules.base")
require("modules.binds")
diff --git a/themes/unity/config/client_menu.json b/themes/unity/config/client_menu.json
index 17fff5a..a09013b 100644
--- a/themes/unity/config/client_menu.json
+++ b/themes/unity/config/client_menu.json
@@ -1,5 +1,6 @@
{
"list": [
+ {"widget": "widgets.clientvolume"},
{"widget": "widgets.clientcontrols"},
{"widget": "widgets.clientbuttons"}
],
diff --git a/themes/unity/theme.lua b/themes/unity/theme.lua
index 55e839d..bcf9cc2 100644
--- a/themes/unity/theme.lua
+++ b/themes/unity/theme.lua
@@ -1,6 +1,6 @@
--- Reno98 - a retro replica of a very recognizable theme
+-- Reno Unity - Unity theme for Reno desktop
--[[
- Reno98 - A theme for Reno desktop
+ Reno Unity - A theme for Reno desktop
Written in 2022 by Yessiest (yessiest@memeware.net)
@@ -306,11 +306,20 @@ theme.widgets = {
end,
height = 20,
width = 140,
+ bg_focus = theme.bg_normal,
+ bg_normal = theme.bg_focus,
handle_width = 8,
- handle_border_color = theme.bg_focus,
+ handle_border_color = theme.bg_normal,
handle_border_width = 2,
- bar_height = 6
- }
+ bar_height = 6,
+ bar_border_color = theme.bg_focus,
+ bar_border_width = 2
+ },
+ checkbox = {
+ shape = gears.shape.circle,
+ shape_border_width = 3,
+ shaoe_border_color = theme.bg_focus
+ },
},
-- }}}
-- {{{ Menus
diff --git a/widgets/battery.lua b/widgets/battery.lua
index 11659b8..aa39521 100644
--- a/widgets/battery.lua
+++ b/widgets/battery.lua
@@ -12,6 +12,7 @@ local gears = require("gears")
local wibox = require("wibox")
local awmtk2 = require("awmtk2")
local syscontrol = require("syscontrol")
+local ask = require("asckey")
local function get_virtual_icon(data)
-- Get an icon from a cumulative total of battery percentages and current charging state
@@ -182,14 +183,25 @@ return function(args)
-- }}}
-- {{{ Backlight
local backlight_devices = syscontrol.backlight.enumerate()
+ local default_backlight_device
for k,v in pairs(backlight_devices) do
local data = syscontrol.backlight.read_attribs(v)
if data then
widget_map[data.name] = wibox.widget(t.container({
- t.article({
- icon = beautiful["backlight-symbolic"],
- title = "Backlight",
- }),
+ {
+ t.article({
+ icon = beautiful["backlight-symbolic"],
+ title = "Backlight",
+ }),
+ (data.writable and t.checkbox({
+ checked = true,
+ id = "checkbox",
+ forced_height = style.article.icon_size,
+ forced_width = style.article.icon_size
+ })),
+ layout = wibox.layout.fixed.horizontal,
+ spacing = style.base.spacing
+ },
t.textbox({
markup = "Brightness: "..tostring(data.brightness),
id = "brightness_id"
@@ -201,7 +213,7 @@ return function(args)
(data.writable and t.slider({
minimum = data.max_brightness*0.05,
maximum = data.max_brightness,
- value = data.brightness,
+ value = tonumber(data.brightness),
id = "slider"
})),
layout = wibox.layout.fixed.vertical
@@ -210,11 +222,22 @@ return function(args)
bgimage = style.container.bgimage_highlight
}))
if data.writable then
- local slider = widget_map[data.name]:get_children_by_id("slider")[1]
+ local w = widget_map[data.name]
+ local slider = w:get_children_by_id("slider")[1]
slider:connect_signal("widget::redraw_needed",function(self)
local value = self.value
syscontrol.backlight.set_brightness(data,math.floor(value))
end)
+ slider.value = tonumber(data.brightness)
+ local checkbox = w:get_children_by_id("checkbox")[1]
+ checkbox:connect_signal("button::press",function()
+ if default_backlight_device then
+ local check2 = widget_map[default_backlight_device.name]
+ :get_children_by_id("checkbox")[1]
+ check2.checked = false
+ end
+ default_backlight_device = data
+ end)
end
layout:add(widget_map[data.name])
end
@@ -236,6 +259,34 @@ return function(args)
end
end
})
+ -- Keybindings
+ root.keys(gears.table.join(
+ root.keys(),
+ ask.k(":battery.brightness_up",function()
+ if default_backlight_device then
+ local data = default_backlight_device
+ local s = widget_map[data.name]:get_children_by_id("slider")[1]
+ local value = s.value+(data.max_brightness*0.05)
+ if value > data.max_brightness then
+ value = data.max_brightness
+ end
+ syscontrol.backlight.set_brightness(data,math.floor(value))
+ s.value = math.floor(value)
+ end
+ end,{description="increase brightness", group = "widgets"}),
+ ask.k(":battery.brightness_down",function()
+ if default_backlight_device then
+ local data = default_backlight_device
+ local s = widget_map[data.name]:get_children_by_id("slider")[1]
+ local value = s.value-(data.max_brightness*0.05)
+ if value < data.max_brightness*0.05 then
+ value = data.max_brightness*0.05
+ end
+ syscontrol.backlight.set_brightness(data,math.floor(value))
+ s.value = math.floor(value)
+ end
+ end,{description="decrease brightness", group = "widgets"})
+ ))
-- }}}
-- We don't need this widget if we don't have anything to show
local function count(t)
diff --git a/widgets/clientvolume.lua b/widgets/clientvolume.lua
new file mode 100644
index 0000000..73d4fc7
--- /dev/null
+++ b/widgets/clientvolume.lua
@@ -0,0 +1,154 @@
+-- This file is part of Reno desktop.
+--
+-- Reno desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+--
+-- Reno desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with Reno desktop. If not, see .
+-- Pulseaudio per-client volume setting
+local awful = require("awful")
+local gears = require("gears")
+local wibox = require("wibox")
+local awmtk2 = require("awmtk2")
+local fastyaml = require("parsers").fast_split_yaml
+local beautiful = require("beautiful")
+local ask = require("asckey")
+local pactl_data = {}
+
+local test_pactl = os.execute("pactl --version")
+local result = test_pactl
+if _VERSION:match("5.1") then
+ result = (test_pactl == 0)
+end
+if not result then
+ return
+end
+
+local function get_icon(percent)
+ if percent >= 66 then
+ return beautiful["volume-high-symbolic"]
+ elseif percent >= 33 then
+ return beautiful["volume-medium-symbolic"]
+ elseif percent > 0 then
+ return beautiful["volume-low-symbolic"]
+ else
+ return beautiful["volume-muted-symbolic"]
+ end
+end
+
+
+return function(args)
+ local style = awmtk2.create_style("client_volume",
+ awmtk2.generic.oneline_widget, args.style)
+ local templates = awmtk2.create_template_lib("client_volume",awmtk2.templates,args.templates)
+ local t = awmtk2.build_templates(templates,style)
+ local widget = wibox.widget(t.container({
+ t.center({
+ id = "client_volume_icon",
+ widget = wibox.widget.imagebox
+ }),
+ t.textbox({
+ id = "error"
+ }),
+ t.slider({
+ minimum = 0,
+ maximum = 100,
+ id = "client_volume",
+ value = -1
+ }),
+ layout = wibox.layout.fixed.horizontal
+ }))
+ local errorbox = widget:get_children_by_id("error")[1]
+ local icon = widget:get_children_by_id("client_volume_icon")[1]
+ local slider = widget:get_children_by_id("client_volume")[1]
+ -- Local tracking value to prevent zero volume on start
+ local slider_touched = false
+ -- Get initial pactl data
+ awful.spawn.easy_async("pactl list sink-inputs",function(stdout)
+ local pactl_data = fastyaml(stdout)
+ end)
+ -- Attach to focus change
+ client.connect_signal("update_volume",function(c)
+ awful.spawn.easy_async("pactl list sink-inputs",function(stdout)
+ local pactl_data = fastyaml(stdout)
+ local cl
+ for k,v in pairs(pactl_data) do
+ if not c then return end
+ if v:match("application.process.id = \""..tostring(c.pid).."\"") then
+ cl = v
+ end
+ end
+ if not cl then
+ slider.visible = false
+ errorbox.visible = true
+ errorbox:set_markup("No sound/Not available")
+ icon:set_image(beautiful["volume-muted-symbolic"])
+ return
+ end
+ local volume = tonumber(cl:match("Volume:[^\n]-(%d*)%%"))
+ slider.visible = true
+ errorbox.visible = false
+ icon:set_image(get_icon(volume))
+ slider.value = volume
+ touched = true
+ end)
+ end)
+ client.connect_signal("focus",function(c)
+ touched = false
+ c:emit_signal("update_volume")
+ end)
+ local update_timer = gears.timer({
+ timeout = 0.5,
+ autostart = true,
+ callback = function()
+ if client.focus then
+ client.focus:emit_signal("update_volume")
+ end
+ end
+ })
+ -- Async lock to prevent callback interference
+ local volume_lock = false
+ -- Function to set client volume
+ local function volume(volume)
+ if volume_lock then return end
+ volume_lock = true
+ awful.spawn.easy_async("pactl list sink-inputs",function(stdout)
+ local pactl_data = fastyaml(stdout)
+ local cl = {}
+ if not (client.focus and client.focus.pid) then
+ volume_lock = false
+ return
+ end
+ for k,v in pairs(pactl_data) do
+ if v:match("application.process.id = \""..tostring(client.focus.pid).."\"") then
+ local sink_id = v:match("^%s*Sink Input #(%d+)")
+ if sink_id then
+ print(sink_id, volume)
+ awful.spawn("pactl set-sink-input-volume "..tostring(sink_id).." "..tostring(volume).."%")
+ end
+ end
+ end
+ volume_lock = false
+ end)
+ end
+ -- Attach change to slider
+ slider:connect_signal("widget::redraw_needed",function(widget)
+ if touched then
+ volume(slider.value)
+ update_timer:again()
+ end
+ end)
+ root.keys(gears.table.join(
+ root.keys(),
+ ask.k(":client.volume_up", function()
+ volume("+5")
+ end,{description = "increase client volume", group = "client"}),
+ ask.k(":client.volume_down", function()
+ volume("-5")
+ end,{description = "decrease client volume", group = "client"}),
+ ask.k(":client.volume_mute", function()
+ volume(0)
+ end,{description = "mute client", group = "client"})
+ ))
+ return widget
+end
diff --git a/widgets/soundclown.lua b/widgets/soundclown.lua
index 0d8b2a9..59bf704 100644
--- a/widgets/soundclown.lua
+++ b/widgets/soundclown.lua
@@ -12,6 +12,7 @@ local wibox = require("wibox")
local gears = require("gears")
local awful = require("awful")
local beautiful = require("beautiful")
+local ask = require("asckey")
return function(args)
local style = awmtk2.create_style("soundclown",
@@ -72,15 +73,16 @@ return function(args)
local bprev = widget:get_children_by_id("prev")[1]
bprev:connect_signal("button::press",style.button.onpress)
bprev:connect_signal("button::release",style.button.onrelease)
- bprev:connect_signal("button::press",function()
+ local function prev()
awful.spawn("mpc cdprev")
- end)
+ end
+ bprev:connect_signal("button::press",prev)
local pause_state = true
local icon = widget:get_children_by_id("statusicon")[1]
local bplay = widget:get_children_by_id("play")[1]
bplay:connect_signal("button::press",style.button.onpress)
bplay:connect_signal("button::release",style.button.onrelease)
- bplay:connect_signal("button::press",function()
+ local function play()
pause_state = (not pause_state)
if pause_state == false then
icon.image = beautiful["mpc-pause-symbolic"]
@@ -89,13 +91,16 @@ return function(args)
icon.image = beautiful["mpc-play-symbolic"]
awful.spawn("mpc play")
end
- end)
+ end
+ bplay:connect_signal("button::press",play)
local bnext = widget:get_children_by_id("next")[1]
bnext:connect_signal("button::press",style.button.onpress)
bnext:connect_signal("button::release",style.button.onrelease)
- bnext:connect_signal("button::press",function()
+ local function nextb()
awful.spawn("mpc next")
- end)
+ end
+ bnext:connect_signal("button::press",nextb)
+ local update_ready = true
local function update_mpd_status()
awful.spawn.easy_async("mpc",function(out)
local status = ""
@@ -104,6 +109,7 @@ return function(args)
state = true
icon.image = beautiful["mpc-play-symbolic"]
display:set_markup(status)
+ update_ready = true
return
else
status = status.."[PAUSED] "
@@ -113,6 +119,7 @@ return function(args)
out:match("[^\n]*").." "..
out:match("%d*:%d*/%d*:%d*%s*%(%d*%%%)")
display:set_markup(status)
+ update_ready = true
end)
end
update_mpd_status()
@@ -120,8 +127,19 @@ return function(args)
timeout = args.polling_delay or 1,
autostart = true,
callback = function()
+ if not update_ready then return end
+ update_ready = false
update_mpd_status()
end
- }
+ }
+ root.keys(gears.table.join(
+ root.keys(),
+ ask.k(":mpc.prev",prev,
+ {description = "switch to previous MPD track",group="widgets"}),
+ ask.k(":mpc.play",play,
+ {description = "play/pause MPD",group="widgets"}),
+ ask.k(":mpc.next",nextb,
+ {description = "switch to next MPD track",group="widgets"})
+ ))
return widget
end