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(C_STANDARD 11)
|
||||
add_compile_options(-Wall -Wextra -pedantic)
|
||||
add_compile_options(-Wall -Wextra)
|
||||
|
||||
pkg_check_modules(CONSTRAIN REQUIRED x11 xi xfixes xrandr)
|
||||
|
||||
add_library(constrain SHARED lib/constrain.c)
|
||||
|
||||
target_link_libraries(constrain ${CONSTRAIN_LIBRARIES})
|
||||
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
|
||||
build:
|
||||
cmake -S . -B build
|
||||
cmake -S . -B build -G Ninja
|
||||
cmake --build build
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -vfr build
|
||||
|
||||
.PHONY: luacheck
|
||||
luacheck:
|
||||
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 {
|
||||
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 function toggle_mute()
|
||||
os.execute('wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle')
|
||||
awesome.emit_signal(volume_changed_signal)
|
||||
end
|
||||
if _G.volume_control == nil then
|
||||
print('[volume_control] init')
|
||||
local lgi = require('lgi')
|
||||
---@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()
|
||||
os.execute('wpctl set-volume @DEFAULT_AUDIO_SINK@ 1%+')
|
||||
awesome.emit_signal(volume_changed_signal)
|
||||
end
|
||||
function volume_control:get_default_node() self.node_id = volume_control.pw_call.default_node(self.node_api._native) end
|
||||
function volume_control:set_volume(vol, m) volume_control.pw_call.set_volume(self.mixer_api._native, self.node_id, vol, m) end
|
||||
function volume_control:toggle_mute() self:set_volume(self.volume, not self.mute) 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()
|
||||
os.execute('wpctl set-volume @DEFAULT_AUDIO_SINK@ 1%-')
|
||||
awesome.emit_signal(volume_changed_signal)
|
||||
function volume_control:om_installed()
|
||||
self:get_default_node()
|
||||
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
|
||||
|
||||
return {
|
||||
volume_changed_signal = volume_changed_signal,
|
||||
toggle_mute = toggle_mute,
|
||||
vol_up = vol_up,
|
||||
vol_down = vol_down,
|
||||
toggle_mute = function() _G.volume_control:toggle_mute() end,
|
||||
vol_up = function() _G.volume_control:volume_delta(1) end,
|
||||
vol_down = function() _G.volume_control:volume_delta(-1) end,
|
||||
}
|
||||
|
@ -1,44 +1,23 @@
|
||||
local wibox = require('wibox')
|
||||
local awful = require('awful')
|
||||
local gears = require('gears')
|
||||
local theme = require('src.theme')
|
||||
local volume = require('src.util.volume')
|
||||
|
||||
local volumeicon = wibox.widget.imagebox(theme.widget_vol_mute)
|
||||
local volume_widget = wibox.widget.textbox()
|
||||
|
||||
local function spawn_callback(stdout)
|
||||
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
|
||||
awesome.connect_signal(volume.volume_changed_signal, function(vol, mute)
|
||||
if mute then
|
||||
volumeicon:set_image(theme.widget_vol_mute)
|
||||
elseif level == 0 then
|
||||
elseif vol == 0 then
|
||||
volumeicon:set_image(theme.widget_vol_mute)
|
||||
elseif level <= 50 then
|
||||
elseif vol <= 50 then
|
||||
volumeicon:set_image(theme.widget_vol_low)
|
||||
else
|
||||
volumeicon:set_image(theme.widget_vol_high)
|
||||
end
|
||||
volume_widget:set_markup(level .. (mute and 'M' or '%'))
|
||||
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)
|
||||
volume_widget.text = vol .. (mute and 'M' or '%')
|
||||
end)
|
||||
|
||||
local volumebuttons = awful.util.table.join(
|
||||
awful.button({}, 1, volume.toggle_mute),
|
||||
|
Loading…
Reference in New Issue
Block a user