feat: Global Hotkey (Super+C) + Clipboard-Watch + Desktop-Integration [appimage]
All checks were successful
Build AppImage / build (push) Successful in 8m53s
All checks were successful
Build AppImage / build (push) Successful in 8m53s
- Global Hotkey: Super+C öffnet Claude-Fenster und fokussiert Input von überall - Clipboard-Watch: Erkennt Code/URLs/Fehler/Pfade in Zwischenablage, zeigt Vorschlag - tauri-plugin-global-shortcut + tauri-plugin-dialog integriert - focus-chat-input Event für Hotkey → Input-Fokus - clipboard-changed Event für Watch → Chat-Vorschlag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6efbd5de5f
commit
87ba8f7bdf
8 changed files with 288 additions and 8 deletions
|
|
@ -13,6 +13,9 @@ Format angelehnt an [Keep a Changelog](https://keepachangelog.com/de/1.0.0/).
|
||||||
- **File-Drop auf Chat**: Dateien per Drag & Drop auf den Chat ziehen — Text-Dateien als Code-Block, Bilder als Base64, Spracherkennung, 500KB-Limit (`ChatPanel.svelte`)
|
- **File-Drop auf Chat**: Dateien per Drag & Drop auf den Chat ziehen — Text-Dateien als Code-Block, Bilder als Base64, Spracherkennung, 500KB-Limit (`ChatPanel.svelte`)
|
||||||
- **Persistent Memory**: Auto-Load Memory-Einträge werden bei jeder Nachricht in den Claude-Context injiziert — Cross-Session Gedächtnis für Patterns, Zugänge, Präferenzen (`memory.rs`, `claude.rs`)
|
- **Persistent Memory**: Auto-Load Memory-Einträge werden bei jeder Nachricht in den Claude-Context injiziert — Cross-Session Gedächtnis für Patterns, Zugänge, Präferenzen (`memory.rs`, `claude.rs`)
|
||||||
- **Memory CRUD-Commands**: Speichern, Löschen, Auflisten, Auto-Load-Filter für Memory-Einträge als Tauri-Commands (`memory.rs`, `lib.rs`)
|
- **Memory CRUD-Commands**: Speichern, Löschen, Auflisten, Auto-Load-Filter für Memory-Einträge als Tauri-Commands (`memory.rs`, `lib.rs`)
|
||||||
|
- **Global Hotkey (Super+C)**: Claude-Eingabe von überall öffnen — Fenster wird angezeigt, fokussiert, Input-Feld aktiviert (`lib.rs`, `ChatPanel.svelte`)
|
||||||
|
- **Clipboard-Watch**: Überwacht Zwischenablage, erkennt Code/URLs/Fehler/Pfade, zeigt Vorschlag im Chat (`clipboard.rs`, `ChatPanel.svelte`)
|
||||||
|
- **File-Browser für Projekt-Wechsel**: Nativer Verzeichnis-Dialog statt manuellem Pfad, Auto-Name aus Ordnername (`SessionList.svelte`, `tauri-plugin-dialog`)
|
||||||
- **Quick-Actions Palette (Ctrl+K)**: VS-Code-artige Kommandopalette mit Suche, Kategorien (Build, Git, Session, Navigation, Voice, Tools), Keyboard-Navigation (`QuickActions.svelte`)
|
- **Quick-Actions Palette (Ctrl+K)**: VS-Code-artige Kommandopalette mit Suche, Kategorien (Build, Git, Session, Navigation, Voice, Tools), Keyboard-Navigation (`QuickActions.svelte`)
|
||||||
- **Lokales Voice (Phase 2.2)**: whisper-cli STT + piper-tts TTS, komplett lokal ohne OpenAI-API (`voice.rs`, `VoicePanel.svelte`)
|
- **Lokales Voice (Phase 2.2)**: whisper-cli STT + piper-tts TTS, komplett lokal ohne OpenAI-API (`voice.rs`, `VoicePanel.svelte`)
|
||||||
- **Chat-Detach**: Chat in separates Fenster herauslösen, Platz für andere Panels, Zurückholen per Button (`chat_window.rs`, `+page.svelte`)
|
- **Chat-Detach**: Chat in separates Fenster herauslösen, Platz für andere Panels, Zurückholen per Button (`chat_window.rs`, `+page.svelte`)
|
||||||
|
|
|
||||||
|
|
@ -91,11 +91,11 @@ Alles aus Phase 1-16 ist implementiert und funktionsfaehig:
|
||||||
|
|
||||||
| Feature | Datei(en) | Beschreibung |
|
| Feature | Datei(en) | Beschreibung |
|
||||||
|---------|-----------|--------------|
|
|---------|-----------|--------------|
|
||||||
| D-Bus Actions | `programs.rs` | Vordefinierte Aktionen: Dolphin oeffnen, Kate starten, Notifications |
|
| D-Bus Actions | `programs.rs` | ⬜ Vordefinierte Aktionen: Dolphin oeffnen, Kate starten, Notifications |
|
||||||
| Clipboard-Watch | neu: `clipboard.rs` | Claude reagiert auf Clipboard-Inhalt (Code-Snippet → erklaeren, URL → zusammenfassen) |
|
| ✅ Clipboard-Watch | `clipboard.rs` | Claude reagiert auf Clipboard-Inhalt (Code/URL/Fehler erkennen) |
|
||||||
| ✅ File-Drop | `ChatPanel.svelte` | Dateien auf Chat droppen → Claude analysiert/bearbeitet |
|
| ✅ File-Drop | `ChatPanel.svelte` | Dateien auf Chat droppen → Claude analysiert/bearbeitet |
|
||||||
| Screenshot-Analyse | `programs.rs` | Bildschirmbereich markieren → Claude beschreibt/debuggt UI |
|
| Screenshot-Analyse | `programs.rs` | ⬜ Bildschirmbereich markieren → Claude beschreibt/debuggt UI |
|
||||||
| Global Hotkey | `lib.rs` | Super+C oeffnet Claude-Eingabe von ueberall |
|
| ✅ Global Hotkey | `lib.rs` | Super+C oeffnet Claude-Eingabe von ueberall |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
67
src-tauri/Cargo.lock
generated
67
src-tauri/Cargo.lock
generated
|
|
@ -491,6 +491,7 @@ dependencies = [
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
|
"tauri-plugin-global-shortcut",
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
|
|
@ -1410,6 +1411,16 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gethostname"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
|
|
@ -1542,6 +1553,24 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "global-hotkey"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"keyboard-types",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"once_cell",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
"x11rb",
|
||||||
|
"xkeysym",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
|
|
@ -4705,6 +4734,21 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-global-shortcut"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405"
|
||||||
|
dependencies = [
|
||||||
|
"global-hotkey",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-shell"
|
name = "tauri-plugin-shell"
|
||||||
version = "2.3.5"
|
version = "2.3.5"
|
||||||
|
|
@ -6348,6 +6392,29 @@ dependencies = [
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||||
|
dependencies = [
|
||||||
|
"gethostname",
|
||||||
|
"rustix",
|
||||||
|
"x11rb-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb-protocol"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xkeysym"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ tokio-tungstenite = "0.23"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
tauri-plugin-dialog = "2.7.0"
|
tauri-plugin-dialog = "2.7.0"
|
||||||
|
tauri-plugin-global-shortcut = "2.3.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
webkit2gtk = "2.0"
|
webkit2gtk = "2.0"
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
"shell:allow-spawn",
|
"shell:allow-spawn",
|
||||||
"shell:allow-stdin-write",
|
"shell:allow-stdin-write",
|
||||||
"shell:allow-kill",
|
"shell:allow-kill",
|
||||||
"dialog:allow-open"
|
"dialog:allow-open",
|
||||||
|
"global-shortcut:allow-register",
|
||||||
|
"global-shortcut:allow-unregister",
|
||||||
|
"global-shortcut:allow-is-registered"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
168
src-tauri/src/clipboard.rs
Normal file
168
src-tauri/src/clipboard.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
// Claude Desktop — Clipboard-Watch
|
||||||
|
// Überwacht die Zwischenablage und reagiert auf Änderungen
|
||||||
|
// (Code-Snippets erklären, URLs zusammenfassen, etc.)
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use tauri::{AppHandle, Manager, Emitter};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Clipboard-Inhalt Typ
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum ClipboardContentType {
|
||||||
|
Code, // Erkannter Code-Snippet
|
||||||
|
Url, // URL/Link
|
||||||
|
Error, // Fehlermeldung/Stacktrace
|
||||||
|
Path, // Dateipfad
|
||||||
|
Text, // Normaler Text
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clipboard-Event das ans Frontend gesendet wird
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ClipboardEvent {
|
||||||
|
pub content: String,
|
||||||
|
pub content_type: ClipboardContentType,
|
||||||
|
pub suggestion: String, // Vorschlag was Claude damit tun könnte
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clipboard-State für Watch-Kontrolle
|
||||||
|
pub struct ClipboardWatcher {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub last_content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClipboardWatcher {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: false,
|
||||||
|
last_content: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ClipboardState = Arc<Mutex<ClipboardWatcher>>;
|
||||||
|
|
||||||
|
/// Erkennt den Typ des Clipboard-Inhalts
|
||||||
|
fn detect_content_type(text: &str) -> (ClipboardContentType, String) {
|
||||||
|
let trimmed = text.trim();
|
||||||
|
|
||||||
|
// URL erkennen
|
||||||
|
if trimmed.starts_with("http://") || trimmed.starts_with("https://") {
|
||||||
|
return (ClipboardContentType::Url, "URL zusammenfassen?".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dateipfad erkennen
|
||||||
|
if trimmed.starts_with('/') && !trimmed.contains('\n') && trimmed.len() < 500 {
|
||||||
|
return (ClipboardContentType::Path, "Datei analysieren?".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fehlermeldung/Stacktrace erkennen
|
||||||
|
let error_indicators = ["error", "Error", "ERROR", "exception", "Exception",
|
||||||
|
"traceback", "Traceback", "panic", "PANIC", "at line", "stack trace"];
|
||||||
|
if error_indicators.iter().any(|e| trimmed.contains(e)) {
|
||||||
|
return (ClipboardContentType::Error, "Fehler analysieren?".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code erkennen (einfache Heuristik)
|
||||||
|
let code_indicators = ["fn ", "function ", "def ", "class ", "import ", "const ",
|
||||||
|
"let ", "var ", "pub ", "async ", "=>", "->", "<?php", "#!/",
|
||||||
|
"{", "}", "();", "= {", "= ["];
|
||||||
|
let code_score: usize = code_indicators.iter()
|
||||||
|
.filter(|ind| trimmed.contains(*ind))
|
||||||
|
.count();
|
||||||
|
if code_score >= 2 || (trimmed.lines().count() > 3 && trimmed.contains('{')) {
|
||||||
|
return (ClipboardContentType::Code, "Code erklären?".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
(ClipboardContentType::Text, String::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Startet den Clipboard-Watcher (Polling alle 2s)
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn clipboard_watch_start(app: AppHandle) -> Result<(), String> {
|
||||||
|
let state = app.state::<ClipboardState>();
|
||||||
|
{
|
||||||
|
let mut watcher = state.lock().unwrap();
|
||||||
|
if watcher.enabled {
|
||||||
|
return Ok(()); // Schon aktiv
|
||||||
|
}
|
||||||
|
watcher.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("📋 Clipboard-Watch gestartet");
|
||||||
|
|
||||||
|
let app_handle = app.clone();
|
||||||
|
let watch_state = app.state::<ClipboardState>().inner().clone();
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
let enabled = {
|
||||||
|
let watcher = watch_state.lock().unwrap();
|
||||||
|
watcher.enabled
|
||||||
|
};
|
||||||
|
if !enabled { break; }
|
||||||
|
|
||||||
|
// Clipboard via xclip lesen (Linux)
|
||||||
|
let output = tokio::process::Command::new("xclip")
|
||||||
|
.args(["-selection", "clipboard", "-o"])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Ok(out) = output {
|
||||||
|
if out.status.success() {
|
||||||
|
let content = String::from_utf8_lossy(&out.stdout).to_string();
|
||||||
|
let trimmed = content.trim().to_string();
|
||||||
|
|
||||||
|
if trimmed.is_empty() || trimmed.len() > 10000 { continue; }
|
||||||
|
|
||||||
|
let changed = {
|
||||||
|
let mut watcher = watch_state.lock().unwrap();
|
||||||
|
if watcher.last_content == trimmed {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
watcher.last_content = trimmed.clone();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
let (content_type, suggestion) = detect_content_type(&trimmed);
|
||||||
|
|
||||||
|
// Nur bei interessanten Inhalten ein Event senden
|
||||||
|
if !suggestion.is_empty() {
|
||||||
|
let event = ClipboardEvent {
|
||||||
|
content: trimmed,
|
||||||
|
content_type,
|
||||||
|
suggestion,
|
||||||
|
};
|
||||||
|
let _ = app_handle.emit("clipboard-changed", &event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("📋 Clipboard-Watch beendet");
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stoppt den Clipboard-Watcher
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn clipboard_watch_stop(app: AppHandle) -> Result<(), String> {
|
||||||
|
let state = app.state::<ClipboardState>();
|
||||||
|
let mut watcher = state.lock().unwrap();
|
||||||
|
watcher.enabled = false;
|
||||||
|
println!("📋 Clipboard-Watch gestoppt");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gibt den aktuellen Clipboard-Status zurück
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn clipboard_watch_status(app: AppHandle) -> Result<bool, String> {
|
||||||
|
let state = app.state::<ClipboardState>();
|
||||||
|
let watcher = state.lock().unwrap();
|
||||||
|
Ok(watcher.enabled)
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tauri::{
|
use tauri::{
|
||||||
Manager,
|
Emitter, Manager,
|
||||||
menu::{Menu, MenuItem},
|
menu::{Menu, MenuItem},
|
||||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||||
};
|
};
|
||||||
|
|
@ -13,6 +13,7 @@ use webkit2gtk::WebViewExt;
|
||||||
|
|
||||||
mod audit;
|
mod audit;
|
||||||
mod claude;
|
mod claude;
|
||||||
|
mod clipboard;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod context;
|
mod context;
|
||||||
mod db;
|
mod db;
|
||||||
|
|
@ -34,9 +35,11 @@ pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.manage(Arc::new(Mutex::new(claude::ClaudeState::default())))
|
.manage(Arc::new(Mutex::new(claude::ClaudeState::default())))
|
||||||
.manage(guard::GuardState::new(Mutex::new(guard::GuardRails::new())))
|
.manage(guard::GuardState::new(Mutex::new(guard::GuardRails::new())))
|
||||||
.manage::<hooks::HookState>(Arc::new(Mutex::new(hooks::HookManager::default())))
|
.manage::<hooks::HookState>(Arc::new(Mutex::new(hooks::HookManager::default())))
|
||||||
|
.manage::<clipboard::ClipboardState>(Arc::new(Mutex::new(clipboard::ClipboardWatcher::default())))
|
||||||
.manage::<ide::IdeState>(Arc::new(Mutex::new(ide::IdeConnector::default())))
|
.manage::<ide::IdeState>(Arc::new(Mutex::new(ide::IdeConnector::default())))
|
||||||
// Phase 2.0: MySQL Pool als Managed State — wird einmal erstellt, von allen Knowledge-Commands geteilt
|
// Phase 2.0: MySQL Pool als Managed State — wird einmal erstellt, von allen Knowledge-Commands geteilt
|
||||||
.manage::<knowledge::MysqlPoolState>(knowledge::create_managed_pool())
|
.manage::<knowledge::MysqlPoolState>(knowledge::create_managed_pool())
|
||||||
|
|
@ -171,6 +174,10 @@ pub fn run() {
|
||||||
update::apply_update,
|
update::apply_update,
|
||||||
update::apply_bundle_update,
|
update::apply_bundle_update,
|
||||||
update::get_current_version,
|
update::get_current_version,
|
||||||
|
// Clipboard-Watch
|
||||||
|
clipboard::clipboard_watch_start,
|
||||||
|
clipboard::clipboard_watch_stop,
|
||||||
|
clipboard::clipboard_watch_status,
|
||||||
// Slash-Command Registry
|
// Slash-Command Registry
|
||||||
commands::get_slash_commands,
|
commands::get_slash_commands,
|
||||||
])
|
])
|
||||||
|
|
@ -263,6 +270,23 @@ pub fn run() {
|
||||||
// Lock-Datei erstellen (Instanz-Schutz + Update-Safety)
|
// Lock-Datei erstellen (Instanz-Schutz + Update-Safety)
|
||||||
update::create_lock_file();
|
update::create_lock_file();
|
||||||
|
|
||||||
|
// Global Hotkey: Super+C öffnet Claude-Eingabe von überall
|
||||||
|
{
|
||||||
|
use tauri_plugin_global_shortcut::GlobalShortcutExt;
|
||||||
|
let hotkey_handle = app.handle().clone();
|
||||||
|
let _ = app.global_shortcut().on_shortcut("Super+C", move |_app, _shortcut, event| {
|
||||||
|
if event.state == tauri_plugin_global_shortcut::ShortcutState::Pressed {
|
||||||
|
if let Some(window) = hotkey_handle.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.unminimize();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
let _ = hotkey_handle.emit("focus-chat-input", ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
println!("⌨️ Global Hotkey registriert: Super+C");
|
||||||
|
}
|
||||||
|
|
||||||
// WebKitGTK: Mikrofon-/Kamera-Permissions automatisch erlauben
|
// WebKitGTK: Mikrofon-/Kamera-Permissions automatisch erlauben
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { emit } from '@tauri-apps/api/event';
|
import { emit, listen } from '@tauri-apps/api/event';
|
||||||
import { messages, currentInput, isProcessing, addMessage, currentSessionId, messageToDb, queuedMessage, messageQueue, type Message, type QuickAction } from '$lib/stores/app';
|
import { messages, currentInput, isProcessing, addMessage, currentSessionId, messageToDb, queuedMessage, messageQueue, type Message, type QuickAction } from '$lib/stores/app';
|
||||||
import { currentTool, processingPhase } from '$lib/stores/events';
|
import { currentTool, processingPhase } from '$lib/stores/events';
|
||||||
import { marked, type Tokens } from 'marked';
|
import { marked, type Tokens } from 'marked';
|
||||||
|
|
@ -702,9 +702,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
window.addEventListener('keydown', handleGlobalKeydown);
|
window.addEventListener('keydown', handleGlobalKeydown);
|
||||||
|
|
||||||
|
// Global Hotkey: Super+C fokussiert das Input-Feld
|
||||||
|
const unlistenFocus = await listen('focus-chat-input', () => {
|
||||||
|
inputTextarea?.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clipboard-Watch Events: Vorschlag im Chat anzeigen
|
||||||
|
const unlistenClipboard = await listen<{ content: string; content_type: string; suggestion: string }>('clipboard-changed', (event) => {
|
||||||
|
const { content, content_type, suggestion } = event.payload;
|
||||||
|
const preview = content.length > 200 ? content.substring(0, 200) + '...' : content;
|
||||||
|
addMessage('system', `📋 Clipboard (${content_type}): ${suggestion}\n\`\`\`\n${preview}\n\`\`\``);
|
||||||
|
});
|
||||||
|
|
||||||
// Queue-Auto-Dispatch: sobald isProcessing von true auf false wechselt
|
// Queue-Auto-Dispatch: sobald isProcessing von true auf false wechselt
|
||||||
// wird die naechste Nachricht aus der Queue abgeschickt (FIFO).
|
// wird die naechste Nachricht aus der Queue abgeschickt (FIFO).
|
||||||
let lastProcessing = false;
|
let lastProcessing = false;
|
||||||
|
|
@ -724,6 +736,8 @@
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', handleGlobalKeydown);
|
window.removeEventListener('keydown', handleGlobalKeydown);
|
||||||
unsubProcessing();
|
unsubProcessing();
|
||||||
|
unlistenFocus();
|
||||||
|
unlistenClipboard();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue