Mute alsa error on arch

This commit is contained in:
Ultradesu
2026-06-10 17:58:55 +01:00
parent 5be633d168
commit 286d42e068
5 changed files with 123 additions and 14 deletions
Generated
+2
View File
@@ -1190,6 +1190,7 @@ dependencies = [
"directories",
"futures-util",
"image",
"libc",
"open",
"ratatui",
"reqwest",
@@ -1203,6 +1204,7 @@ dependencies = [
"toml",
"tracing",
"tracing-subscriber",
"windows-sys 0.61.2",
]
[[package]]
+6
View File
@@ -31,3 +31,9 @@ tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
[target.'cfg(target_os="macos")'.dependencies]
core-foundation = "0.10.1"
[target."cfg(windows)".dependencies]
windows-sys = { version = "0.61.2", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging", "Win32_System_LibraryLoader"] }
[target."cfg(unix)".dependencies]
libc = "0.2.186"
+5 -4
View File
@@ -31,13 +31,14 @@ zbus (no libdbus), images and audio decoding are Rust crates.
### macOS / Windows
No system packages required. (Windows: media keys are not wired up yet;
playback works.)
No system packages required.
## Configuration
- `~/.config/furumi/keymap.toml` — keybinding overrides, see
- `keymap.toml` in the config dir — keybinding overrides, see
`src/config/default_keymap.toml` for the format and defaults.
- `~/.config/furumi/credentials.json` — created on login (0600).
Config dir: `~/.config/furumi` on Linux,
`~/Library/Application Support/furumi` on macOS.
- `credentials.json` in the same dir — created on login (0600).
- Logs: in-app on the Logs tab (`5`), and in the cache dir
(`furumi-cli.log`), filtered by `RUST_LOG`.
+41
View File
@@ -19,6 +19,7 @@ fn main() -> Result<()> {
if let Err(err) = config::logging::init() {
startup_warning = Some(format!("logging disabled: {err:#}"));
}
capture_stderr();
let (keymap, keymap_warning) = config::keymap::Keymap::load();
let startup_warning = keymap_warning.or(startup_warning);
@@ -84,6 +85,46 @@ fn run_app(
result
}
/// C libraries (ALSA on some distros, in particular) print warnings straight
/// to stderr, which corrupts the TUI. Replace stderr with a pipe and forward
/// every line into tracing — it lands in the Logs tab and the log file
/// instead of the screen.
#[cfg(unix)]
fn capture_stderr() {
use std::io::BufRead as _;
use std::os::fd::FromRawFd as _;
let mut fds = [0i32; 2];
// SAFETY: plain pipe/dup2 syscalls on freshly created fds.
unsafe {
if libc::pipe(fds.as_mut_ptr()) != 0 {
return;
}
let [read_fd, write_fd] = fds;
if libc::dup2(write_fd, libc::STDERR_FILENO) == -1 {
libc::close(read_fd);
libc::close(write_fd);
return;
}
libc::close(write_fd);
let reader = std::fs::File::from_raw_fd(read_fd);
std::thread::Builder::new()
.name("stderr".to_string())
.spawn(move || {
for line in std::io::BufReader::new(reader).lines() {
let Ok(line) = line else { break };
if !line.trim().is_empty() {
tracing::warn!(target: "stderr", "{line}");
}
}
})
.ok();
}
}
#[cfg(not(unix))]
fn capture_stderr() {}
/// Kitty keyboard protocol, where supported, disambiguates Esc from alt-keys
/// and modifier combos. The flags are popped on exit and on panic — leaving
/// them pushed corrupts the user's shell.
+69 -10
View File
@@ -4,10 +4,9 @@
//! MPRemoteCommandCenter on macOS, SMTC on Windows. On macOS the command
//! callbacks are only delivered while the main thread services its CFRunLoop,
//! so the app runs on a worker thread and `run_on_main_thread` keeps the main
//! thread pumping the run loop and applying metadata updates.
//!
//! Windows note: SMTC needs a window handle; creating a hidden window is not
//! wired up yet, so media keys are skipped there with a log line.
//! thread pumping the run loop and applying metadata updates. On Windows,
//! SMTC needs a window: a hidden one is created here and its message queue
//! is pumped the same way.
use std::sync::mpsc::{Receiver, RecvTimeoutError};
use std::time::Duration;
@@ -86,15 +85,23 @@ pub fn run_on_main_thread(
}
fn create_controls() -> Option<MediaControls> {
// SMTC on Windows attaches to a window; console apps create a hidden one.
#[cfg(target_os = "windows")]
let hwnd = match create_hidden_window() {
Some(hwnd) => Some(hwnd),
None => {
tracing::warn!("media keys: hidden window creation failed");
return None;
}
};
#[cfg(not(target_os = "windows"))]
let hwnd = None;
let config = PlatformConfig {
display_name: "Furumi",
dbus_name: "cy.hexor.furumi",
hwnd: None,
hwnd,
};
if cfg!(windows) {
tracing::info!("media keys: hidden-window SMTC setup not implemented yet, skipping");
return None;
}
match MediaControls::new(config) {
Ok(controls) => Some(controls),
Err(err) => {
@@ -104,6 +111,42 @@ fn create_controls() -> Option<MediaControls> {
}
}
/// An invisible top-level window owning the SMTC session. Created on the
/// main thread, which also pumps its messages in `pump_platform_events`.
#[cfg(target_os = "windows")]
fn create_hidden_window() -> Option<*mut std::ffi::c_void> {
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
use windows_sys::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, RegisterClassW, WNDCLASSW,
};
unsafe {
let class_name: Vec<u16> = "furumi_media_keys\0".encode_utf16().collect();
let instance = GetModuleHandleW(core::ptr::null());
let mut class: WNDCLASSW = core::mem::zeroed();
class.lpfnWndProc = Some(DefWindowProcW);
class.hInstance = instance;
class.lpszClassName = class_name.as_ptr();
if RegisterClassW(&class) == 0 {
return None;
}
let hwnd = CreateWindowExW(
0,
class_name.as_ptr(),
class_name.as_ptr(),
0,
0,
0,
0,
0,
core::ptr::null_mut(),
core::ptr::null_mut(),
instance,
core::ptr::null(),
);
if hwnd.is_null() { None } else { Some(hwnd) }
}
}
fn apply(controls: &mut MediaControls, update: MediaUpdate) {
let result = match update {
MediaUpdate::Metadata {
@@ -155,5 +198,21 @@ fn pump_platform_events() {
);
}
#[cfg(not(target_os = "macos"))]
/// On Windows the hidden SMTC window needs its message queue drained on the
/// thread that created it.
#[cfg(target_os = "windows")]
fn pump_platform_events() {
use windows_sys::Win32::UI::WindowsAndMessaging::{
DispatchMessageW, MSG, PM_REMOVE, PeekMessageW, TranslateMessage,
};
unsafe {
let mut msg: MSG = core::mem::zeroed();
while PeekMessageW(&mut msg, core::ptr::null_mut(), 0, 0, PM_REMOVE) != 0 {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn pump_platform_events() {}