gaming: arctis-sound-manager + quickshell ANC switcher

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-07-28 19:21:37 +01:00
parent 1df7ebb302
commit 027858ff1e
4 changed files with 143 additions and 1 deletions

View file

@ -418,6 +418,7 @@ in
readonly property string systemctl: "${pkgs.systemd}/bin/systemctl"
readonly property string weatherFetch: "${weatherFetchScript}"
readonly property string ssh: "${pkgs.openssh}/bin/ssh"
readonly property string gdbus: "${pkgs.glib}/bin/gdbus"
// Unprivileged ping works via ICMP dgram sockets NixOS sets
// net.ipv4.ping_group_range for everyone (no wrapper exists).
readonly property string ping: "${pkgs.iputils}/bin/ping"
@ -2932,6 +2933,115 @@ in
}
}
${lib.optionalString (!isMacbook) ''
// Nova Pro noise cancelling talks to asm-daemon
// (Arctis Sound Manager) over the session bus.
// SetSetting("noise_cancelling", "0|1|2"), status read
// back as off/transparent/on. Card hides when the
// daemon or headset is gone.
Card {
id: ancCard
visible: ancMode !== ""
width: parent.width
property string ancMode: ""
function setMode(key, value) {
if (key === ancMode) return;
ancMode = key; // optimistic; next poll corrects if the device disagrees
ancSet.mode = value;
ancSet.running = true;
}
Process {
id: ancSet
property string mode: "0"
command: [Commands.gdbus, "call", "--session",
"--dest", "name.giacomofurlan.ArctisManager.Next",
"--object-path", "/name/giacomofurlan/ArctisManager/Next/Settings",
"--method", "name.giacomofurlan.ArctisManager.Next.Settings.SetSetting",
"noise_cancelling", mode]
}
Process {
id: ancStatus
command: [Commands.gdbus, "call", "--session",
"--dest", "name.giacomofurlan.ArctisManager.Next",
"--object-path", "/name/giacomofurlan/ArctisManager/Next/Status",
"--method", "name.giacomofurlan.ArctisManager.Next.Status.GetStatus"]
stdout: StdioCollector {
onStreamFinished: {
// gdbus prints a GVariant tuple: ('{"headset": ...}',)
const a = text.indexOf("('");
const b = text.lastIndexOf("',");
if (a < 0 || b <= a) { ancCard.ancMode = ""; return; }
try {
const st = JSON.parse(text.slice(a + 2, b));
const nc = st.headset ? st.headset.noise_cancelling : undefined;
ancCard.ancMode = (nc && nc.value) ? nc.value : "";
} catch (e) { ancCard.ancMode = ""; }
}
}
}
// Poll only while the dropdown is up; keeps ticking
// with the card hidden so a headset appearing
// mid-open reveals it.
Timer {
interval: 3000
repeat: true
triggeredOnStart: true
running: volDropdown.visible
onTriggered: ancStatus.running = true
}
Row {
spacing: 6
SIcon { anchors.verticalCenter: parent.verticalCenter; text: "headphones" }
SText {
anchors.verticalCenter: parent.verticalCenter
text: "Noise Cancelling"
font.weight: Font.Medium
}
}
Row {
width: parent.width
spacing: 6
Repeater {
model: [
{ key: "off", label: "Off", val: "0" },
{ key: "transparent", label: "Transparent", val: "1" },
{ key: "on", label: "ANC", val: "2" }
]
Rectangle {
required property var modelData
property bool active: ancCard.ancMode === modelData.key
width: (parent.width - 12) / 3
height: 26
radius: 6
color: active ? Theme.base0D : "transparent"
border.color: active ? Theme.base0D : Theme.base03
border.width: 1
SText {
anchors.centerIn: parent
text: modelData.label
font.pixelSize: 11
color: active ? Theme.base00 : Theme.base05
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: ancCard.setMode(modelData.key, modelData.val)
}
}
}
}
}
''}
// Applications card
Card {
visible: appStreamsCol.childrenRect.height > 0