summaryrefslogtreecommitdiff
path: root/pactl-widget/volume.lua
diff options
context:
space:
mode:
authorStefan Huber <shuber@sthu.org>2022-12-30 16:15:54 +0100
committerStefan Huber <shuber@sthu.org>2023-01-18 07:59:53 +0100
commit81d725fe84ba96f689ad1e4e8990c7750bc40d33 (patch)
tree74fa1e2dcf5ddbc9cdd5f8cff149c597d8ce524b /pactl-widget/volume.lua
parent5f251902cf2b5cc25e54e2ce3b6292a741d52399 (diff)
pactl: A new volume widget using pactl only
Add a new volume widget that is using pactl only for controlling volume and selecting sources and sinks. It therefore works with PulseAudio or PipeWire as backend, unlike the original Volume widget. The code is split as follows: - volume.lua contains the UI logic - pactl.lua contains the pactl interfacing and output parsing - utils.lua contains some shared helper routines It is heavily based on the original Volume code and supports the same configuration options and uses the same widget code.
Diffstat (limited to 'pactl-widget/volume.lua')
-rw-r--r--pactl-widget/volume.lua233
1 files changed, 233 insertions, 0 deletions
diff --git a/pactl-widget/volume.lua b/pactl-widget/volume.lua
new file mode 100644
index 0000000..53441fd
--- /dev/null
+++ b/pactl-widget/volume.lua
@@ -0,0 +1,233 @@
+-------------------------------------------------
+-- A purely pactl-based volume widget based on the original Volume widget
+-- More details could be found here:
+-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/pactl-widget
+
+-- @author Stefan Huber
+-- @copyright 2023 Stefan Huber
+-------------------------------------------------
+
+local awful = require("awful")
+local wibox = require("wibox")
+local spawn = require("awful.spawn")
+local gears = require("gears")
+local beautiful = require("beautiful")
+
+local pactl = require("awesome-wm-widgets.pactl-widget.pactl")
+local utils = require("awesome-wm-widgets.pactl-widget.utils")
+
+
+local widget_types = {
+ icon_and_text = require("awesome-wm-widgets.volume-widget.widgets.icon-and-text-widget"),
+ icon = require("awesome-wm-widgets.volume-widget.widgets.icon-widget"),
+ arc = require("awesome-wm-widgets.volume-widget.widgets.arc-widget"),
+ horizontal_bar = require("awesome-wm-widgets.volume-widget.widgets.horizontal-bar-widget"),
+ vertical_bar = require("awesome-wm-widgets.volume-widget.widgets.vertical-bar-widget")
+}
+local volume = {}
+
+local rows = { layout = wibox.layout.fixed.vertical }
+
+local popup = awful.popup{
+ bg = beautiful.bg_normal,
+ ontop = true,
+ visible = false,
+ shape = gears.shape.rounded_rect,
+ border_width = 1,
+ border_color = beautiful.bg_focus,
+ maximum_width = 400,
+ offset = { y = 5 },
+ widget = {}
+}
+
+local function build_main_line(device)
+ if device.active_port ~= nil and device.ports[device.active_port] ~= nil then
+ return device.description .. ' ยท ' .. utils.split(device.ports[device.active_port], " ")[1]
+ else
+ return device.description
+ end
+end
+
+local function build_rows(devices, on_checkbox_click, device_type)
+ local device_rows = { layout = wibox.layout.fixed.vertical }
+ for _, device in pairs(devices) do
+
+ local checkbox = wibox.widget {
+ checked = device.is_default,
+ color = beautiful.bg_normal,
+ paddings = 2,
+ shape = gears.shape.circle,
+ forced_width = 20,
+ forced_height = 20,
+ check_color = beautiful.fg_urgent,
+ widget = wibox.widget.checkbox
+ }
+
+ checkbox:connect_signal("button::press", function()
+ pactl.set_default(device_type, device.name)
+ on_checkbox_click()
+ end)
+
+ local row = wibox.widget {
+ {
+ {
+ {
+ checkbox,
+ valign = 'center',
+ layout = wibox.container.place,
+ },
+ {
+ {
+ text = build_main_line(device),
+ align = 'left',
+ widget = wibox.widget.textbox
+ },
+ left = 10,
+ layout = wibox.container.margin
+ },
+ spacing = 8,
+ layout = wibox.layout.align.horizontal
+ },
+ margins = 4,
+ layout = wibox.container.margin
+ },
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ }
+
+ row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end)
+ row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end)
+
+ local old_cursor, old_wibox
+ row:connect_signal("mouse::enter", function()
+ local wb = mouse.current_wibox
+ old_cursor, old_wibox = wb.cursor, wb
+ wb.cursor = "hand1"
+ end)
+ row:connect_signal("mouse::leave", function()
+ if old_wibox then
+ old_wibox.cursor = old_cursor
+ old_wibox = nil
+ end
+ end)
+
+ row:connect_signal("button::press", function()
+ pactl.set_default(device_type, device.name)
+ on_checkbox_click()
+ end)
+
+ table.insert(device_rows, row)
+ end
+
+ return device_rows
+end
+
+local function build_header_row(text)
+ return wibox.widget{
+ {
+ markup = "<b>" .. text .. "</b>",
+ align = 'center',
+ widget = wibox.widget.textbox
+ },
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ }
+end
+
+local function rebuild_popup()
+ for i = 0, #rows do
+ rows[i]=nil
+ end
+
+ local sinks, sources = pactl.get_sinks_and_sources()
+ table.insert(rows, build_header_row("SINKS"))
+ table.insert(rows, build_rows(sinks, function() rebuild_popup() end, "sink"))
+ table.insert(rows, build_header_row("SOURCES"))
+ table.insert(rows, build_rows(sources, function() rebuild_popup() end, "source"))
+
+ popup:setup(rows)
+end
+
+local function worker(user_args)
+
+ local args = user_args or {}
+
+ local mixer_cmd = args.mixer_cmd or 'pavucontrol'
+ local widget_type = args.widget_type
+ local refresh_rate = args.refresh_rate or 1
+ local step = args.step or 5
+ local device = args.device or '@DEFAULT_SINK@'
+
+ if widget_types[widget_type] == nil then
+ volume.widget = widget_types['icon_and_text'].get_widget(args.icon_and_text_args)
+ else
+ volume.widget = widget_types[widget_type].get_widget(args)
+ end
+
+ local function update_graphic(widget)
+ local vol = pactl.get_volume(device)
+ if vol ~= nil then
+ widget:set_volume_level(vol)
+ end
+
+ if pactl.get_mute(device) then
+ widget:mute()
+ else
+ widget:unmute()
+ end
+ end
+
+ function volume:inc(s)
+ pactl.volume_increase(device, s or step)
+ update_graphic(volume.widget)
+ end
+
+ function volume:dec(s)
+ pactl.volume_decrease(device, s or step)
+ update_graphic(volume.widget)
+ end
+
+ function volume:toggle()
+ pactl.mute_toggle(device)
+ update_graphic(volume.widget)
+ end
+
+ function volume:popup()
+ if popup.visible then
+ popup.visible = not popup.visible
+ else
+ rebuild_popup()
+ popup:move_next_to(mouse.current_widget_geometry)
+ end
+ end
+
+ function volume:mixer()
+ if mixer_cmd then
+ spawn(mixer_cmd)
+ end
+ end
+
+ volume.widget:buttons(
+ awful.util.table.join(
+ awful.button({}, 1, function() volume:toggle() end),
+ awful.button({}, 2, function() volume:mixer() end),
+ awful.button({}, 3, function() volume:popup() end),
+ awful.button({}, 4, function() volume:inc() end),
+ awful.button({}, 5, function() volume:dec() end)
+ )
+ )
+
+ gears.timer {
+ timeout = refresh_rate,
+ call_now = true,
+ autostart = true,
+ callback = function()
+ update_graphic(volume.widget)
+ end
+ }
+
+ return volume.widget
+end
+
+
+return setmetatable(volume, { __call = function(_, ...) return worker(...) end })