From 3078dbf912ce8cb12d4c7afdb366bb60dfbd0ecd Mon Sep 17 00:00:00 2001
From: Tuan Nguyen <38831897+justmangoou@users.noreply.github.com>
Date: Sat, 27 Jun 2026 15:30:30 +0700
Subject: [PATCH 1/2] initial commit
---
assets/encoders/headphonesBig.svg | 3 +
assets/encoders/headphonesBigMuted.svg | 6 ++
assets/encoders/microphoneBig.svg | 4 +
assets/encoders/microphoneBigMuted.svg | 6 ++
assets/manifest.json | 3 +
.../voice_settings/audio_device_utils.rs | 1 +
src/actions/voice_settings/volume_control.rs | 102 +++++++++++++++++-
src/rpc_events.rs | 6 ++
8 files changed, 130 insertions(+), 1 deletion(-)
create mode 100644 assets/encoders/headphonesBig.svg
create mode 100644 assets/encoders/headphonesBigMuted.svg
create mode 100644 assets/encoders/microphoneBig.svg
create mode 100644 assets/encoders/microphoneBigMuted.svg
diff --git a/assets/encoders/headphonesBig.svg b/assets/encoders/headphonesBig.svg
new file mode 100644
index 0000000..6c804b5
--- /dev/null
+++ b/assets/encoders/headphonesBig.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/encoders/headphonesBigMuted.svg b/assets/encoders/headphonesBigMuted.svg
new file mode 100644
index 0000000..eb880e7
--- /dev/null
+++ b/assets/encoders/headphonesBigMuted.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/encoders/microphoneBig.svg b/assets/encoders/microphoneBig.svg
new file mode 100644
index 0000000..4aff504
--- /dev/null
+++ b/assets/encoders/microphoneBig.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/encoders/microphoneBigMuted.svg b/assets/encoders/microphoneBigMuted.svg
new file mode 100644
index 0000000..7d3e5d6
--- /dev/null
+++ b/assets/encoders/microphoneBigMuted.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/manifest.json b/assets/manifest.json
index 27f19aa..9a3916c 100644
--- a/assets/manifest.json
+++ b/assets/manifest.json
@@ -94,6 +94,9 @@
"Icon": "actions/volumeControl",
"Tooltip": "Control the volume levels of audio input and output devices",
"Controllers": ["Keypad", "Encoder"],
+ "Encoder": {
+ "layout": "$B1"
+ },
"PropertyInspectorPath": "pi/volumecontrol.html",
"DisableAutomaticStates": true,
"States": [{ "Image": "actions/volumeControl" }]
diff --git a/src/actions/voice_settings/audio_device_utils.rs b/src/actions/voice_settings/audio_device_utils.rs
index 3e805fd..a1049ea 100644
--- a/src/actions/voice_settings/audio_device_utils.rs
+++ b/src/actions/voice_settings/audio_device_utils.rs
@@ -56,6 +56,7 @@ pub struct AudioDeviceWrapper {
pub device_id: String,
pub volume: f32,
pub available_devices: Vec,
+ pub enable: bool,
}
impl From for SetVoiceSettingsArgs {
diff --git a/src/actions/voice_settings/volume_control.rs b/src/actions/voice_settings/volume_control.rs
index 793c9a3..4f0a6ab 100644
--- a/src/actions/voice_settings/volume_control.rs
+++ b/src/actions/voice_settings/volume_control.rs
@@ -1,6 +1,7 @@
use super::audio_device_utils::{AudioDeviceType, AudioDeviceWrapper, get_audio_device_settings};
use super::update_voice_setting;
+use discord_ipc_rust::models::send::commands::SetVoiceSettingsArgs;
use openaction::{Action, ActionUuid, Instance, OpenActionResult, async_trait};
use serde::{Deserialize, Serialize};
@@ -32,6 +33,55 @@ impl Default for VolumeControlSettings {
}
}
+const MUTED_ICON: &str = include_str!("../../../assets/encoders/microphoneBigMuted.svg");
+const UNMUTED_ICON: &str = include_str!("../../../assets/encoders/microphoneBig.svg");
+const DEAFEN_ICON: &str = include_str!("../../../assets/encoders/headphonesBigMuted.svg");
+const UNDEAFEN_ICON: &str = include_str!("../../../assets/encoders/headphonesBig.svg");
+
+async fn update_feedback(
+ instance: &Instance,
+ settings: &VolumeControlSettings,
+) -> OpenActionResult<()> {
+ let device_type = &settings.device_type;
+ let Some(device_settings) = get_audio_device_settings(device_type).await else {
+ log::error!(
+ "Failed to obtain voice settings for {:?} device",
+ device_type
+ );
+ instance.show_alert().await?;
+ return Ok(());
+ };
+
+ let icon = match (device_type, device_settings.enable) {
+ (AudioDeviceType::Input, true) => UNMUTED_ICON,
+ (AudioDeviceType::Input, false) => MUTED_ICON,
+ (AudioDeviceType::Output, true) => UNDEAFEN_ICON,
+ (AudioDeviceType::Output, false) => DEAFEN_ICON,
+ };
+
+ let value = device_type.to_linear(device_settings.volume) as i32;
+ let indicator_value = match device_type {
+ AudioDeviceType::Input => value,
+ AudioDeviceType::Output => value / 2,
+ };
+
+ let title = match device_type {
+ AudioDeviceType::Input => "Microphone",
+ AudioDeviceType::Output => "Headphones",
+ };
+
+ instance
+ .set_feedback(&serde_json::json!({
+ "title": title,
+ "icon": icon,
+ "value": format!("{}%", value),
+ "indicator": {
+ "value": indicator_value
+ }
+ }))
+ .await
+}
+
async fn adjust_volume(
instance: &Instance,
device_type: &AudioDeviceType,
@@ -69,6 +119,14 @@ impl Action for VolumeControlAction {
const UUID: ActionUuid = "me.amankhanna.oadiscord.volumecontrol";
type Settings = VolumeControlSettings;
+ async fn did_receive_settings(
+ &self,
+ instance: &Instance,
+ settings: &Self::Settings,
+ ) -> OpenActionResult<()> {
+ update_feedback(instance, settings).await
+ }
+
async fn key_up(&self, instance: &Instance, settings: &Self::Settings) -> OpenActionResult<()> {
let value = match settings.action_type {
VolumeControlActionType::Increase => settings.step_size as f32,
@@ -85,6 +143,49 @@ impl Action for VolumeControlAction {
.await
}
+ async fn dial_up(
+ &self,
+ instance: &Instance,
+ settings: &Self::Settings,
+ ) -> OpenActionResult<()> {
+ let device_type = &settings.device_type;
+ let Some(device_settings) = get_audio_device_settings(device_type).await else {
+ log::error!(
+ "Failed to obtain voice settings for {:?} device",
+ device_type
+ );
+ instance.show_alert().await?;
+ return Ok(());
+ };
+
+ match device_type {
+ AudioDeviceType::Input => {
+ let new_mute = !device_settings.enable;
+ update_voice_setting(
+ instance,
+ SetVoiceSettingsArgs {
+ mute: Some(new_mute),
+ ..Default::default()
+ },
+ if new_mute { 1 } else { 0 },
+ )
+ .await
+ }
+ AudioDeviceType::Output => {
+ let new_deaf = !device_settings.enable;
+ update_voice_setting(
+ instance,
+ SetVoiceSettingsArgs {
+ deaf: Some(new_deaf),
+ ..Default::default()
+ },
+ if new_deaf { 1 } else { 0 },
+ )
+ .await
+ }
+ }
+ }
+
async fn dial_rotate(
&self,
instance: &Instance,
@@ -93,7 +194,6 @@ impl Action for VolumeControlAction {
_pressed: bool,
) -> OpenActionResult<()> {
let delta = (settings.step_size as f32) * ticks as f32;
-
adjust_volume(instance, &settings.device_type, delta, false).await
}
}
diff --git a/src/rpc_events.rs b/src/rpc_events.rs
index b0efed5..abc828e 100644
--- a/src/rpc_events.rs
+++ b/src/rpc_events.rs
@@ -160,6 +160,7 @@ async fn apply_voice_state(settings: discord_ipc_rust::models::shared::voice::Vo
device_id: input.device_id,
volume: input.volume,
available_devices: input.available_devices,
+ enable: mute,
});
}
@@ -171,9 +172,14 @@ async fn apply_voice_state(settings: discord_ipc_rust::models::shared::voice::Vo
device_id: output.device_id,
volume: output.volume,
available_devices: output.available_devices,
+ enable: deaf,
});
}
+ for instance in visible_instances(crate::actions::VolumeControlAction::UUID).await {
+ let _ = instance.get_settings().await;
+ }
+
for instance in visible_instances(crate::actions::SetAudioDeviceAction::UUID).await {
let _ = crate::actions::voice_settings::set_audio_device::send_available_devices_to_pi(
&instance,
From 6f73a2ac867369e064c6d8e781edbfe00a117dae Mon Sep 17 00:00:00 2001
From: Tuan Nguyen <38831897+justmangoou@users.noreply.github.com>
Date: Sat, 27 Jun 2026 16:26:03 +0700
Subject: [PATCH 2/2] fix: logic
---
src/actions/voice_settings/volume_control.rs | 16 ++++++++++++----
src/rpc_events.rs | 4 ++--
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/actions/voice_settings/volume_control.rs b/src/actions/voice_settings/volume_control.rs
index 4f0a6ab..f0ca215 100644
--- a/src/actions/voice_settings/volume_control.rs
+++ b/src/actions/voice_settings/volume_control.rs
@@ -119,6 +119,14 @@ impl Action for VolumeControlAction {
const UUID: ActionUuid = "me.amankhanna.oadiscord.volumecontrol";
type Settings = VolumeControlSettings;
+ async fn will_appear(
+ &self,
+ instance: &Instance,
+ settings: &Self::Settings,
+ ) -> OpenActionResult<()> {
+ update_feedback(instance, settings).await
+ }
+
async fn did_receive_settings(
&self,
instance: &Instance,
@@ -160,26 +168,26 @@ impl Action for VolumeControlAction {
match device_type {
AudioDeviceType::Input => {
- let new_mute = !device_settings.enable;
+ let new_mute = device_settings.enable;
update_voice_setting(
instance,
SetVoiceSettingsArgs {
mute: Some(new_mute),
..Default::default()
},
- if new_mute { 1 } else { 0 },
+ 0,
)
.await
}
AudioDeviceType::Output => {
- let new_deaf = !device_settings.enable;
+ let new_deaf = device_settings.enable;
update_voice_setting(
instance,
SetVoiceSettingsArgs {
deaf: Some(new_deaf),
..Default::default()
},
- if new_deaf { 1 } else { 0 },
+ 0,
)
.await
}
diff --git a/src/rpc_events.rs b/src/rpc_events.rs
index abc828e..cd1e6da 100644
--- a/src/rpc_events.rs
+++ b/src/rpc_events.rs
@@ -160,7 +160,7 @@ async fn apply_voice_state(settings: discord_ipc_rust::models::shared::voice::Vo
device_id: input.device_id,
volume: input.volume,
available_devices: input.available_devices,
- enable: mute,
+ enable: !mute && !deaf,
});
}
@@ -172,7 +172,7 @@ async fn apply_voice_state(settings: discord_ipc_rust::models::shared::voice::Vo
device_id: output.device_id,
volume: output.volume,
available_devices: output.available_devices,
- enable: deaf,
+ enable: !deaf,
});
}