Mute alsa error on arch
This commit is contained in:
Generated
+2
@@ -1190,6 +1190,7 @@ dependencies = [
|
|||||||
"directories",
|
"directories",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"image",
|
"image",
|
||||||
|
"libc",
|
||||||
"open",
|
"open",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -1203,6 +1204,7 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -31,3 +31,9 @@ tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
|||||||
|
|
||||||
[target.'cfg(target_os="macos")'.dependencies]
|
[target.'cfg(target_os="macos")'.dependencies]
|
||||||
core-foundation = "0.10.1"
|
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
|
### macOS / Windows
|
||||||
|
|
||||||
No system packages required. (Windows: media keys are not wired up yet;
|
No system packages required.
|
||||||
playback works.)
|
|
||||||
|
|
||||||
## Configuration
|
## 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.
|
`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
|
- Logs: in-app on the Logs tab (`5`), and in the cache dir
|
||||||
(`furumi-cli.log`), filtered by `RUST_LOG`.
|
(`furumi-cli.log`), filtered by `RUST_LOG`.
|
||||||
|
|||||||
+41
@@ -19,6 +19,7 @@ fn main() -> Result<()> {
|
|||||||
if let Err(err) = config::logging::init() {
|
if let Err(err) = config::logging::init() {
|
||||||
startup_warning = Some(format!("logging disabled: {err:#}"));
|
startup_warning = Some(format!("logging disabled: {err:#}"));
|
||||||
}
|
}
|
||||||
|
capture_stderr();
|
||||||
let (keymap, keymap_warning) = config::keymap::Keymap::load();
|
let (keymap, keymap_warning) = config::keymap::Keymap::load();
|
||||||
let startup_warning = keymap_warning.or(startup_warning);
|
let startup_warning = keymap_warning.or(startup_warning);
|
||||||
|
|
||||||
@@ -84,6 +85,46 @@ fn run_app(
|
|||||||
result
|
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
|
/// Kitty keyboard protocol, where supported, disambiguates Esc from alt-keys
|
||||||
/// and modifier combos. The flags are popped on exit and on panic — leaving
|
/// and modifier combos. The flags are popped on exit and on panic — leaving
|
||||||
/// them pushed corrupts the user's shell.
|
/// them pushed corrupts the user's shell.
|
||||||
|
|||||||
+69
-10
@@ -4,10 +4,9 @@
|
|||||||
//! MPRemoteCommandCenter on macOS, SMTC on Windows. On macOS the command
|
//! MPRemoteCommandCenter on macOS, SMTC on Windows. On macOS the command
|
||||||
//! callbacks are only delivered while the main thread services its CFRunLoop,
|
//! 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
|
//! 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.
|
//! 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
|
||||||
//! Windows note: SMTC needs a window handle; creating a hidden window is not
|
//! is pumped the same way.
|
||||||
//! wired up yet, so media keys are skipped there with a log line.
|
|
||||||
|
|
||||||
use std::sync::mpsc::{Receiver, RecvTimeoutError};
|
use std::sync::mpsc::{Receiver, RecvTimeoutError};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -86,15 +85,23 @@ pub fn run_on_main_thread(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_controls() -> Option<MediaControls> {
|
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 {
|
let config = PlatformConfig {
|
||||||
display_name: "Furumi",
|
display_name: "Furumi",
|
||||||
dbus_name: "cy.hexor.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) {
|
match MediaControls::new(config) {
|
||||||
Ok(controls) => Some(controls),
|
Ok(controls) => Some(controls),
|
||||||
Err(err) => {
|
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) {
|
fn apply(controls: &mut MediaControls, update: MediaUpdate) {
|
||||||
let result = match update {
|
let result = match update {
|
||||||
MediaUpdate::Metadata {
|
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() {}
|
fn pump_platform_events() {}
|
||||||
|
|||||||
Reference in New Issue
Block a user