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:
Stefano Mazzucco 2017-07-10 22:06:41 +01:00
parent a9da866952
commit 3410f9ac7a
5 changed files with 87 additions and 179 deletions

View File

@ -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

View File

@ -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"
}
}

View File

@ -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" },
}

View File

@ -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

View File

@ -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()