fix(audio-switcher): update mute state after toggle (#158)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
id = "blackbartblues/audio-switcher"
|
id = "blackbartblues/audio-switcher"
|
||||||
name = "Audio Switcher"
|
name = "Audio Switcher"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
plugin_api = 9
|
plugin_api = 9
|
||||||
author = "blackbartblues"
|
author = "blackbartblues"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -1050,9 +1050,28 @@ end
|
|||||||
local function toggleMute(command, kind)
|
local function toggleMute(command, kind)
|
||||||
local target = kind == "output" and "@DEFAULT_SINK@" or "@DEFAULT_SOURCE@"
|
local target = kind == "output" and "@DEFAULT_SINK@" or "@DEFAULT_SOURCE@"
|
||||||
local pactlCommand = kind == "output" and "set-sink-mute" or "set-source-mute"
|
local pactlCommand = kind == "output" and "set-sink-mute" or "set-source-mute"
|
||||||
|
local currentMuted = kind == "output" and snapshot.outputMuted or false
|
||||||
|
if kind == "input" then currentMuted = snapshot.inputMuted end
|
||||||
|
local requestedMuted = not currentMuted
|
||||||
runPactl({ pactlCommand, target, "toggle" }, function(result)
|
runPactl({ pactlCommand, target, "toggle" }, function(result)
|
||||||
resultMessage(command, result.exitCode == 0, trim(result.stderr))
|
local ok = result.exitCode == 0
|
||||||
refreshAll()
|
resultMessage(command, ok, trim(result.stderr))
|
||||||
|
if not ok then
|
||||||
|
refreshAll()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local volume
|
||||||
|
if kind == "output" then
|
||||||
|
snapshot.outputMuted = requestedMuted
|
||||||
|
volume = snapshot.outputVolume
|
||||||
|
else
|
||||||
|
snapshot.inputMuted = requestedMuted
|
||||||
|
volume = snapshot.inputVolume
|
||||||
|
end
|
||||||
|
updateActiveDeviceVolume(kind, volume, requestedMuted)
|
||||||
|
publishSnapshot()
|
||||||
|
refreshCurrentVolume(kind)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
local function clone(value)
|
||||||
|
if type(value) ~= "table" then return value end
|
||||||
|
local result = {}
|
||||||
|
for key, item in pairs(value) do result[clone(key)] = clone(item) end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local values = {}
|
||||||
|
local watchers = {}
|
||||||
|
local pending = {}
|
||||||
|
local streamCallback = nil
|
||||||
|
|
||||||
|
local decoded = {
|
||||||
|
INFO = {
|
||||||
|
default_sink_name = "sink.main",
|
||||||
|
default_source_name = "source.main",
|
||||||
|
},
|
||||||
|
SINKS_UNMUTED = {
|
||||||
|
{
|
||||||
|
name = "sink.main",
|
||||||
|
description = "Main output",
|
||||||
|
mute = false,
|
||||||
|
volume = { front_left = { value_percent = "42%" } },
|
||||||
|
properties = {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SOURCES_UNMUTED = {
|
||||||
|
{
|
||||||
|
name = "source.main",
|
||||||
|
description = "Main input",
|
||||||
|
mute = false,
|
||||||
|
volume = { front_left = { value_percent = "37%" } },
|
||||||
|
properties = {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
OUTPUT_VOLUME = {
|
||||||
|
volume = { front_left = { value_percent = "42%" } },
|
||||||
|
},
|
||||||
|
INPUT_VOLUME = {
|
||||||
|
volume = { front_left = { value_percent = "37%" } },
|
||||||
|
},
|
||||||
|
MUTED = { mute = true },
|
||||||
|
UNMUTED = { mute = false },
|
||||||
|
}
|
||||||
|
|
||||||
|
noctalia = {
|
||||||
|
pluginDataDir = function() return nil end,
|
||||||
|
getConfig = function() return nil end,
|
||||||
|
commandExists = function(command) return command == "pactl" end,
|
||||||
|
readFile = function() return nil end,
|
||||||
|
writeFile = function() return true end,
|
||||||
|
setUpdateInterval = function() end,
|
||||||
|
runAsync = function(command, callback)
|
||||||
|
pending[#pending + 1] = { command = command, callback = callback }
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
runStream = function(_command, callback)
|
||||||
|
streamCallback = callback
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
json = {
|
||||||
|
decode = function(value) return clone(decoded[value]) end,
|
||||||
|
encode = function() return "PREFERENCES" end,
|
||||||
|
},
|
||||||
|
string = {
|
||||||
|
trim = function(value) return tostring(value or ""):match("^%s*(.-)%s*$") or "" end,
|
||||||
|
},
|
||||||
|
state = {
|
||||||
|
get = function(key) return clone(values[key]) end,
|
||||||
|
set = function(key, value)
|
||||||
|
values[key] = clone(value)
|
||||||
|
if watchers[key] ~= nil then watchers[key](clone(value)) end
|
||||||
|
end,
|
||||||
|
watch = function(key, callback) watchers[key] = callback end,
|
||||||
|
},
|
||||||
|
tr = function(key) return key end,
|
||||||
|
log = function() end,
|
||||||
|
notify = function() end,
|
||||||
|
notifyError = function() end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function complete(expected, stdout, exitCode)
|
||||||
|
local call = table.remove(pending, 1)
|
||||||
|
assert(call ~= nil, "expected pending command containing " .. expected)
|
||||||
|
assert(call.command:find(expected, 1, true) ~= nil, "unexpected command: " .. call.command)
|
||||||
|
call.callback({
|
||||||
|
exitCode = exitCode or 0,
|
||||||
|
stdout = stdout or "",
|
||||||
|
stderr = "",
|
||||||
|
timedOut = false,
|
||||||
|
stdoutTruncated = false,
|
||||||
|
stderrTruncated = false,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function snapshot()
|
||||||
|
return values["audio_switcher_snapshot"]
|
||||||
|
end
|
||||||
|
|
||||||
|
local serviceFile = assert(io.open("service.luau", "r"))
|
||||||
|
local serviceSource = serviceFile:read("*a")
|
||||||
|
serviceFile:close()
|
||||||
|
serviceSource = serviceSource:gsub("([%w_%.]+)%s*%+=%s*([^\n]+)", "%1 = %1 + %2")
|
||||||
|
serviceSource = serviceSource:gsub("([%w_%.]+)%s*%-=%s*([^\n]+)", "%1 = %1 - %2")
|
||||||
|
assert(load(serviceSource, "@service.luau"))()
|
||||||
|
assert(type(streamCallback) == "function", "pactl subscription was not started")
|
||||||
|
|
||||||
|
complete("'info'", "INFO")
|
||||||
|
complete("'list' 'sinks'", "SINKS_UNMUTED")
|
||||||
|
complete("'list' 'sources'", "SOURCES_UNMUTED")
|
||||||
|
|
||||||
|
assert(snapshot().outputMuted == false, "initial output mute state is incorrect")
|
||||||
|
assert(snapshot().inputMuted == false, "initial input mute state is incorrect")
|
||||||
|
|
||||||
|
noctalia.state.set("audio_switcher_command", {
|
||||||
|
requestId = "mute-output",
|
||||||
|
action = "toggle_output_mute",
|
||||||
|
})
|
||||||
|
streamCallback("Event 'change' on sink #1")
|
||||||
|
complete("'set-sink-mute'", "")
|
||||||
|
|
||||||
|
assert(snapshot().outputMuted == true, "successful output toggle did not update the snapshot immediately")
|
||||||
|
assert(snapshot().outputs[1].muted == true, "successful output toggle did not update the active device")
|
||||||
|
complete("'get-sink-volume'", "OUTPUT_VOLUME")
|
||||||
|
complete("'get-sink-mute'", "MUTED")
|
||||||
|
complete("'get-sink-volume'", "OUTPUT_VOLUME")
|
||||||
|
complete("'get-sink-mute'", "MUTED")
|
||||||
|
assert(snapshot().outputMuted == true, "authoritative output mute refresh lost the toggled state")
|
||||||
|
|
||||||
|
noctalia.state.set("audio_switcher_command", {
|
||||||
|
requestId = "mute-input",
|
||||||
|
action = "toggle_input_mute",
|
||||||
|
})
|
||||||
|
complete("'set-source-mute'", "")
|
||||||
|
|
||||||
|
assert(snapshot().inputMuted == true, "successful input toggle did not update the snapshot immediately")
|
||||||
|
assert(snapshot().inputs[1].muted == true, "successful input toggle did not update the active device")
|
||||||
|
complete("'get-source-volume'", "INPUT_VOLUME")
|
||||||
|
complete("'get-source-mute'", "MUTED")
|
||||||
|
assert(snapshot().inputMuted == true, "authoritative input mute refresh lost the toggled state")
|
||||||
|
|
||||||
|
noctalia.state.set("audio_switcher_command", {
|
||||||
|
requestId = "unmute-output",
|
||||||
|
action = "toggle_output_mute",
|
||||||
|
})
|
||||||
|
complete("'set-sink-mute'", "")
|
||||||
|
|
||||||
|
assert(snapshot().outputMuted == false, "successful output unmute did not update the snapshot immediately")
|
||||||
|
assert(snapshot().inputMuted == true, "output unmute changed the input mute state")
|
||||||
|
complete("'get-sink-volume'", "OUTPUT_VOLUME")
|
||||||
|
complete("'get-sink-mute'", "UNMUTED")
|
||||||
|
assert(snapshot().outputMuted == false, "authoritative output refresh lost the unmuted state")
|
||||||
|
|
||||||
|
assert(#pending == 0, "mute toggles unexpectedly started a full device refresh")
|
||||||
|
|
||||||
|
print("audio-switcher mute state tests: ok")
|
||||||
Reference in New Issue
Block a user