diff --git a/package-lock.json b/package-lock.json index 2edcd6c..5b91924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,14 @@ "@fontsource/jetbrains-mono": "^5.2.8", "@tauri-apps/api": "^2.11.1", "@tauri-apps/plugin-autostart": "^2.5.1", + "@tauri-apps/plugin-dialog": "^2.7.1", "@tauri-apps/plugin-opener": "^2.5.4", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-ligatures": "^0.10.0", "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^6.0.0", - "vue": "^3.5.39" + "vue": "^3.5.39", + "vue-i18n": "^11.4.6" }, "devDependencies": { "@tauri-apps/cli": "^2.11.4", @@ -117,6 +119,67 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@intlify/core-base": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.6.tgz", + "integrity": "sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw==", + "license": "MIT", + "dependencies": { + "@intlify/devtools-types": "11.4.6", + "@intlify/message-compiler": "11.4.6", + "@intlify/shared": "11.4.6" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/devtools-types": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.6.tgz", + "integrity": "sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.6", + "@intlify/shared": "11.4.6" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.6.tgz", + "integrity": "sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "11.4.6", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.6.tgz", + "integrity": "sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==", + "license": "MIT", + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -685,6 +748,15 @@ "@tauri-apps/api": "^2.8.0" } }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.1.tgz", + "integrity": "sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + } + }, "node_modules/@tauri-apps/plugin-opener": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.4.tgz", @@ -811,6 +883,12 @@ "@vue/shared": "3.5.39" } }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, "node_modules/@vue/language-core": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.6.tgz", @@ -1635,6 +1713,27 @@ } } }, + "node_modules/vue-i18n": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.6.tgz", + "integrity": "sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.6", + "@intlify/devtools-types": "11.4.6", + "@intlify/shared": "11.4.6", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vue-tsc": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.6.tgz", diff --git a/package.json b/package.json index b4cf7b7..3e6a321 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "@fontsource/jetbrains-mono": "^5.2.8", "@tauri-apps/api": "^2.11.1", "@tauri-apps/plugin-autostart": "^2.5.1", + "@tauri-apps/plugin-dialog": "^2.7.1", "@tauri-apps/plugin-opener": "^2.5.4", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-ligatures": "^0.10.0", "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^6.0.0", - "vue": "^3.5.39" + "vue": "^3.5.39", + "vue-i18n": "^11.4.6" }, "devDependencies": { "@tauri-apps/cli": "^2.11.4", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 13b58c4..022eabf 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -82,6 +82,9 @@ dependencies = [ "base64 0.22.1", "getrandom 0.2.17", "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", "portable-pty", "serde", "serde_json", @@ -89,6 +92,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-autostart", + "tauri-plugin-dialog", "tauri-plugin-log", "ureq", ] @@ -2067,9 +2071,17 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.0", "block2", + "libc", "objc2", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", "objc2-foundation", + "objc2-quartz-core", ] [[package]] @@ -2089,6 +2101,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ + "bitflags 2.13.0", "objc2", "objc2-foundation", ] @@ -2149,6 +2162,19 @@ dependencies = [ "objc2-core-graphics", ] +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -2172,6 +2198,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.0", "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -2742,6 +2769,30 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + [[package]] name = "ring" version = "0.17.14" @@ -3563,6 +3614,48 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", +] + [[package]] name = "tauri-plugin-log" version = "2.8.0" @@ -4647,6 +4740,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -4680,13 +4782,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -4717,6 +4836,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4729,6 +4854,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4741,12 +4872,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4759,6 +4902,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4771,6 +4920,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4783,6 +4938,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4795,6 +4956,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9369c63..ee55ebf 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -24,8 +24,14 @@ log = "0.4" tauri = { version = "2.11.3", features = ["tray-icon"] } tauri-plugin-log = "2" tauri-plugin-autostart = "2" +tauri-plugin-dialog = "2" ureq = { version = "2", features = ["json"] } base64 = "0.22" sha2 = "0.10" getrandom = "0.2" portable-pty = "0.9" + +[target.'cfg(target_os = "macos")'.dependencies] +objc2 = "0.6" +objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSImage", "NSResponder"] } +objc2-foundation = { version = "0.3", features = ["NSString"] } diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index a0cc551..2868d06 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -9,6 +9,7 @@ "permissions": [ "core:default", "core:window:allow-start-dragging", - "autostart:default" + "autostart:default", + "dialog:default" ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index bbe54f4..71516c8 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -58,6 +58,7 @@ struct Project { path: String, pool: Option, running: bool, + terminal: TerminalConfig, } #[derive(Serialize, Deserialize)] @@ -67,9 +68,28 @@ struct Pool { credential_type: String, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Default)] struct ProjectConfig { - pool: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pool: Option, + #[serde(default, skip_serializing_if = "TerminalConfig::is_empty")] + terminal: TerminalConfig, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct TerminalConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub theme: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub icon: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub title: Option, +} + +impl TerminalConfig { + fn is_empty(&self) -> bool { + self.theme.is_none() && self.icon.is_none() && self.title.is_none() + } } fn bundle_id(project: &str) -> String { @@ -130,20 +150,13 @@ fn list_projects_in(paths: &Paths) -> Result, String> { continue; } let name = entry.file_name().to_string_lossy().into_owned(); - let cfg_path = path.join(PROJECT_FILE); - let pool = if cfg_path.is_file() { - let raw = fs::read_to_string(&cfg_path).map_err(|e| e.to_string())?; - let cfg: ProjectConfig = serde_json::from_str(&raw) - .map_err(|e| format!("{}: {e}", cfg_path.display()))?; - Some(cfg.pool) - } else { - None - }; + let cfg = read_project_config_in(paths, &name)?; projects.push(Project { running: is_running(&name), path: path.to_string_lossy().into_owned(), + pool: cfg.pool, + terminal: cfg.terminal, name, - pool, }); } projects.sort_by(|a, b| a.name.cmp(&b.name)); @@ -168,18 +181,59 @@ fn delete_project_in(paths: &Paths, name: &str) -> Result<(), String> { fs::remove_dir_all(&dir).map_err(|e| e.to_string()) } +fn read_project_config_in(paths: &Paths, project: &str) -> Result { + let cfg_path = paths.project_config(project); + if !cfg_path.is_file() { + return Ok(ProjectConfig::default()); + } + let raw = + fs::read_to_string(&cfg_path).map_err(|e| format!("{}: {e}", cfg_path.display()))?; + serde_json::from_str(&raw).map_err(|e| format!("{}: {e}", cfg_path.display())) +} + +fn write_project_config_in( + paths: &Paths, + project: &str, + cfg: &ProjectConfig, +) -> Result<(), String> { + let raw = serde_json::to_string_pretty(cfg).map_err(|e| e.to_string())?; + fs::write(paths.project_config(project), raw + "\n").map_err(|e| e.to_string()) +} + fn assign_pool_in(paths: &Paths, project: &str, pool: &str) -> Result<(), String> { if !paths.pool_dir(pool).join(POOL_FILE).is_file() { return Err(format!("Pool existiert nicht: {pool}")); } - let cfg = ProjectConfig { pool: pool.to_string() }; - let raw = serde_json::to_string_pretty(&cfg).map_err(|e| e.to_string())?; - fs::write(paths.project_config(project), raw + "\n").map_err(|e| e.to_string()) + let mut cfg = read_project_config_in(paths, project)?; + cfg.pool = Some(pool.to_string()); + write_project_config_in(paths, project, &cfg) } +/// Nimmt den Pool raus; die Config-Datei bleibt nur, wenn sie noch +/// Terminal-Einstellungen trägt. fn unassign_pool_in(paths: &Paths, project: &str) -> Result<(), String> { - let cfg_path = paths.project_config(project); - fs::remove_file(&cfg_path).map_err(|e| format!("{}: {e}", cfg_path.display())) + let mut cfg = read_project_config_in(paths, project)?; + cfg.pool = None; + if cfg.terminal.is_empty() { + let cfg_path = paths.project_config(project); + return fs::remove_file(&cfg_path).map_err(|e| format!("{}: {e}", cfg_path.display())); + } + write_project_config_in(paths, project, &cfg) +} + +fn set_terminal_config_in( + paths: &Paths, + project: &str, + terminal: TerminalConfig, +) -> Result<(), String> { + let mut cfg = read_project_config_in(paths, project)?; + cfg.terminal = terminal; + write_project_config_in(paths, project, &cfg) +} + +/// Terminal-Einstellungen eines Projekts, für den Terminal-Prozess. +pub(crate) fn terminal_config(project: &str) -> Result { + Ok(read_project_config_in(&Paths::real(), project)?.terminal) } /// Namen aller Projekte, die `pool` zugeordnet haben. @@ -194,7 +248,7 @@ fn projects_using_pool(paths: &Paths, pool: &str) -> Result, String> } let raw = fs::read_to_string(&cfg_path).map_err(|e| e.to_string())?; let cfg: ProjectConfig = serde_json::from_str(&raw).map_err(|e| e.to_string())?; - if cfg.pool == pool { + if cfg.pool.as_deref() == Some(pool) { users.push(entry.file_name().to_string_lossy().into_owned()); } } @@ -372,6 +426,16 @@ fn unassign_pool(project: String) -> Result<(), String> { unassign_pool_in(&Paths::real(), &project) } +#[tauri::command] +fn set_terminal_config( + project: String, + theme: Option, + icon: Option, + title: Option, +) -> Result<(), String> { + set_terminal_config_in(&Paths::real(), &project, TerminalConfig { theme, icon, title }) +} + #[tauri::command] fn set_apikey(pool: String, key: String) -> Result<(), String> { set_apikey_in(&Paths::real(), &pool, &key) @@ -675,18 +739,34 @@ fn pct_decode(s: &str) -> String { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + // generate_context! darf pro Crate nur einmal expandieren (_EMBED_INFO_PLIST). + let context = tauri::generate_context!(); let mut args = std::env::args().skip(1); - let builder = match args.next().as_deref() { + match args.next().as_deref() { // `app --terminal `: eigener Prozess pro Terminal-Fenster, // damit jedes Terminal ein eigenes Dock-Icon bekommt. Some("--terminal") => { - terminal_builder(args.next().expect("--terminal braucht einen Projektnamen")) + let project = args.next().expect("--terminal braucht einen Projektnamen"); + let icon = terminal_config(&project) + .expect("Projekt-Config nicht lesbar") + .icon; + terminal_builder(project) + .build(context) + .expect("error while building tauri application") + // Das Dock-Icon erst nach dem App-Start setzen: in setup() gesetzt + // überschreibt macOS es beim Anlegen des Dock-Tiles wieder. + .run(move |_app, event| { + if let tauri::RunEvent::Ready = event { + if let Some(icon) = icon.as_deref() { + terminal::set_dock_icon(icon); + } + } + }); } - _ => main_builder(), - }; - builder - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + _ => main_builder() + .run(context) + .expect("error while running tauri application"), + } } /// Haupt-App: reine Tray-App ohne Dock-Eintrag. @@ -698,6 +778,7 @@ fn main_builder() -> tauri::Builder { tauri_plugin_autostart::MacosLauncher::LaunchAgent, None, )) + .plugin(tauri_plugin_dialog::init()) .setup(|app| { if cfg!(debug_assertions) { app.handle().plugin( @@ -755,6 +836,7 @@ fn main_builder() -> tauri::Builder { delete_pool, assign_pool, unassign_pool, + set_terminal_config, start_project, stop_project, restart_project, @@ -771,7 +853,8 @@ fn terminal_builder(project: String) -> tauri::Builder { tauri::Builder::default() .manage(terminal::Terminals::default()) .setup(move |app| { - terminal::build_window(app.handle(), &project)?; + let cfg = terminal_config(&project)?; + terminal::build_window(app.handle(), &project, &cfg)?; Ok(()) }) // Fenster zu → PTY-Session abräumen; danach endet der Prozess. @@ -982,7 +1065,7 @@ mod tests { let cfg: ProjectConfig = serde_json::from_str(&fs::read_to_string(p.project_config("proj")).unwrap()) .unwrap(); - assert_eq!(cfg.pool, "kunde"); + assert_eq!(cfg.pool.as_deref(), Some("kunde")); let pools = list_pools_in(&p).unwrap(); assert_eq!(pools[0].projects, vec!["proj"]); diff --git a/src-tauri/src/terminal.rs b/src-tauri/src/terminal.rs index 7415228..e74c9c1 100644 --- a/src-tauri/src/terminal.rs +++ b/src-tauri/src/terminal.rs @@ -34,19 +34,52 @@ pub fn open_terminal(project: String) -> Result<(), String> { Ok(()) } +/// Fenster-Hintergrund je Theme — muss zu den Theme-Definitionen in +/// terminal.ts passen, sonst blitzt beim Öffnen die falsche Farbe auf. +fn theme_background(theme: &str) -> (u8, u8, u8) { + match theme { + "dracula" => (0x28, 0x2a, 0x36), + "solarized-dark" => (0x00, 0x2b, 0x36), + "gruvbox" => (0x28, 0x28, 0x28), + "one-dark" => (0x28, 0x2c, 0x34), + _ => (0x1e, 0x1e, 0x2e), // Catppuccin Mocha + } +} + +/// Setzt das Dock-Icon dieses Terminal-Prozesses aus einer PNG/ICNS-Datei. +#[cfg(target_os = "macos")] +pub fn set_dock_icon(path: &str) { + use objc2::{AnyThread, MainThreadMarker}; + use objc2_app_kit::{NSApplication, NSImage}; + use objc2_foundation::NSString; + + let mtm = MainThreadMarker::new().expect("set_dock_icon läuft nicht auf dem Main-Thread"); + let img = NSImage::initWithContentsOfFile(NSImage::alloc(), &NSString::from_str(path)) + .unwrap_or_else(|| panic!("Dock-Icon nicht ladbar: {path}")); + // unsafe laut objc2-Signatur; das NSImage stammt aus einer Datei und ist gültig. + unsafe { + NSApplication::sharedApplication(mtm).setApplicationIconImage(Some(&img)); + } +} + /// Baut das Terminal-Fenster des Terminal-Prozesses. Die PTY entsteht erst, /// wenn das Fenster geladen ist und `term_start` ruft — so gehen keine /// Ausgaben verloren, bevor der Event-Listener steht. -pub fn build_window(app: &AppHandle, project: &str) -> tauri::Result<()> { +pub fn build_window( + app: &AppHandle, + project: &str, + cfg: &crate::TerminalConfig, +) -> tauri::Result<()> { + let (r, g, b) = theme_background(cfg.theme.as_deref().unwrap_or_default()); + let title = cfg.title.as_deref().unwrap_or(project); let builder = WebviewWindowBuilder::new( app, format!("term-{project}"), WebviewUrl::App(format!("terminal.html?project={project}").into()), ) - .title(format!("{project} — Session")) + .title(format!("{title} — Session")) .inner_size(980.0, 640.0) - // Theme-Hintergrund (Catppuccin Mocha), sonst blitzt beim Öffnen Weiß auf. - .background_color(tauri::window::Color(0x1e, 0x1e, 0x2e, 0xff)); + .background_color(tauri::window::Color(r, g, b, 0xff)); // Titelbar liegt über dem Inhalt; der Header in terminal.html ist Drag-Region. #[cfg(target_os = "macos")] diff --git a/src/App.vue b/src/App.vue index a03772a..c9ddc06 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,11 +1,14 @@