Mute alsa error on arch
This commit is contained in:
Generated
+2
@@ -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]]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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() {}
|
||||
|
||||
Reference in New Issue
Block a user