feat(ios,p0): iOS P0 platform, audio fixes, channel UX
This commit is contained in:
@@ -46,9 +46,7 @@ use zbus::blocking::Connection as BlockingConnection;
|
||||
use zbus::zvariant::{OwnedValue, Value};
|
||||
use zbus::{proxy, Connection as AsyncConnection};
|
||||
|
||||
use super::{
|
||||
AudioTransmitGate, DesktopPttBackend, PttBackendError, PttBinding, PttInputClass,
|
||||
};
|
||||
use super::{AudioTransmitGate, DesktopPttBackend, PttBackendError, PttBinding, PttInputClass};
|
||||
use crate::ptt::{PttBackendDescriptor, PttCapabilityLevel};
|
||||
|
||||
/// Try to construct a `LinuxGnomeWaylandBackend`. Returns `None`
|
||||
@@ -84,7 +82,9 @@ fn is_gnome_on_wayland() -> bool {
|
||||
let desktop = env::var("XDG_CURRENT_DESKTOP")
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
desktop.split(':').any(|s| s == "gnome" || s == "gnome-flashback")
|
||||
desktop
|
||||
.split(':')
|
||||
.any(|s| s == "gnome" || s == "gnome-flashback")
|
||||
}
|
||||
|
||||
// ---------- D-Bus proxies ----------
|
||||
@@ -171,11 +171,7 @@ trait Request {
|
||||
/// is `0` for success, `1` for user cancellation, `2` for
|
||||
/// other failure.
|
||||
#[zbus(signal)]
|
||||
fn response(
|
||||
&self,
|
||||
response: u32,
|
||||
results: HashMap<String, OwnedValue>,
|
||||
) -> zbus::Result<()>;
|
||||
fn response(&self, response: u32, results: HashMap<String, OwnedValue>) -> zbus::Result<()>;
|
||||
|
||||
/// Cancel an in-flight request.
|
||||
fn close(&self) -> zbus::Result<()>;
|
||||
@@ -246,13 +242,11 @@ impl LinuxGnomeWaylandBackend {
|
||||
let join_result = std::thread::Builder::new()
|
||||
.name("chanora-ptt-portal-probe".to_string())
|
||||
.spawn(|| -> Result<u32, String> {
|
||||
let conn = BlockingConnection::session()
|
||||
.map_err(|e| format!("session bus: {e}"))?;
|
||||
let proxy = BlockingGlobalShortcutsProxy::new(&conn)
|
||||
.map_err(|e| format!("proxy: {e}"))?;
|
||||
proxy
|
||||
.version()
|
||||
.map_err(|e| format!("portal version: {e}"))
|
||||
let conn =
|
||||
BlockingConnection::session().map_err(|e| format!("session bus: {e}"))?;
|
||||
let proxy =
|
||||
BlockingGlobalShortcutsProxy::new(&conn).map_err(|e| format!("proxy: {e}"))?;
|
||||
proxy.version().map_err(|e| format!("portal version: {e}"))
|
||||
})
|
||||
.map_err(|e| PttBackendError::Init(format!("probe thread spawn: {e}")))?
|
||||
.join()
|
||||
@@ -309,9 +303,10 @@ impl DesktopPttBackend for LinuxGnomeWaylandBackend {
|
||||
// Stash command + worker handles. `try_lock` is fine: the
|
||||
// backend isn't yet shared, and `start` is called once at
|
||||
// engine init.
|
||||
let mut inner = self.inner.try_lock().map_err(|_| {
|
||||
PttBackendError::Init("backend inner mutex contended".to_string())
|
||||
})?;
|
||||
let mut inner = self
|
||||
.inner
|
||||
.try_lock()
|
||||
.map_err(|_| PttBackendError::Init("backend inner mutex contended".to_string()))?;
|
||||
// Clean up any prior worker (defensive — `start` is
|
||||
// expected to be called exactly once per backend
|
||||
// instance).
|
||||
@@ -546,7 +541,9 @@ async fn create_session(
|
||||
.get("session_handle")
|
||||
.and_then(|v| <&str>::try_from(v).ok())
|
||||
.map(|s| s.to_string())
|
||||
.ok_or_else(|| zbus::Error::Failure("CreateSession returned no session_handle".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
zbus::Error::Failure("CreateSession returned no session_handle".to_string())
|
||||
})?;
|
||||
Ok(zbus::zvariant::OwnedObjectPath::try_from(session_handle)
|
||||
.map_err(|e| zbus::Error::Failure(format!("session_handle path parse: {e}")))?)
|
||||
}
|
||||
@@ -714,19 +711,13 @@ mod tests {
|
||||
#[test]
|
||||
fn classify_returns_keyboard_for_typical_trigger_description() {
|
||||
let v = shortcuts_owned_value(vec![shortcut_entry(SHORTCUT_ID, Some("Ctrl+Alt+P"))]);
|
||||
assert_eq!(
|
||||
classify_shortcuts_value(&v),
|
||||
Some(PttInputClass::Keyboard)
|
||||
);
|
||||
assert_eq!(classify_shortcuts_value(&v), Some(PttInputClass::Keyboard));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_returns_keyboard_when_trigger_description_missing() {
|
||||
let v = shortcuts_owned_value(vec![shortcut_entry(SHORTCUT_ID, None)]);
|
||||
assert_eq!(
|
||||
classify_shortcuts_value(&v),
|
||||
Some(PttInputClass::Keyboard)
|
||||
);
|
||||
assert_eq!(classify_shortcuts_value(&v), Some(PttInputClass::Keyboard));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -38,9 +38,7 @@ use std::time::Duration;
|
||||
use tokio::sync::watch;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use super::{
|
||||
AudioTransmitGate, DesktopPttBackend, PttBackendError, PttBinding, PttInputClass,
|
||||
};
|
||||
use super::{AudioTransmitGate, DesktopPttBackend, PttBackendError, PttBinding, PttInputClass};
|
||||
use crate::ptt::{PttBackendDescriptor, PttCapabilityLevel};
|
||||
|
||||
// ---------- FFI ----------
|
||||
@@ -407,15 +405,10 @@ impl MacOSEventTapBackend {
|
||||
|
||||
/// Build the descriptor for a given permission + binding pair
|
||||
/// (used by `descriptor()` and the re-query worker).
|
||||
fn build_descriptor(
|
||||
permission: PermissionState,
|
||||
class: PttInputClass,
|
||||
) -> PttBackendDescriptor {
|
||||
fn build_descriptor(permission: PermissionState, class: PttInputClass) -> PttBackendDescriptor {
|
||||
let level = match permission {
|
||||
PermissionState::Granted => match class {
|
||||
PttInputClass::MouseSideButton => {
|
||||
PttCapabilityLevel::L3GlobalWithMouseButtons
|
||||
}
|
||||
PttInputClass::MouseSideButton => PttCapabilityLevel::L3GlobalWithMouseButtons,
|
||||
_ => PttCapabilityLevel::L2GlobalHoldToTalk,
|
||||
},
|
||||
// Undetermined or Denied (we wouldn't be here for
|
||||
@@ -535,9 +528,8 @@ impl DesktopPttBackend for MacOSEventTapBackend {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let source = unsafe {
|
||||
CFMachPortCreateRunLoopSource(std::ptr::null_mut(), port, 0)
|
||||
};
|
||||
let source =
|
||||
unsafe { CFMachPortCreateRunLoopSource(std::ptr::null_mut(), port, 0) };
|
||||
if source.is_null() {
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
@@ -613,10 +605,7 @@ impl DesktopPttBackend for MacOSEventTapBackend {
|
||||
let now = query_permission();
|
||||
if now != last {
|
||||
perm_atomic.store(now.to_u8(), Ordering::Relaxed);
|
||||
let desc = MacOSEventTapBackend::build_descriptor(
|
||||
now,
|
||||
perm_binding_class,
|
||||
);
|
||||
let desc = MacOSEventTapBackend::build_descriptor(now, perm_binding_class);
|
||||
let _ = perm_desc_tx.send(desc);
|
||||
info!(
|
||||
target: "chanora_audio",
|
||||
@@ -711,9 +700,7 @@ extern "C" fn tap_callback(
|
||||
// restart the app. The CGEvent docs explicitly say returning
|
||||
// the event unchanged is the correct no-op for these
|
||||
// notification types.
|
||||
if etype == KCG_EVENT_TAP_DISABLED_BY_TIMEOUT
|
||||
|| etype == KCG_EVENT_TAP_DISABLED_BY_USER_INPUT
|
||||
{
|
||||
if etype == KCG_EVENT_TAP_DISABLED_BY_TIMEOUT || etype == KCG_EVENT_TAP_DISABLED_BY_USER_INPUT {
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
event = "tap_disabled",
|
||||
@@ -735,9 +722,7 @@ extern "C" fn tap_callback(
|
||||
if bound < 0 {
|
||||
return event;
|
||||
}
|
||||
let kc = unsafe {
|
||||
CGEventGetIntegerValueField(event, KCG_KEYBOARD_EVENT_KEYCODE)
|
||||
};
|
||||
let kc = unsafe { CGEventGetIntegerValueField(event, KCG_KEYBOARD_EVENT_KEYCODE) };
|
||||
if kc == bound as i64 {
|
||||
let pressed = etype == KCG_EVENT_KEY_DOWN;
|
||||
state.gate.set(pressed);
|
||||
@@ -748,9 +733,7 @@ extern "C" fn tap_callback(
|
||||
if bound < 0 {
|
||||
return event;
|
||||
}
|
||||
let btn = unsafe {
|
||||
CGEventGetIntegerValueField(event, KCG_MOUSE_EVENT_BUTTON_NUMBER)
|
||||
};
|
||||
let btn = unsafe { CGEventGetIntegerValueField(event, KCG_MOUSE_EVENT_BUTTON_NUMBER) };
|
||||
if btn == bound as i64 {
|
||||
let pressed = etype == KCG_EVENT_OTHER_MOUSE_DOWN;
|
||||
state.gate.set(pressed);
|
||||
@@ -812,10 +795,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn build_descriptor_granted_none_reports_L2_keyboard() {
|
||||
let d = MacOSEventTapBackend::build_descriptor(
|
||||
PermissionState::Granted,
|
||||
PttInputClass::None,
|
||||
);
|
||||
let d =
|
||||
MacOSEventTapBackend::build_descriptor(PermissionState::Granted, PttInputClass::None);
|
||||
assert_eq!(d.level, PttCapabilityLevel::L2GlobalHoldToTalk);
|
||||
assert_eq!(d.bound_input_class, None);
|
||||
}
|
||||
|
||||
@@ -29,16 +29,15 @@ use windows::core::{w, PCWSTR};
|
||||
use windows::Win32::Foundation::{HMODULE, HWND, LPARAM, LRESULT, WPARAM};
|
||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows::Win32::UI::Input::{
|
||||
GetRawInputData, RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE,
|
||||
RAWINPUTHEADER, RID_INPUT, RIDEV_INPUTSINK, RIDEV_REMOVE, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
GetRawInputData, RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTHEADER,
|
||||
RIDEV_INPUTSINK, RIDEV_REMOVE, RID_INPUT, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
CallNextHookEx, CreateWindowExW, DefWindowProcW, DispatchMessageW, GetMessageW,
|
||||
PostThreadMessageW, RegisterClassExW, SetWindowsHookExW, TranslateMessage,
|
||||
UnhookWindowsHookEx, HC_ACTION, HHOOK, HOOKPROC, KBDLLHOOKSTRUCT, MSG, MSLLHOOKSTRUCT,
|
||||
WH_KEYBOARD_LL, WH_MOUSE_LL, WINDOW_EX_STYLE, WINDOW_STYLE, WM_INPUT, WM_KEYDOWN, WM_KEYUP,
|
||||
WM_QUIT, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, XBUTTON1,
|
||||
XBUTTON2,
|
||||
PostThreadMessageW, RegisterClassExW, SetWindowsHookExW, TranslateMessage, UnhookWindowsHookEx,
|
||||
HC_ACTION, HHOOK, HOOKPROC, KBDLLHOOKSTRUCT, MSG, MSLLHOOKSTRUCT, WH_KEYBOARD_LL, WH_MOUSE_LL,
|
||||
WINDOW_EX_STYLE, WINDOW_STYLE, WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_QUIT, WM_SYSKEYDOWN,
|
||||
WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, XBUTTON1, XBUTTON2,
|
||||
};
|
||||
|
||||
use super::{AudioTransmitGate, DesktopPttBackend, PttBackendError, PttBinding};
|
||||
@@ -467,10 +466,7 @@ unsafe fn run_raw_input_loop(
|
||||
hwndTarget: hwnd,
|
||||
},
|
||||
];
|
||||
let reg_ok = RegisterRawInputDevices(
|
||||
&devices,
|
||||
std::mem::size_of::<RAWINPUTDEVICE>() as u32,
|
||||
);
|
||||
let reg_ok = RegisterRawInputDevices(&devices, std::mem::size_of::<RAWINPUTDEVICE>() as u32);
|
||||
if reg_ok.is_err() {
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
@@ -920,11 +916,7 @@ unsafe extern "system" fn kbd_hook_proc(code: i32, wparam: WPARAM, lparam: LPARA
|
||||
/// `KBDLLHOOKSTRUCT` from `lparam` then calls into this helper so
|
||||
/// the tests can exercise the press-edge translation without
|
||||
/// installing a global hook.
|
||||
pub(crate) fn dispatch_hook_keyboard(
|
||||
ctx: &HookContext,
|
||||
wparam: WPARAM,
|
||||
kb: &KBDLLHOOKSTRUCT,
|
||||
) {
|
||||
pub(crate) fn dispatch_hook_keyboard(ctx: &HookContext, wparam: WPARAM, kb: &KBDLLHOOKSTRUCT) {
|
||||
if ctx.binding.class() != 1 {
|
||||
return;
|
||||
}
|
||||
@@ -1206,10 +1198,7 @@ mod tests {
|
||||
// Keyboard class + mouse-side-button key string: the
|
||||
// keymap parses the string as a key label and finds no
|
||||
// match → None.
|
||||
let r = resolve_binding(&binding(
|
||||
PttInputClass::Keyboard,
|
||||
"mouse-side-button:8",
|
||||
));
|
||||
let r = resolve_binding(&binding(PttInputClass::Keyboard, "mouse-side-button:8"));
|
||||
assert_eq!(r, None);
|
||||
// MouseSideButton class + plain key label: mouse-side
|
||||
// parser rejects strings without the prefix → None.
|
||||
|
||||
Reference in New Issue
Block a user