diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c913e7e..ab4e97c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,4 @@ jobs: - name: Lint ML service working-directory: ml_service - run: | - uv sync - uv run ruff check . + run: uvx ruff check . diff --git a/ml_service/src/ok_claude_ml/server.py b/ml_service/src/ok_claude_ml/server.py index 7c6aadc..bee0a04 100644 --- a/ml_service/src/ok_claude_ml/server.py +++ b/ml_service/src/ok_claude_ml/server.py @@ -4,15 +4,12 @@ import logging import os import signal -import struct -import sys from .protocol import ( MSG_PING, MSG_SYNTHESIZE, MSG_TRANSCRIBE, MSG_VAD_FEED, - HEADER_SIZE, read_frame, write_error, write_frame, diff --git a/ml_service/src/ok_claude_ml/tts.py b/ml_service/src/ok_claude_ml/tts.py index ed8e15e..bfcde39 100644 --- a/ml_service/src/ok_claude_ml/tts.py +++ b/ml_service/src/ok_claude_ml/tts.py @@ -38,8 +38,6 @@ def sample_rate(self) -> int: @staticmethod def _default_model_path() -> str: - import subprocess - import shutil # Try to find a piper model in common locations for path in [ diff --git a/src/cli.rs b/src/cli.rs index cb67fb2..6e13af6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -242,11 +242,10 @@ fn force_cleanup(config: &crate::config::Config) { let pid_path = config.pid_path(); // Try to kill the daemon process from the PID file - if let Ok(pid_str) = std::fs::read_to_string(&pid_path) { - if let Ok(pid) = pid_str.trim().parse::() { + if let Ok(pid_str) = std::fs::read_to_string(&pid_path) + && let Ok(pid) = pid_str.trim().parse::() { unsafe { libc::kill(pid, libc::SIGTERM); } } - } // Kill any lingering ok-claude-ml processes let _ = std::process::Command::new("pkill") diff --git a/src/config.rs b/src/config.rs index 2cb188e..017dbe9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct Config { pub hotkey: HotkeyConfig, pub audio: AudioConfig, @@ -38,6 +39,7 @@ pub struct SttConfig { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct TtsConfig { pub model: Option, pub voice: Option, @@ -45,6 +47,7 @@ pub struct TtsConfig { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct DaemonConfig { pub socket_path: Option, pub pid_file: Option, @@ -104,16 +107,12 @@ impl Config { /// GUI-specific configuration (user-authored). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct GuiConfig { /// Preferred editor command. Falls back to $VISUAL, then $EDITOR, then "code". pub editor: Option, } -impl Default for GuiConfig { - fn default() -> Self { - Self { editor: None } - } -} impl GuiConfig { /// Resolve the editor command: config > $VISUAL > $EDITOR > "zed" @@ -121,33 +120,18 @@ impl GuiConfig { if let Some(ref ed) = self.editor { return ed.clone(); } - if let Ok(v) = std::env::var("VISUAL") { - if !v.is_empty() { + if let Ok(v) = std::env::var("VISUAL") + && !v.is_empty() { return v; } - } - if let Ok(v) = std::env::var("EDITOR") { - if !v.is_empty() { + if let Ok(v) = std::env::var("EDITOR") + && !v.is_empty() { return v; } - } "zed".to_string() } } -impl Default for Config { - fn default() -> Self { - Self { - hotkey: HotkeyConfig::default(), - audio: AudioConfig::default(), - stt: SttConfig::default(), - tts: TtsConfig::default(), - daemon: DaemonConfig::default(), - tmux: TmuxConfig::default(), - gui: GuiConfig::default(), - } - } -} impl Default for HotkeyConfig { fn default() -> Self { @@ -177,24 +161,7 @@ impl Default for SttConfig { } } -impl Default for TtsConfig { - fn default() -> Self { - Self { - model: None, - voice: None, - } - } -} -impl Default for DaemonConfig { - fn default() -> Self { - Self { - socket_path: None, - pid_file: None, - log_file: None, - } - } -} impl Default for TmuxConfig { fn default() -> Self { @@ -222,21 +189,13 @@ pub struct GuiState { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] +#[derive(Default)] pub struct PersistedSession { pub name: String, pub cwd: Option, pub shell_mode: bool, } -impl Default for PersistedSession { - fn default() -> Self { - Self { - name: String::new(), - cwd: None, - shell_mode: false, - } - } -} impl AppState { pub fn state_path() -> PathBuf { diff --git a/src/gui/app.rs b/src/gui/app.rs index 97a292c..663fb67 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -177,13 +177,12 @@ impl ClaudioApp { if !text.is_empty() { self.last_transcription = Some(text.clone()); } - if let Some(session) = self.sessions.iter().find(|s| s.id == session_id) { - if !text.is_empty() { + if let Some(session) = self.sessions.iter().find(|s| s.id == session_id) + && !text.is_empty() { // Write text to terminal without auto-submitting. // User reviews and presses Enter manually. session.write_raw(&text); } - } cx.notify(); } DaemonEvent::SessionCreated { @@ -261,12 +260,11 @@ impl ClaudioApp { return; } self.needs_focus_sync = false; - if let Some(ref id) = self.focused_session_id { - if let Some(session) = self.sessions.iter().find(|s| &s.id == id) { + if let Some(ref id) = self.focused_session_id + && let Some(session) = self.sessions.iter().find(|s| &s.id == id) { let handle = session.terminal_view.read(cx).focus_handle().clone(); window.focus(&handle); } - } } pub fn focus_session_by_id(&mut self, id: &str, cx: &mut Context) { @@ -453,13 +451,12 @@ impl ClaudioApp { let config = crate::config::Config::load().ok(); if let Some(ref config) = config { let pid_path = config.pid_path(); - if let Ok(pid_str) = std::fs::read_to_string(&pid_path) { - if let Ok(pid) = pid_str.trim().parse::() { + if let Ok(pid_str) = std::fs::read_to_string(&pid_path) + && let Ok(pid) = pid_str.trim().parse::() { unsafe { libc::kill(pid, libc::SIGTERM); } } - } let _ = std::process::Command::new("pkill") .args(["-f", "ok-claude-ml"]) .status(); @@ -480,8 +477,8 @@ impl ClaudioApp { if let Some(parent) = log_path.parent() { let _ = std::fs::create_dir_all(parent); } - if let Ok(log_file) = std::fs::File::create(&log_path) { - if let Ok(log_file2) = log_file.try_clone() { + if let Ok(log_file) = std::fs::File::create(&log_path) + && let Ok(log_file2) = log_file.try_clone() { let _ = std::process::Command::new(exe) .args(["start", "--foreground"]) .stdout(std::process::Stdio::from(log_file2)) @@ -489,7 +486,6 @@ impl ClaudioApp { .stdin(std::process::Stdio::null()) .spawn(); } - } } // Wait for daemon to come up @@ -620,13 +616,12 @@ impl ClaudioApp { } fn toggle_autopilot(&mut self, _: &ToggleAutopilot, _window: &mut Window, cx: &mut Context) { - if let Some(ref id) = self.focused_session_id { - if let Some(session) = self.sessions.iter().find(|s| &s.id == id) { + if let Some(ref id) = self.focused_session_id + && let Some(session) = self.sessions.iter().find(|s| &s.id == id) { let prev = session.autopilot.load(Ordering::Relaxed); session.autopilot.store(!prev, Ordering::Relaxed); cx.notify(); } - } } fn stop_speech(&mut self, _: &StopSpeech, _window: &mut Window, _cx: &mut Context) { @@ -650,8 +645,8 @@ impl ClaudioApp { None }; - if let Some(raw_text) = raw_text { - if !raw_text.trim().is_empty() { + if let Some(raw_text) = raw_text + && !raw_text.trim().is_empty() { // Kill any ongoing speech let _ = std::process::Command::new("pkill").arg("-f").arg("pw-play.*/tmp/claudio_tts").output(); @@ -711,7 +706,6 @@ impl ClaudioApp { } }); } - } } fn default_cwd(&self) -> PathBuf { @@ -760,11 +754,10 @@ impl ClaudioApp { } pub fn inject_text_to_focused(&self, text: &str) { - if let Some(ref id) = self.focused_session_id { - if let Some(session) = self.sessions.iter().find(|s| &s.id == id) { + if let Some(ref id) = self.focused_session_id + && let Some(session) = self.sessions.iter().find(|s| &s.id == id) { session.write_raw(text); } - } } pub fn open_in_editor(&self, dir: &Path) { @@ -831,12 +824,11 @@ impl ClaudioApp { cx.notify(); } _ => { - if let Some(ch) = &ev.keystroke.key_char { - if !ev.keystroke.modifiers.control && !ev.keystroke.modifiers.alt { + if let Some(ch) = &ev.keystroke.key_char + && !ev.keystroke.modifiers.control && !ev.keystroke.modifiers.alt { self.worktree_name_input.push_str(ch); cx.notify(); } - } } } return; @@ -851,12 +843,11 @@ impl ClaudioApp { cx.notify(); } _ => { - if let Some(ch) = &ev.keystroke.key_char { - if !ev.keystroke.modifiers.control && !ev.keystroke.modifiers.alt { + if let Some(ch) = &ev.keystroke.key_char + && !ev.keystroke.modifiers.control && !ev.keystroke.modifiers.alt { self.rename_input.push_str(ch); cx.notify(); } - } } } return; @@ -873,12 +864,11 @@ impl ClaudioApp { cx.notify(); } _ => { - if let Some(ch) = &ev.keystroke.key_char { - if !ev.keystroke.modifiers.control && !ev.keystroke.modifiers.alt { + if let Some(ch) = &ev.keystroke.key_char + && !ev.keystroke.modifiers.control && !ev.keystroke.modifiers.alt { self.file_tree.search_query.push_str(ch); cx.notify(); } - } } } } diff --git a/src/gui/file_tree.rs b/src/gui/file_tree.rs index c2188f8..bb1a966 100644 --- a/src/gui/file_tree.rs +++ b/src/gui/file_tree.rs @@ -1,3 +1,5 @@ +#![allow(clippy::ptr_arg)] + use std::collections::HashSet; use std::path::PathBuf; diff --git a/src/gui/ipc_bridge.rs b/src/gui/ipc_bridge.rs index e2550af..dece392 100644 --- a/src/gui/ipc_bridge.rs +++ b/src/gui/ipc_bridge.rs @@ -42,11 +42,10 @@ fn run_subscription_inner(socket_path: &Path, tx: &flume::Sender) - continue; } - if let Ok(event) = serde_json::from_str::(trimmed) { - if tx.send(event).is_err() { + if let Ok(event) = serde_json::from_str::(trimmed) + && tx.send(event).is_err() { break; // GUI closed } - } } Ok(()) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 4d4bd83..72896d5 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -13,7 +13,6 @@ mod theme; use std::path::Path; use anyhow::Result; -use gpui; use gpui::AppContext; use self::app::ClaudioApp; @@ -21,6 +20,8 @@ use self::app::ClaudioApp; pub fn run(socket_path: &Path) -> Result<()> { let socket = socket_path.to_path_buf(); + force_x11_on_gnome(); + let app = gpui::Application::new(); app.run(move |cx: &mut gpui::App| { actions::register(cx); @@ -49,3 +50,24 @@ pub fn run(socket_path: &Path) -> Result<()> { Ok(()) } + +// GNOME/Mutter refuses xdg-decoration server-side mode and GPUI does not draw +// its own CSD, leaving the window chromeless. Routing through XWayland gets +// SSD from mutter-x11-frames. Other Wayland compositors honor SSD natively. +fn force_x11_on_gnome() { + if std::env::var_os("CLAUDIO_FORCE_WAYLAND").is_some() { + return; + } + let desktop = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); + let on_gnome = desktop.split(':').any(|s| s.eq_ignore_ascii_case("GNOME")); + let on_wayland = std::env::var_os("WAYLAND_DISPLAY") + .map(|v| !v.is_empty()) + .unwrap_or(false); + let has_x11 = std::env::var_os("DISPLAY") + .map(|v| !v.is_empty()) + .unwrap_or(false); + if on_gnome && on_wayland && has_x11 { + // SAFETY: called before gpui (and therefore any threads) start. + unsafe { std::env::remove_var("WAYLAND_DISPLAY") }; + } +} diff --git a/src/gui/orchestrator_index.rs b/src/gui/orchestrator_index.rs index 5bc10fa..cfadcbb 100644 --- a/src/gui/orchestrator_index.rs +++ b/src/gui/orchestrator_index.rs @@ -48,6 +48,7 @@ struct Frontmatter { } #[derive(Debug, Clone)] +#[allow(dead_code)] pub struct FeatureSummary { pub repo: String, pub feature_slug: String, @@ -83,8 +84,7 @@ pub fn parse_status_text(text: &str, path: &Path) -> Result { let last_log_line = stripped .body .lines() - .filter(|l| !l.trim().is_empty()) - .last() + .rfind(|l| !l.trim().is_empty()) .map(|l| l.trim_start_matches("- ").trim().to_string()); let (repo, feature_slug) = derive_repo_and_feature(path) diff --git a/src/gui/session_state.rs b/src/gui/session_state.rs index cf846c9..58934e7 100644 --- a/src/gui/session_state.rs +++ b/src/gui/session_state.rs @@ -2,7 +2,6 @@ use std::io::{Read, Write}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use gpui; use gpui::AppContext; use gpui_terminal::{ColorPalette, TerminalConfig, TerminalView}; use portable_pty::{native_pty_system, CommandBuilder, PtySize}; diff --git a/src/gui/status_bar.rs b/src/gui/status_bar.rs index 34ca430..09f279c 100644 --- a/src/gui/status_bar.rs +++ b/src/gui/status_bar.rs @@ -202,11 +202,10 @@ impl ClaudioApp { .cursor_pointer() .on_mouse_down(MouseButton::Left, cx.listener(move |app, _ev, _window, cx| { let id = session_id.clone(); - if is_minimized { - if let Some(s) = app.sessions.iter_mut().find(|s| s.id == id) { + if is_minimized + && let Some(s) = app.sessions.iter_mut().find(|s| s.id == id) { s.minimized = false; } - } app.focus_session_by_id(&id, cx); })) .on_mouse_down(MouseButton::Right, cx.listener(move |app, _ev: &MouseDownEvent, _window, cx| { @@ -231,11 +230,10 @@ impl ClaudioApp { .cursor_pointer() .hover(|s| s.text_color(rgb(theme::BLUE))) .on_mouse_down(MouseButton::Left, cx.listener(move |app, _ev, _window, _cx| { - if let Some(session) = app.sessions.iter().find(|s| s.id == session_id_editor) { - if let Some(ref cwd) = session.cwd { + if let Some(session) = app.sessions.iter().find(|s| s.id == session_id_editor) + && let Some(ref cwd) = session.cwd { app.open_in_editor(cwd); } - } })), ) .child( diff --git a/src/gui/terminal_grid.rs b/src/gui/terminal_grid.rs index efa4286..994f18a 100644 --- a/src/gui/terminal_grid.rs +++ b/src/gui/terminal_grid.rs @@ -284,5 +284,5 @@ pub fn grid_rows(n: usize) -> usize { return 1; } let cols = grid_cols(n); - (n + cols - 1) / cols + n.div_ceil(cols) } diff --git a/src/hotkey/evdev.rs b/src/hotkey/evdev.rs index 52148a3..551e6c6 100644 --- a/src/hotkey/evdev.rs +++ b/src/hotkey/evdev.rs @@ -109,17 +109,15 @@ impl PttListener { let fetch_err = match device.fetch_events() { Ok(events) => { for ev in events { - if ev.event_type() == EventType::KEY { - if let InputEventKind::Key(k) = ev.kind() { - if k == key { + if ev.event_type() == EventType::KEY + && let InputEventKind::Key(k) = ev.kind() + && k == key { match ev.value() { 1 => { let _ = tx.send(true); } // press 0 => { let _ = tx.send(false); } // release _ => {} // repeat, ignore } } - } - } } continue; } @@ -187,15 +185,14 @@ fn find_keyboard_device() -> Result { for entry in std::fs::read_dir("/dev/input")? { let entry = entry?; let path = entry.path(); - if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - if !name.starts_with("event") { + if let Some(name) = path.file_name().and_then(|n| n.to_str()) + && !name.starts_with("event") { continue; } - } - if let Ok(device) = Device::open(&path) { - if let Some(keys) = device.supported_keys() { - if keys.contains(Key::KEY_A) && keys.contains(Key::KEY_Z) { + if let Ok(device) = Device::open(&path) + && let Some(keys) = device.supported_keys() + && keys.contains(Key::KEY_A) && keys.contains(Key::KEY_Z) { info!( "Auto-detected keyboard: {} ({})", path.display(), @@ -203,8 +200,6 @@ fn find_keyboard_device() -> Result { ); return Ok(path.to_string_lossy().into_owned()); } - } - } } bail!( "No keyboard device found in /dev/input/. \ diff --git a/src/ml_bridge/client.rs b/src/ml_bridge/client.rs index 6f0da28..665fb62 100644 --- a/src/ml_bridge/client.rs +++ b/src/ml_bridge/client.rs @@ -221,13 +221,12 @@ impl MlServiceProcess { impl Drop for MlServiceProcess { fn drop(&mut self) { - if let Some(ref mut child) = self.child { - if let Some(pid) = child.id() { + if let Some(ref mut child) = self.child + && let Some(pid) = child.id() { unsafe { libc::kill(pid as i32, libc::SIGTERM); } } - } } } diff --git a/src/session/manager.rs b/src/session/manager.rs index 685b1b3..18abac6 100644 --- a/src/session/manager.rs +++ b/src/session/manager.rs @@ -10,6 +10,12 @@ pub struct SessionManager { focused_id: Option, } +impl Default for SessionManager { + fn default() -> Self { + Self::new() + } +} + impl SessionManager { pub fn new() -> Self { Self { diff --git a/src/session/mod.rs b/src/session/mod.rs index 561cf19..0c6d31d 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -1,3 +1,4 @@ pub mod manager; pub mod mode; +#[allow(clippy::module_inception)] pub mod session; diff --git a/src/session/session.rs b/src/session/session.rs index 9ce67a6..d93d916 100644 --- a/src/session/session.rs +++ b/src/session/session.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use super::mode::SessionMode; /// Daemon-side session metadata. PTYs are owned by the GUI process. +#[allow(dead_code)] pub struct Session { pub id: String, pub name: String,