Unify code from widget and widget_client files
It turns out that Awesome can automatically deal with GLib's main loop and we don't need an external process to listen to pulseaudio's DBus. This greatly simplifies the code since we: - can merge the widget and widget_client files - don't need to do all the string parsing but can modify the widget directly Credit to https://github.com/psychon for raising the issue and providing sample code. Closes #1 (https://github.com/stefano-m/awesome-pulseaudio_widget/issues/1)
This commit is contained in:
parent
a9da866952
commit
3410f9ac7a
12
README.md
12
README.md
|
@ -4,18 +4,6 @@ A widget for the Awesome Window Manager (version 4.x) that
|
|||
uses [pulseaudio_dbus](https://github.com/stefano-m/lua-pulseaudio_dbus) to
|
||||
control your audio devices.
|
||||
|
||||
## A note about PulseAudio, DBus and Awesome
|
||||
|
||||
The Pulseaudio DBus interface requires clients to use peer-to-peer connection
|
||||
rather than the usual system/session buses. This means that we *cannot* use the
|
||||
Awesome DBus API that supports *only* system and session buses.
|
||||
|
||||
The solution is to run an external client application to establish a
|
||||
peer-to-peer connection and listen to DBus signals. The output of the client is
|
||||
read by the widget that updates itself accordingly. This is done thanks
|
||||
to
|
||||
[`awful.spawn.with_line_callback`](https://awesomewm.org/apidoc/libraries/awful.spawn.html#with_line_callback).
|
||||
|
||||
# Requirements
|
||||
|
||||
In addition to the requirements listed in the `rockspec` file, you will need
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package = "pulseaudio_widget"
|
||||
version = "0.3.1-1"
|
||||
source = {
|
||||
url = "git://github.com/stefano-m/awesome-pulseaudio_widget",
|
||||
tag = "v0.3.1"
|
||||
}
|
||||
description = {
|
||||
summary = "A PulseAudio widget for the Awesome Window Manager",
|
||||
detailed = [[
|
||||
Control your audio in the Awesome with PulseAudio and DBus.
|
||||
]],
|
||||
homepage = "https://github.com/stefano-m/awesome-pulseaudio_widget",
|
||||
license = "GPL v3"
|
||||
}
|
||||
supported_platforms = {
|
||||
"linux"
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1",
|
||||
"pulseaudio_dbus >= 0.11.0, < 0.12"
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
pulseaudio_widget = "pulseaudio_widget.lua"
|
||||
}
|
||||
}
|
|
@ -19,6 +19,5 @@ dependencies = {
|
|||
supported_platforms = { "linux" }
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = { pulseaudio_widget = "pulseaudio_widget.lua",
|
||||
pulseaudio_widget_client = "pulseaudio_widget_client.lua"},
|
||||
modules = { pulseaudio_widget = "pulseaudio_widget.lua" },
|
||||
}
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
This program was inspired by the
|
||||
[Awesome Pulseaudio Widget (APW)](https://github.com/mokasin/apw)
|
||||
]]
|
||||
|
||||
local awesome = awesome -- luacheck: ignore
|
||||
local string = string
|
||||
|
||||
local awful = require("awful")
|
||||
|
@ -124,63 +122,44 @@ function widget.toggle_muted_mic()
|
|||
end
|
||||
end
|
||||
|
||||
function widget:kill_client()
|
||||
if type(self.server_pid) == "number" then
|
||||
awful.spawn("kill -TERM " .. self.server_pid)
|
||||
end
|
||||
end
|
||||
|
||||
function widget:run_client()
|
||||
|
||||
local function update_after_signal(line, regex, sub, sep)
|
||||
sep = sep or " "
|
||||
local v, found = line:gsub(regex, sub)
|
||||
if found ~= 0 then
|
||||
local idx = v:find(sep)
|
||||
local vol = v:sub(1, idx - 1)
|
||||
local path = v:sub(idx + 1)
|
||||
if path:find("/sink%d+$") then
|
||||
self:update_appearance(vol)
|
||||
self.notify(vol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local pid = awful.spawn.with_line_callback(
|
||||
[[lua -e 'require("pulseaudio_widget_client")']],
|
||||
{
|
||||
stdout = function (line)
|
||||
|
||||
update_after_signal(line, "^(VolumeUpdated:%s+)(%d+)(|)([%w/]+)", "%2 %4")
|
||||
|
||||
update_after_signal(line, "^(MuteUpdated:%s+)(%w+)(|)([%w/]+)", "%2 %4")
|
||||
|
||||
local v, found
|
||||
v, found = line:gsub("^(NewSink:%s+)(/.*%w)", "%2")
|
||||
if found ~=0 then
|
||||
self:update_sink(v)
|
||||
local volume = self.sink:is_muted() and "Muted" or self.sink:get_volume_percent()[1]
|
||||
self:update_appearance(volume)
|
||||
self.notify(volume)
|
||||
end
|
||||
|
||||
v, found = line:gsub("^(NewSource:%s+)(/.*%w)", "%2")
|
||||
if found ~=0 then
|
||||
self:update_source({v})
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
self.server_pid = pid
|
||||
end
|
||||
|
||||
widget:buttons(gears.table.join(
|
||||
awful.button({ }, 1, widget.toggle_muted),
|
||||
awful.button({ }, 3, function () awful.spawn(widget.mixer) end),
|
||||
awful.button({ }, 4, widget.volume_up),
|
||||
awful.button({ }, 5, widget.volume_down)))
|
||||
|
||||
awesome.connect_signal("exit", function () widget:kill_client() end)
|
||||
function widget:connect_device(device)
|
||||
if not device then
|
||||
return
|
||||
end
|
||||
|
||||
if device.signals.VolumeUpdated then
|
||||
device:connect_signal(
|
||||
function (this, vols)
|
||||
-- FIXME: BaseVolume for sources (i.e. microphones) won't give the correct percentage
|
||||
local v = math.ceil(tonumber(vols[1][1]) / this.BaseVolume * 100)
|
||||
if this.object_path == self.sink.object_path then
|
||||
self:update_appearance(v)
|
||||
self.notify(v)
|
||||
end
|
||||
end,
|
||||
"VolumeUpdated"
|
||||
)
|
||||
end
|
||||
|
||||
if device.signals.MuteUpdated then
|
||||
device:connect_signal(
|
||||
function (this, is_mute)
|
||||
local m = is_mute[1] and "Muted" or "Unmuted"
|
||||
if this.object_path == self.sink.object_path then
|
||||
self:update_appearance(m)
|
||||
self.notify(m)
|
||||
end
|
||||
end,
|
||||
"MuteUpdated"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function widget:init()
|
||||
local status, address = pcall(pulse.get_address)
|
||||
|
@ -196,15 +175,40 @@ function widget:init()
|
|||
self.connection = pulse.get_connection(address)
|
||||
self.core = pulse.get_core(self.connection)
|
||||
|
||||
-- listen on ALL objects as sinks and sources may change
|
||||
self.core:ListenForSignal("org.PulseAudio.Core1.Device.VolumeUpdated", {})
|
||||
self.core:ListenForSignal("org.PulseAudio.Core1.Device.MuteUpdated", {})
|
||||
|
||||
self.core:ListenForSignal("org.PulseAudio.Core1.NewSink", {self.core.object_path})
|
||||
self.core:connect_signal(
|
||||
function (_, newsinks)
|
||||
self:update_sink(newsinks[1])
|
||||
self:connect_device(self.sink)
|
||||
local volume = self.sink:is_muted() and "Muted" or self.sink:get_volume_percent()[1]
|
||||
self:update_appearance(volume)
|
||||
end,
|
||||
"NewSink"
|
||||
)
|
||||
|
||||
self.core:ListenForSignal("org.PulseAudio.Core1.NewSource", {self.core.object_path})
|
||||
self.core:connect_signal(
|
||||
function (_, newsources)
|
||||
self:update_source(newsources)
|
||||
self:connect_device(self.source)
|
||||
end,
|
||||
"NewSource"
|
||||
)
|
||||
|
||||
self:update_source(self.core:get_sources())
|
||||
self:connect_device(self.source)
|
||||
|
||||
local sink_path = assert(self.core:get_sinks()[1], "No sinks found")
|
||||
self:update_sink(sink_path)
|
||||
self:connect_device(self.sink)
|
||||
|
||||
local volume = self.sink:is_muted() and "Muted" or self.sink:get_volume_percent()[1]
|
||||
self:update_appearance(volume)
|
||||
|
||||
self:run_client()
|
||||
|
||||
self.__index = self
|
||||
|
||||
return self
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
--[[
|
||||
Copyright 2017 Stefano Mazzucco <stefano AT curso DOT re>
|
||||
|
||||
This program 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.
|
||||
|
||||
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
]]
|
||||
|
||||
--[[-- This module is meant to be run with
|
||||
[`awful.spawn.with_line_callback`](https://awesomewm.org/apidoc/libraries/awful.spawn.html#with_line_callback).
|
||||
|
||||
It starts a client that listens to the pulseaudio DBus server and prints to
|
||||
standard output wheter the volume or sinks (e.g. selecting the audio from the
|
||||
TV) change. The changes are printed to standard output and are used by the
|
||||
pulseaudio widget to change its appearance.
|
||||
|
||||
We must do this because Awesome's DBus API can only connect to system and
|
||||
session buses, but pulseaudio uses its own per-user connection.
|
||||
|
||||
]]
|
||||
|
||||
local pulse = require("pulseaudio_dbus")
|
||||
local GLib = require("lgi").GLib
|
||||
|
||||
local address = pulse.get_address()
|
||||
|
||||
local connection = pulse.get_connection(address)
|
||||
local core = pulse.get_core(connection)
|
||||
local sink = pulse.get_device(connection, core.Sinks[1])
|
||||
local source
|
||||
|
||||
local function get_source(conn, sources)
|
||||
for _, source_path in ipairs(sources) do
|
||||
local s = pulse.get_device(conn, source_path)
|
||||
if s.Name and not s.Name:match("%.monitor$") then
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
source = get_source(connection, core.Sources)
|
||||
|
||||
-- listen on ALL objects as sinks may change
|
||||
core:ListenForSignal("org.PulseAudio.Core1.Device.VolumeUpdated", {})
|
||||
core:ListenForSignal("org.PulseAudio.Core1.Device.MuteUpdated", {})
|
||||
|
||||
local function connect_device(s)
|
||||
|
||||
if s.signals.VolumeUpdated then
|
||||
s:connect_signal(
|
||||
function (self, vols)
|
||||
local v = math.ceil(tonumber(vols[1][1]) / self.BaseVolume * 100)
|
||||
-- FIXME: BaseVolume for sources (i.e. microphones) won't give the correct percentage
|
||||
print(string.format("VolumeUpdated: %s|%s", v, self.object_path))
|
||||
end,
|
||||
"VolumeUpdated"
|
||||
)
|
||||
end
|
||||
|
||||
if s.signals.MuteUpdated then
|
||||
s:connect_signal(
|
||||
function (self, is_mute)
|
||||
local m = is_mute[1] and "Muted" or "Unmuted"
|
||||
print(string.format("MuteUpdated: %s|%s", m, self.object_path))
|
||||
end,
|
||||
"MuteUpdated"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
connect_device(sink)
|
||||
if source then
|
||||
connect_device(source)
|
||||
end
|
||||
|
||||
core:ListenForSignal("org.PulseAudio.Core1.NewSink", {core.object_path})
|
||||
core:connect_signal(
|
||||
function (_, newsinks)
|
||||
print(string.format("NewSink: %s", newsinks[1]))
|
||||
sink = pulse.get_device(connection, newsinks[1])
|
||||
connect_device(sink)
|
||||
end,
|
||||
"NewSink"
|
||||
)
|
||||
|
||||
core:ListenForSignal("org.PulseAudio.Core1.NewSource", {core.object_path})
|
||||
core:connect_signal(
|
||||
function (_, newsources)
|
||||
print(string.format("NewSource: %s", newsources[1]))
|
||||
source = get_source(connection, newsources)
|
||||
if source then
|
||||
connect_device(source)
|
||||
end
|
||||
end,
|
||||
"NewSource"
|
||||
)
|
||||
|
||||
-- Start the client. Send SIGTERM to stop it.
|
||||
print("Starting Awesome PulseAudio Widget Client")
|
||||
GLib.MainLoop():run()
|
Loading…
Reference in New Issue