From 7d60e5625fca869a7e03bfe88b4106a4018999e7 Mon Sep 17 00:00:00 2001 From: Yessiest Date: Fri, 16 Sep 2022 22:42:50 +0400 Subject: [PATCH] big update --- README.md | 5 + desktop.conf | 11 +- libs/asckey.lua | 6 + libs/awmtk2.lua | 16 +++ libs/parser_test.lua | 169 +++++++++++++++++++++++++++ libs/parsers.lua | 53 +++++++++ libs/syscontrol.lua | 3 +- modules/binds.lua | 19 ++- modules/desktop.lua | 8 +- modules/global.lua | 10 +- modules/powermanX.lua | 63 ++++++++++ rc.lua | 1 + themes/unity/config/client_menu.json | 1 + themes/unity/theme.lua | 19 ++- widgets/battery.lua | 63 +++++++++- widgets/clientvolume.lua | 154 ++++++++++++++++++++++++ widgets/soundclown.lua | 32 +++-- 17 files changed, 601 insertions(+), 32 deletions(-) create mode 100644 modules/powermanX.lua create mode 100644 widgets/clientvolume.lua 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