Replaced volume with event-based version for pipewire
This commit is contained in:
parent
2b8f18c58c
commit
b4675cc0b3
@ -5,11 +5,14 @@ find_package(PkgConfig)
|
|||||||
|
|
||||||
set(POSITION_INDEPENDENT_CODE YES)
|
set(POSITION_INDEPENDENT_CODE YES)
|
||||||
set(C_STANDARD 11)
|
set(C_STANDARD 11)
|
||||||
add_compile_options(-Wall -Wextra -pedantic)
|
add_compile_options(-Wall -Wextra)
|
||||||
|
|
||||||
pkg_check_modules(CONSTRAIN REQUIRED x11 xi xfixes xrandr)
|
pkg_check_modules(CONSTRAIN REQUIRED x11 xi xfixes xrandr)
|
||||||
|
|
||||||
add_library(constrain SHARED lib/constrain.c)
|
add_library(constrain SHARED lib/constrain.c)
|
||||||
|
|
||||||
target_link_libraries(constrain ${CONSTRAIN_LIBRARIES})
|
target_link_libraries(constrain ${CONSTRAIN_LIBRARIES})
|
||||||
target_include_directories(constrain PUBLIC ${CONSTRAIN_INCLUDE_DIRS})
|
target_include_directories(constrain PUBLIC ${CONSTRAIN_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
pkg_check_modules(PWCALL REQUIRED luajit gobject-2.0)
|
||||||
|
add_library(pwcall SHARED lib/pwcall.cxx)
|
||||||
|
target_link_libraries(pwcall ${PWCALL_LIBRARIES})
|
||||||
|
target_include_directories(pwcall PUBLIC ${PWCALL_INCLUDE_DIRS})
|
||||||
|
6
Makefile
6
Makefile
@ -13,9 +13,13 @@ deploy: build
|
|||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
cmake -S . -B build
|
cmake -S . -B build -G Ninja
|
||||||
cmake --build build
|
cmake --build build
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -vfr build
|
||||||
|
|
||||||
.PHONY: luacheck
|
.PHONY: luacheck
|
||||||
luacheck:
|
luacheck:
|
||||||
luacheck $(LUA_FILES)
|
luacheck $(LUA_FILES)
|
||||||
|
55
lib/pwcall.cxx
Normal file
55
lib/pwcall.cxx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#define SOL_ALL_SAFETIES_ON 1
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include "sol.hpp"
|
||||||
|
|
||||||
|
static guint default_node_sig = 0, get_volume_sig = 0, set_volume_sig = 0;
|
||||||
|
|
||||||
|
static guint default_node(gpointer api) {
|
||||||
|
if (default_node_sig == 0)
|
||||||
|
default_node_sig = g_signal_lookup("get-default-node", G_TYPE_FROM_INSTANCE(api));
|
||||||
|
|
||||||
|
guint retval;
|
||||||
|
g_signal_emit(api, default_node_sig, 0, "Audio/Sink", &retval);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::tuple<guint, bool> get_volume(gpointer api, guint32 id) {
|
||||||
|
if (get_volume_sig == 0)
|
||||||
|
get_volume_sig = g_signal_lookup("get-volume", G_TYPE_FROM_INSTANCE(api));
|
||||||
|
|
||||||
|
GVariant *variant = nullptr;
|
||||||
|
g_signal_emit(api, get_volume_sig, 0, id, &variant);
|
||||||
|
|
||||||
|
gdouble volumed;
|
||||||
|
gboolean mute;
|
||||||
|
g_variant_lookup(variant, "volume", "d", &volumed);
|
||||||
|
g_variant_lookup(variant, "mute", "b", &mute);
|
||||||
|
if (variant) g_variant_unref(variant);
|
||||||
|
|
||||||
|
return std::tuple<guint, bool>(std::round(volumed * 100.0), mute == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_volume(gpointer api, guint32 id, guint voli, bool mute) {
|
||||||
|
if (set_volume_sig == 0)
|
||||||
|
set_volume_sig = g_signal_lookup("set-volume", G_TYPE_FROM_INSTANCE(api));
|
||||||
|
|
||||||
|
g_auto(GVariantBuilder) b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT);
|
||||||
|
g_variant_builder_add(&b, "{sv}", "volume", g_variant_new_double(((gdouble)voli)/100));
|
||||||
|
g_variant_builder_add(&b, "{sv}", "mute", g_variant_new_boolean(mute ? TRUE : FALSE));
|
||||||
|
|
||||||
|
GVariant *variant = g_variant_builder_end(&b);
|
||||||
|
gboolean res;
|
||||||
|
g_signal_emit(api, set_volume_sig, 0, id, variant, &res);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int luaopen_libpwcall(lua_State *L) {
|
||||||
|
sol::state_view lua(L);
|
||||||
|
auto tbl = lua.create_table();
|
||||||
|
tbl.set_function("default_node", default_node);
|
||||||
|
tbl.set_function("get_volume", get_volume);
|
||||||
|
tbl.set_function("set_volume", set_volume);
|
||||||
|
sol::stack::push(L, tbl);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
28907
lib/sol.hpp
Normal file
28907
lib/sol.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,3 @@
|
|||||||
local fs = require('gears.filesystem')
|
|
||||||
|
|
||||||
print(awesome.conffile)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
conf_dir = awesome.conffile:match('^(.+)/')
|
conf_dir = awesome.conffile:match('^(.+)/')
|
||||||
}
|
}
|
@ -1,23 +1,101 @@
|
|||||||
|
-- https://github.com/Alexays/Waybar/blob/master/src/modules/wireplumber.cpp
|
||||||
|
-- https://gitlab.freedesktop.org/pipewire/wireplumber/-/blob/master/modules/module-default-nodes-api.c
|
||||||
|
-- https://gitlab.freedesktop.org/pipewire/wireplumber/-/blob/master/modules/module-mixer-api.c
|
||||||
|
|
||||||
local volume_changed_signal = 'volume_changed'
|
local volume_changed_signal = 'volume_changed'
|
||||||
|
|
||||||
local function toggle_mute()
|
if _G.volume_control == nil then
|
||||||
os.execute('wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle')
|
print('[volume_control] init')
|
||||||
awesome.emit_signal(volume_changed_signal)
|
local lgi = require('lgi')
|
||||||
end
|
---@class (exact) VolumeControl
|
||||||
|
---@field pw_call {}
|
||||||
|
---@field lgi any
|
||||||
|
---@field wp any
|
||||||
|
---@field glib any
|
||||||
|
---@field gobject any
|
||||||
|
---@field core any
|
||||||
|
---@field om any
|
||||||
|
---@field node_api { _native: any } | nil
|
||||||
|
---@field mixer_api { scale: number, _native: any } | nil
|
||||||
|
---@field node_id integer
|
||||||
|
---@field volume integer
|
||||||
|
---@field mute boolean
|
||||||
|
local volume_control = {
|
||||||
|
pw_call = require('libpwcall'),
|
||||||
|
lgi = lgi,
|
||||||
|
wp = lgi.Wp,
|
||||||
|
glib = lgi.GLib,
|
||||||
|
gobject = lgi.GObject,
|
||||||
|
core = nil,
|
||||||
|
om = nil,
|
||||||
|
node_api = nil,
|
||||||
|
mixer_api = nil,
|
||||||
|
node_id = 0,
|
||||||
|
volume = 0,
|
||||||
|
mute = false
|
||||||
|
}
|
||||||
|
|
||||||
local function vol_up()
|
function volume_control:get_default_node() self.node_id = volume_control.pw_call.default_node(self.node_api._native) end
|
||||||
os.execute('wpctl set-volume @DEFAULT_AUDIO_SINK@ 1%+')
|
function volume_control:set_volume(vol, m) volume_control.pw_call.set_volume(self.mixer_api._native, self.node_id, vol, m) end
|
||||||
awesome.emit_signal(volume_changed_signal)
|
function volume_control:toggle_mute() self:set_volume(self.volume, not self.mute) end
|
||||||
end
|
function volume_control:volume_delta(d) self:set_volume(self.volume + d, self.mute) end
|
||||||
|
function volume_control:get_volume()
|
||||||
|
self.volume, self.mute = volume_control.pw_call.get_volume(self.mixer_api._native, self.node_id)
|
||||||
|
awesome.emit_signal(volume_changed_signal, self.volume, self.mute)
|
||||||
|
end
|
||||||
|
|
||||||
local function vol_down()
|
function volume_control:om_installed()
|
||||||
os.execute('wpctl set-volume @DEFAULT_AUDIO_SINK@ 1%-')
|
self:get_default_node()
|
||||||
awesome.emit_signal(volume_changed_signal)
|
self:get_volume()
|
||||||
|
self.gobject.signal_connect_closure(self.node_api, 'changed', self.gobject.Closure(function(_)
|
||||||
|
volume_control:get_default_node()
|
||||||
|
volume_control:get_volume()
|
||||||
|
end))
|
||||||
|
self.gobject.signal_connect_closure(self.mixer_api, 'changed', self.gobject.Closure(function(_, nid)
|
||||||
|
if nid ~= volume_control.node_id then return end
|
||||||
|
volume_control:get_volume()
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
function volume_control:init()
|
||||||
|
self.wp.init(self.wp.InitFlags.PIPEWIRE)
|
||||||
|
self.core = self.wp.Core.new(nil, nil, nil)
|
||||||
|
self.om = self.wp.ObjectManager.new()
|
||||||
|
self.om.on_installed = function() self:om_installed() end
|
||||||
|
|
||||||
|
local int = self.wp.ObjectInterest.new_type('WpNode')
|
||||||
|
int:add_constraint(self.wp.ConstraintType.PW_PROPERTY, 'media.class', self.wp.ConstraintVerb.EQUALS, self.glib.Variant.new_string('Audio/Sink'))
|
||||||
|
self.om:add_interest_full(int)
|
||||||
|
if self.core:connect() == 0 then error('Failed to connect to pipewire') end
|
||||||
|
local pending = 2
|
||||||
|
local function plugin_loaded(p, res)
|
||||||
|
if volume_control.wp.Object.activate_finish(p, res) == 0 then error('Failed to activate plugin') end
|
||||||
|
pending = pending - 1
|
||||||
|
if pending ~= 0 then return end
|
||||||
|
volume_control.core:install_object_manager(volume_control.om)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.core:load_component('libwireplumber-module-default-nodes-api', 'module', nil, 'default-nodes-api', nil, function(_, res)
|
||||||
|
if volume_control.core:load_component_finish(res, nil) == 0 then error('Failed to load default-nodes-api') end
|
||||||
|
volume_control.core:load_component('libwireplumber-module-mixer-api', 'module', nil, 'mixer-api', nil, function(_, res2)
|
||||||
|
if volume_control.core:load_component_finish(res2, nil) == 0 then error('Failed to load mixer-api') end
|
||||||
|
volume_control.node_api = volume_control.wp.Plugin.find(volume_control.core, 'default-nodes-api')
|
||||||
|
volume_control.wp.Object.activate(volume_control.node_api, volume_control.wp.PluginFeatures.ENABLED, nil, plugin_loaded)
|
||||||
|
volume_control.mixer_api = volume_control.wp.Plugin.find(volume_control.core, 'mixer-api')
|
||||||
|
volume_control.mixer_api.scale = 1
|
||||||
|
volume_control.wp.Object.activate(volume_control.mixer_api, volume_control.wp.PluginFeatures.ENABLED, nil, plugin_loaded)
|
||||||
|
end, nil)
|
||||||
|
end, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
volume_control:init()
|
||||||
|
|
||||||
|
_G.volume_control = volume_control
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
volume_changed_signal = volume_changed_signal,
|
volume_changed_signal = volume_changed_signal,
|
||||||
toggle_mute = toggle_mute,
|
toggle_mute = function() _G.volume_control:toggle_mute() end,
|
||||||
vol_up = vol_up,
|
vol_up = function() _G.volume_control:volume_delta(1) end,
|
||||||
vol_down = vol_down,
|
vol_down = function() _G.volume_control:volume_delta(-1) end,
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,23 @@
|
|||||||
local wibox = require('wibox')
|
local wibox = require('wibox')
|
||||||
local awful = require('awful')
|
local awful = require('awful')
|
||||||
local gears = require('gears')
|
|
||||||
local theme = require('src.theme')
|
local theme = require('src.theme')
|
||||||
local volume = require('src.util.volume')
|
local volume = require('src.util.volume')
|
||||||
|
|
||||||
local volumeicon = wibox.widget.imagebox(theme.widget_vol_mute)
|
local volumeicon = wibox.widget.imagebox(theme.widget_vol_mute)
|
||||||
local volume_widget = wibox.widget.textbox()
|
local volume_widget = wibox.widget.textbox()
|
||||||
|
|
||||||
local function spawn_callback(stdout)
|
awesome.connect_signal(volume.volume_changed_signal, function(vol, mute)
|
||||||
local volume_str = stdout:sub(9,12):gsub('[.]', '')
|
|
||||||
local mute = stdout:match('MUTED')
|
|
||||||
local level = tonumber(volume_str)
|
|
||||||
if level == nil then
|
|
||||||
volume_widget:set_markup('Waiting')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if mute then
|
if mute then
|
||||||
volumeicon:set_image(theme.widget_vol_mute)
|
volumeicon:set_image(theme.widget_vol_mute)
|
||||||
elseif level == 0 then
|
elseif vol == 0 then
|
||||||
volumeicon:set_image(theme.widget_vol_mute)
|
volumeicon:set_image(theme.widget_vol_mute)
|
||||||
elseif level <= 50 then
|
elseif vol <= 50 then
|
||||||
volumeicon:set_image(theme.widget_vol_low)
|
volumeicon:set_image(theme.widget_vol_low)
|
||||||
else
|
else
|
||||||
volumeicon:set_image(theme.widget_vol_high)
|
volumeicon:set_image(theme.widget_vol_high)
|
||||||
end
|
end
|
||||||
volume_widget:set_markup(level .. (mute and 'M' or '%'))
|
volume_widget.text = vol .. (mute and 'M' or '%')
|
||||||
end
|
end)
|
||||||
|
|
||||||
local function timer_callback()
|
|
||||||
awful.spawn.easy_async('wpctl get-volume @DEFAULT_AUDIO_SINK@', spawn_callback)
|
|
||||||
end
|
|
||||||
|
|
||||||
local volume_timer = gears.timer {
|
|
||||||
timeout = 30,
|
|
||||||
autostart = true,
|
|
||||||
call_now = true,
|
|
||||||
callback = timer_callback
|
|
||||||
}
|
|
||||||
|
|
||||||
awesome.connect_signal(volume.volume_changed_signal, function() volume_timer:emit_signal('timeout') end)
|
|
||||||
|
|
||||||
local volumebuttons = awful.util.table.join(
|
local volumebuttons = awful.util.table.join(
|
||||||
awful.button({}, 1, volume.toggle_mute),
|
awful.button({}, 1, volume.toggle_mute),
|
||||||
|
Loading…
Reference in New Issue
Block a user