Replaced volume with event-based version for pipewire
This commit is contained in:
		@@ -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),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user