Linux-Port: GNOME-Popup + Tray, Terminal-Schriftgröße global
- Popup als rahmenloses Fenster mit D-Bus-Relay (popup.html/ts, Popup.vue); GNOME-Extension schiebt es unter den Panel-Button - Terminal: globale Schriftgröße via settings.json (terminalFontSize), Zoom mit Ctrl/Cmd +/-/0 im Terminal - trayLinux-Icon, Capabilities, App.vue/terminal.html angepasst
This commit is contained in:
Generated
-45
@@ -309,9 +309,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -329,9 +326,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -349,9 +343,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -369,9 +360,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -389,9 +377,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -409,9 +394,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -597,9 +579,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -617,9 +596,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -637,9 +613,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -657,9 +630,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -677,9 +647,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1262,9 +1229,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1286,9 +1250,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1310,9 +1271,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1334,9 +1292,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Projekte</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/popup.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+439
@@ -33,6 +33,7 @@ name = "ai-control"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"glib",
|
||||
"keyring",
|
||||
"log",
|
||||
"objc2",
|
||||
@@ -48,6 +49,7 @@ dependencies = [
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-log",
|
||||
"uuid",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -103,6 +105,137 @@ version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-executor"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"concurrent-queue",
|
||||
"fastrand",
|
||||
"futures-lite",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atk"
|
||||
version = "0.18.2"
|
||||
@@ -221,6 +354,19 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.7.0"
|
||||
@@ -486,6 +632,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
@@ -902,6 +1057,33 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.4"
|
||||
@@ -929,6 +1111,37 @@ dependencies = [
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
@@ -1076,6 +1289,19 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.32"
|
||||
@@ -1433,6 +1659,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@@ -1921,6 +2153,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
@@ -2347,6 +2585,16 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -2372,6 +2620,12 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -2460,6 +2714,17 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.33"
|
||||
@@ -2505,6 +2770,20 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-pty"
|
||||
version = "0.9.0"
|
||||
@@ -2924,6 +3203,19 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
@@ -3269,6 +3561,16 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
@@ -3830,6 +4132,19 @@ dependencies = [
|
||||
"toml 1.1.2+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.5.0"
|
||||
@@ -4136,9 +4451,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
@@ -4188,6 +4515,17 @@ version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
@@ -5115,6 +5453,67 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-task",
|
||||
"async-trait",
|
||||
"blocking",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"libc",
|
||||
"ordered-stream",
|
||||
"rustix",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"uuid",
|
||||
"windows-sys 0.61.2",
|
||||
"winnow 1.0.3",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "4.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winnow 1.0.3",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.52"
|
||||
@@ -5214,3 +5613,43 @@ name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"winnow 1.0.3",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "5.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.118",
|
||||
"winnow 1.0.3",
|
||||
]
|
||||
|
||||
@@ -31,6 +31,10 @@ portable-pty = "0.9"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
keyring = { version = "3", features = ["apple-native", "sync-secret-service", "windows-native"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
zbus = "5"
|
||||
glib = "0.18"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6"
|
||||
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSImage", "NSResponder", "NSRunningApplication"] }
|
||||
|
||||
@@ -4,11 +4,18 @@
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main",
|
||||
"popup",
|
||||
"term-*"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-hide",
|
||||
"core:window:allow-start-resize-dragging",
|
||||
"autostart:default",
|
||||
"dialog:default"
|
||||
]
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
+310
-53
@@ -340,12 +340,12 @@ fn spawn_session_watcher(app: tauri::AppHandle) {
|
||||
let handle = app.clone();
|
||||
app
|
||||
.run_on_main_thread(move || {
|
||||
let menu = tray_menu(&handle).expect("Tray-Menü nicht aufbaubar");
|
||||
handle
|
||||
.tray_by_id("main")
|
||||
.expect("Tray-Icon fehlt")
|
||||
.set_menu(Some(menu))
|
||||
.expect("Tray-Menü nicht setzbar");
|
||||
// Nur wenn ein Tray existiert (macOS). Unter Linux pollt das Popup selbst.
|
||||
if let Some(tray) = handle.tray_by_id("main") {
|
||||
if let Ok(menu) = tray_menu(&handle) {
|
||||
let _ = tray.set_menu(Some(menu));
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("Main-Thread nicht erreichbar");
|
||||
state = current;
|
||||
@@ -556,6 +556,8 @@ fn create_project_full_in(
|
||||
if todo {
|
||||
set_todo_in(paths, name, true)?;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
write_terminal_desktop(paths, name, &cfg.terminal);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -618,7 +620,15 @@ fn add_project_in(paths: &Paths, path: &str) -> Result<(), String> {
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
check_name(&name)?;
|
||||
register_project(paths, &name, &dir)
|
||||
register_project(paths, &name, &dir)?;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let cfg = read_project_config_in(paths, &name)
|
||||
.map(|c| c.terminal)
|
||||
.unwrap_or_default();
|
||||
write_terminal_desktop(paths, &name, &cfg);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -739,7 +749,10 @@ fn delete_project_in(paths: &Paths, name: &str, delete_work_dirs: bool) -> Resul
|
||||
}
|
||||
}
|
||||
fs::remove_dir_all(&dir).map_err(|e| e.to_string())?;
|
||||
unregister_project(paths, name)
|
||||
unregister_project(paths, name)?;
|
||||
#[cfg(target_os = "linux")]
|
||||
remove_terminal_desktop(paths, name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_project_config_in(paths: &Paths, project: &str) -> Result<ProjectConfig, String> {
|
||||
@@ -809,7 +822,10 @@ fn set_terminal_config_in(
|
||||
}
|
||||
}
|
||||
cfg.terminal = terminal;
|
||||
write_project_config_in(paths, project, &cfg)
|
||||
write_project_config_in(paths, project, &cfg)?;
|
||||
#[cfg(target_os = "linux")]
|
||||
write_terminal_desktop(paths, project, &cfg.terminal);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Icon-Pfad einer Projekt-Config auflösen: relative Namen liegen im
|
||||
@@ -822,6 +838,78 @@ fn resolve_icon_path(paths: &Paths, icon: &str) -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
/// ~/.local/share/applications — Ziel der pro-Terminal-.desktop-Dateien.
|
||||
/// paths-abhängig (nicht $HOME direkt), damit Tests in ihr tmp-home schreiben.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn applications_dir(paths: &Paths) -> PathBuf {
|
||||
paths.home.join(".local/share/applications")
|
||||
}
|
||||
|
||||
/// Schreibt/aktualisiert die .desktop eines Projekts: dash-to-dock ordnet dem
|
||||
/// Fenster (app_id `aicontrol-<projekt>`, via set_prgname) darüber das Projekt-
|
||||
/// Icon zu. NoDisplay=true hält den App-Starter sauber.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn write_terminal_desktop(paths: &Paths, project: &str, cfg: &TerminalConfig) {
|
||||
let dir = applications_dir(paths);
|
||||
if fs::create_dir_all(&dir).is_err() {
|
||||
return;
|
||||
}
|
||||
let exec = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.to_str().map(str::to_string))
|
||||
.unwrap_or_else(|| "ai-control".into());
|
||||
let icon_line = cfg
|
||||
.icon
|
||||
.as_deref()
|
||||
.map(|i| resolve_icon_path(paths, i))
|
||||
.filter(|p| p.exists())
|
||||
.and_then(|p| p.to_str().map(str::to_string))
|
||||
.map(|p| format!("Icon={p}\n"))
|
||||
.unwrap_or_default();
|
||||
let content = format!(
|
||||
"[Desktop Entry]\nType=Application\nName={project}\nExec={exec} --terminal {project}\n\
|
||||
{icon_line}StartupWMClass=aicontrol-{project}\nNoDisplay=true\n"
|
||||
);
|
||||
let _ = fs::write(dir.join(format!("aicontrol-{project}.desktop")), content);
|
||||
}
|
||||
|
||||
/// Entfernt die .desktop eines Projekts (beim Löschen).
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn remove_terminal_desktop(paths: &Paths, project: &str) {
|
||||
let _ = fs::remove_file(applications_dir(paths).join(format!("aicontrol-{project}.desktop")));
|
||||
}
|
||||
|
||||
/// Beim App-Start: für jedes registrierte Projekt die .desktop neu schreiben und
|
||||
/// verwaiste (kein registriertes Projekt mehr) entfernen. Dadurch sind sie immer
|
||||
/// vorhanden und aktuell, bevor überhaupt ein Terminal startet.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn sync_all_desktops(paths: &Paths) {
|
||||
let Ok(reg) = load_registry(paths) else {
|
||||
return;
|
||||
};
|
||||
for project in reg.keys() {
|
||||
let cfg = read_project_config_in(paths, project)
|
||||
.map(|c| c.terminal)
|
||||
.unwrap_or_default();
|
||||
write_terminal_desktop(paths, project, &cfg);
|
||||
}
|
||||
let dir = applications_dir(paths);
|
||||
if let Ok(entries) = fs::read_dir(&dir) {
|
||||
for e in entries.flatten() {
|
||||
let name = e.file_name();
|
||||
let Some(name) = name.to_str() else { continue };
|
||||
if let Some(project) = name
|
||||
.strip_prefix("aicontrol-")
|
||||
.and_then(|n| n.strip_suffix(".desktop"))
|
||||
{
|
||||
if !reg.contains_key(project) {
|
||||
let _ = fs::remove_file(e.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Icon eines Projekts als PNG-data-URL für die Übersicht; ICNS wird per
|
||||
/// sips nach PNG konvertiert, weil der Browser ICNS nicht rendert.
|
||||
#[tauri::command]
|
||||
@@ -837,20 +925,7 @@ fn project_icon(project: String) -> Result<Option<String>, String> {
|
||||
.and_then(|e| e.to_str())
|
||||
.is_some_and(|e| e.eq_ignore_ascii_case("icns"));
|
||||
let png = if is_icns {
|
||||
let tmp = std::env::temp_dir().join(format!("ai-control-icon-{project}.png"));
|
||||
let out = std::process::Command::new("sips")
|
||||
.args(["-s", "format", "png"])
|
||||
.arg(&path)
|
||||
.arg("--out")
|
||||
.arg(&tmp)
|
||||
.output()
|
||||
.map_err(|e| format!("sips: {e}"))?;
|
||||
if !out.status.success() {
|
||||
return Err(format!("sips: {}", String::from_utf8_lossy(&out.stderr)));
|
||||
}
|
||||
let bytes = fs::read(&tmp).map_err(|e| format!("{}: {e}", tmp.display()))?;
|
||||
let _ = fs::remove_file(&tmp);
|
||||
bytes
|
||||
icns_to_png_bytes(&path, &project)?
|
||||
} else {
|
||||
fs::read(&path).map_err(|e| format!("{}: {e}", path.display()))?
|
||||
};
|
||||
@@ -904,17 +979,49 @@ fn menu_icon(
|
||||
Ok(tauri::image::Image::new_owned(canvas, W as u32, H as u32))
|
||||
}
|
||||
|
||||
/// Projekt-Icon als 36×36-RGBA (sips konvertiert, auch ICNS).
|
||||
/// Projekt-Icon als 36×36-RGBA. PNG wird von tauri geladen und skaliert;
|
||||
/// ICNS läuft über sips (nur macOS).
|
||||
fn project_icon_rgba_36(
|
||||
paths: &Paths,
|
||||
project: &str,
|
||||
icon: &str,
|
||||
) -> Result<tauri::image::Image<'static>, String> {
|
||||
let src = resolve_icon_path(paths, icon);
|
||||
let is_icns = src
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.is_some_and(|e| e.eq_ignore_ascii_case("icns"));
|
||||
let img = if is_icns {
|
||||
tauri::image::Image::from_bytes(&icns_to_png_bytes(&src, project)?).map_err(|e| e.to_string())?
|
||||
} else {
|
||||
tauri::image::Image::from_path(&src).map_err(|e| format!("{}: {e}", src.display()))?
|
||||
};
|
||||
Ok(scale_rgba(&img, 36))
|
||||
}
|
||||
|
||||
/// Nearest-Neighbor-Skalierung eines RGBA-Bildes auf ein Quadrat der Kantenlänge `size`.
|
||||
fn scale_rgba(img: &tauri::image::Image, size: usize) -> tauri::image::Image<'static> {
|
||||
let (sw, sh) = (img.width() as usize, img.height() as usize);
|
||||
let src = img.rgba();
|
||||
let mut out = vec![0u8; size * size * 4];
|
||||
for y in 0..size {
|
||||
for x in 0..size {
|
||||
let sx = x * sw / size;
|
||||
let sy = y * sh / size;
|
||||
let si = (sy * sw + sx) * 4;
|
||||
let di = (y * size + x) * 4;
|
||||
out[di..di + 4].copy_from_slice(&src[si..si + 4]);
|
||||
}
|
||||
}
|
||||
tauri::image::Image::new_owned(out, size as u32, size as u32)
|
||||
}
|
||||
|
||||
/// ICNS → PNG-Bytes via sips (nur macOS; Linux hat kein ICNS).
|
||||
fn icns_to_png_bytes(src: &std::path::Path, project: &str) -> Result<Vec<u8>, String> {
|
||||
let tmp = std::env::temp_dir().join(format!("ai-control-tray-icon-{project}.png"));
|
||||
let out = Command::new("sips")
|
||||
.args(["-s", "format", "png", "-z", "36", "36"])
|
||||
.arg(&src)
|
||||
.args(["-s", "format", "png"])
|
||||
.arg(src)
|
||||
.arg("--out")
|
||||
.arg(&tmp)
|
||||
.output()
|
||||
@@ -924,7 +1031,7 @@ fn project_icon_rgba_36(
|
||||
}
|
||||
let bytes = fs::read(&tmp).map_err(|e| format!("{}: {e}", tmp.display()))?;
|
||||
let _ = fs::remove_file(&tmp);
|
||||
tauri::image::Image::from_bytes(&bytes).map_err(|e| e.to_string())
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Terminal-Einstellungen eines Projekts, für den Terminal-Prozess.
|
||||
@@ -1818,6 +1925,33 @@ fn set_sync_setting(enabled: bool) -> Result<(), String> {
|
||||
set_sync_on_session_end_in(&Paths::real(), enabled)
|
||||
}
|
||||
|
||||
/// Terminal-Schriftgröße (settings.json: terminalFontSize), ein Wert für alle
|
||||
/// Terminals. Default 13.
|
||||
#[tauri::command]
|
||||
fn terminal_font_size() -> u32 {
|
||||
fs::read_to_string(Paths::real().config_dir().join(APP_SETTINGS_FILE))
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
|
||||
.and_then(|v| v["terminalFontSize"].as_u64())
|
||||
.map(|n| n as u32)
|
||||
.unwrap_or(13)
|
||||
}
|
||||
|
||||
/// Setzt die Schriftgröße; erhält übrige App-settings.
|
||||
#[tauri::command]
|
||||
fn set_terminal_font_size(size: u32) -> Result<(), String> {
|
||||
let paths = Paths::real();
|
||||
let path = paths.config_dir().join(APP_SETTINGS_FILE);
|
||||
let mut v: serde_json::Value = fs::read_to_string(&path)
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_else(|| serde_json::json!({}));
|
||||
v["terminalFontSize"] = serde_json::json!(size);
|
||||
fs::create_dir_all(paths.config_dir()).map_err(|e| e.to_string())?;
|
||||
let raw = serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?;
|
||||
fs::write(&path, raw + "\n").map_err(|e| format!("{}: {e}", path.display()))
|
||||
}
|
||||
|
||||
/// Verlinkt die synced Runtime eines bestehenden Pools. Nur bei idlem Pool —
|
||||
/// sonst würde das Transkript der laufenden Session ersetzt.
|
||||
#[tauri::command]
|
||||
@@ -1839,6 +1973,10 @@ pub fn run() {
|
||||
// damit jedes Terminal ein eigenes Dock-Icon bekommt.
|
||||
Some("--terminal") => {
|
||||
let project = args.next().expect("--terminal braucht einen Projektnamen");
|
||||
// Linux: eigene Wayland-app_id pro Terminal-Prozess -> eigener Dock-Eintrag
|
||||
// (muss vor dem GTK-Init stehen). Test, ob set_prgname die app_id setzt.
|
||||
#[cfg(target_os = "linux")]
|
||||
glib::set_prgname(Some(format!("aicontrol-{project}").as_str()));
|
||||
let icon = terminal_config(&project)
|
||||
.expect("Projekt-Config nicht lesbar")
|
||||
.icon
|
||||
@@ -1908,6 +2046,72 @@ fn start_or_focus(app: &tauri::AppHandle, project: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tray-/Popup-Klick: Projekt starten oder das laufende Terminal fokussieren.
|
||||
#[tauri::command]
|
||||
fn start_or_focus_cmd(app: tauri::AppHandle, project: String) {
|
||||
start_or_focus(&app, &project);
|
||||
}
|
||||
|
||||
/// Popup-Fußzeile „Öffnen": das Hauptfenster zeigen.
|
||||
#[tauri::command]
|
||||
fn open_main_window(app: tauri::AppHandle) {
|
||||
use tauri::Manager;
|
||||
if let Some(w) = app.get_webview_window("main") {
|
||||
let _ = w.show();
|
||||
let _ = w.set_focus();
|
||||
}
|
||||
}
|
||||
|
||||
/// Popup-Fußzeile „Beenden": App verlassen.
|
||||
#[tauri::command]
|
||||
fn quit_app(app: tauri::AppHandle) {
|
||||
app.exit(0);
|
||||
}
|
||||
|
||||
/// D-Bus-Relay für die GNOME-Extension: Show()/Hide() steuern das Popup-Fenster.
|
||||
/// Die Extension ruft Show() beim Panel-Klick und positioniert das Fenster selbst.
|
||||
#[cfg(target_os = "linux")]
|
||||
struct PopupService {
|
||||
app: tauri::AppHandle,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[zbus::interface(name = "com.aicontrol.Popup1")]
|
||||
impl PopupService {
|
||||
fn show(&self) {
|
||||
use tauri::Manager;
|
||||
let app = self.app.clone();
|
||||
let win = app.clone();
|
||||
let _ = app.run_on_main_thread(move || {
|
||||
if let Some(w) = win.get_webview_window("popup") {
|
||||
let _ = w.show();
|
||||
let _ = w.set_focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
fn hide(&self) {
|
||||
use tauri::Manager;
|
||||
let app = self.app.clone();
|
||||
let win = app.clone();
|
||||
let _ = app.run_on_main_thread(move || {
|
||||
if let Some(w) = win.get_webview_window("popup") {
|
||||
let _ = w.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn serve_popup_dbus(app: tauri::AppHandle) -> zbus::Result<()> {
|
||||
let _conn = zbus::blocking::connection::Builder::session()?
|
||||
.name("com.aicontrol.Popup")?
|
||||
.serve_at("/com/aicontrol/Popup", PopupService { app })?
|
||||
.build()?;
|
||||
loop {
|
||||
std::thread::park();
|
||||
}
|
||||
}
|
||||
|
||||
/// Haupt-App: reine Tray-App ohne Dock-Eintrag.
|
||||
fn main_builder() -> tauri::Builder<tauri::Wry> {
|
||||
use tauri::Manager;
|
||||
@@ -1935,38 +2139,77 @@ fn main_builder() -> tauri::Builder<tauri::Wry> {
|
||||
// hält das Tray-Menü aktuell.
|
||||
spawn_session_watcher(app.handle().clone());
|
||||
|
||||
// Alle pro-Terminal-.desktop-Dateien neu schreiben + verwaiste entfernen,
|
||||
// damit sie da sind, bevor ein Terminal startet.
|
||||
#[cfg(target_os = "linux")]
|
||||
sync_all_desktops(&Paths::real());
|
||||
|
||||
// Fenster im Code statt in tauri.conf.json, damit der Terminal-Prozess
|
||||
// (gleiches Binary, gleiche Config) kein main-Fenster anlegt.
|
||||
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::default())
|
||||
let main_win = tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::default())
|
||||
.title("ai-control")
|
||||
.inner_size(800.0, 600.0)
|
||||
.visible(false)
|
||||
.build()?;
|
||||
.visible(false);
|
||||
// Wie die Terminal-Fenster: eigener Header als Titelleiste. macOS behält die
|
||||
// Ampel (Overlay), Linux ist dekorationslos (eigene Fensterknöpfe im Header).
|
||||
#[cfg(target_os = "macos")]
|
||||
let main_win = main_win
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.hidden_title(true);
|
||||
#[cfg(target_os = "linux")]
|
||||
let main_win = main_win.decorations(false);
|
||||
main_win.build()?;
|
||||
|
||||
let menu = tray_menu(app.handle())?;
|
||||
tauri::tray::TrayIconBuilder::with_id("main")
|
||||
// include_bytes! statt default_window_icon: cargo trackt die Datei,
|
||||
// neu generierte Icons landen damit sicher im nächsten Build.
|
||||
.icon(tauri::image::Image::from_bytes(include_bytes!(
|
||||
"../icons/trayTemplate.png"
|
||||
))?)
|
||||
// Template-Icon: macOS färbt es passend zur Menüleiste ein.
|
||||
.icon_as_template(true)
|
||||
.menu(&menu)
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
"open" => {
|
||||
let w = app.get_webview_window("main").unwrap();
|
||||
w.show().unwrap();
|
||||
w.set_focus().unwrap();
|
||||
}
|
||||
"quit" => app.exit(0),
|
||||
id => {
|
||||
if let Some(project) = id.strip_prefix("project:") {
|
||||
start_or_focus(app, project);
|
||||
// macOS: natives Tray-Menü (rendert Icons pro Eintrag). Unter Linux/GNOME
|
||||
// übernimmt die Shell-Extension den Panel-Button + Popup — kein Tray hier,
|
||||
// sonst erschiene das aIC-Icon doppelt.
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let menu = tray_menu(app.handle())?;
|
||||
tauri::tray::TrayIconBuilder::with_id("main")
|
||||
.icon(tauri::image::Image::from_bytes(include_bytes!(
|
||||
"../icons/trayTemplate.png"
|
||||
))?)
|
||||
.icon_as_template(true)
|
||||
.menu(&menu)
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
"open" => {
|
||||
let w = app.get_webview_window("main").unwrap();
|
||||
w.show().unwrap();
|
||||
w.set_focus().unwrap();
|
||||
}
|
||||
"quit" => app.exit(0),
|
||||
id => {
|
||||
if let Some(project) = id.strip_prefix("project:") {
|
||||
start_or_focus(app, project);
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
}
|
||||
|
||||
// Linux/GNOME: rahmenloses Popup-Fenster + D-Bus-Relay. Die Shell-Extension
|
||||
// (ai-control-popup@local) ruft Show() und schiebt das Fenster unter ihren
|
||||
// Panel-Button — GNOME liefert der App selbst keinen Tray-Klick.
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
tauri::WebviewWindowBuilder::new(app, "popup", tauri::WebviewUrl::App("popup.html".into()))
|
||||
.title("ai-control-popup")
|
||||
.inner_size(320.0, 300.0)
|
||||
.decorations(false)
|
||||
.visible(false)
|
||||
.transparent(true)
|
||||
.skip_taskbar(true)
|
||||
.always_on_top(true)
|
||||
.resizable(false)
|
||||
.build()?;
|
||||
let app_dbus = app.handle().clone();
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = serve_popup_dbus(app_dbus) {
|
||||
eprintln!("D-Bus-Popup-Dienst: {e}");
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
@@ -1976,6 +2219,13 @@ fn main_builder() -> tauri::Builder<tauri::Wry> {
|
||||
api.prevent_close();
|
||||
window.hide().unwrap();
|
||||
}
|
||||
// Popup schließt bei Fokusverlust.
|
||||
#[cfg(target_os = "linux")]
|
||||
if let tauri::WindowEvent::Focused(false) = event {
|
||||
if window.label() == "popup" {
|
||||
let _ = window.hide();
|
||||
}
|
||||
}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
list_projects,
|
||||
@@ -2002,8 +2252,13 @@ fn main_builder() -> tauri::Builder<tauri::Wry> {
|
||||
usage_stats,
|
||||
stop_project,
|
||||
restart_project,
|
||||
start_or_focus_cmd,
|
||||
open_main_window,
|
||||
quit_app,
|
||||
sync_setting,
|
||||
set_sync_setting,
|
||||
terminal_font_size,
|
||||
set_terminal_font_size,
|
||||
link_pool_runtime,
|
||||
oauth_login,
|
||||
keychain_status,
|
||||
@@ -2036,7 +2291,9 @@ fn terminal_builder(project: String) -> tauri::Builder<tauri::Wry> {
|
||||
// Header im Terminal-Prozess: Projektliste, Icon und Pool-Name.
|
||||
list_projects,
|
||||
project_icon,
|
||||
pool_label
|
||||
pool_label,
|
||||
terminal_font_size,
|
||||
set_terminal_font_size
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ pub fn open_terminal(app: AppHandle, project: String) -> Result<(), String> {
|
||||
|
||||
/// Tritt die Aktivierung an den nächsten startenden Prozess mit dieser
|
||||
/// Bundle-ID ab (der Terminal-Prozess läuft unter derselben).
|
||||
#[cfg(target_os = "macos")]
|
||||
fn yield_activation_to_bundle(bundle_id: &str) {
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::NSApplication;
|
||||
@@ -50,8 +51,13 @@ fn yield_activation_to_bundle(bundle_id: &str) {
|
||||
.yieldActivationToApplicationWithBundleIdentifier(&NSString::from_str(bundle_id));
|
||||
}
|
||||
|
||||
/// Linux kennt keine kooperative Aktivierungsabtretung.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn yield_activation_to_bundle(_bundle_id: &str) {}
|
||||
|
||||
/// Selbst-Aktivierung des frisch gestarteten Terminal-Prozesses (Ready-Event);
|
||||
/// die Gegenseite hat vorher per yield abgetreten.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn activate_self(app: &AppHandle) {
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::NSApplication;
|
||||
@@ -62,6 +68,14 @@ pub fn activate_self(app: &AppHandle) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Linux: Fenster ohne NSApplication über die Tauri-API fokussieren.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn activate_self(app: &AppHandle) {
|
||||
if let Some(window) = app.webview_windows().values().next() {
|
||||
window.set_focus().expect("Terminal-Fenster nicht fokussierbar");
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
@@ -95,9 +109,14 @@ pub fn set_dock_icon(path: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Linux kennt kein Dock-Icon pro Prozess.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn set_dock_icon(_path: &str) {}
|
||||
|
||||
/// Holt das Terminal-Fenster eines laufenden Projekts in den Vordergrund:
|
||||
/// aktiviert den Terminal-Prozess über seine PID. Aktivierung ist seit
|
||||
/// macOS 14 kooperativ — die Tray-App tritt sie vorher ab.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn focus_terminal(pid: u32) {
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationOptions, NSRunningApplication};
|
||||
@@ -111,6 +130,11 @@ pub fn focus_terminal(pid: u32) {
|
||||
term.activateWithOptions(NSApplicationActivationOptions::ActivateAllWindows);
|
||||
}
|
||||
|
||||
/// Linux: Fremdprozess-Fokus über die PID steht ohne NSRunningApplication
|
||||
/// nicht zur Verfügung.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn focus_terminal(_pid: u32) {}
|
||||
|
||||
/// 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.
|
||||
@@ -136,6 +160,10 @@ pub fn build_window(
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.hidden_title(true);
|
||||
|
||||
// Linux/GNOME: keine GTK-Deko — eigene Titelleiste im Header (terminal.html).
|
||||
#[cfg(target_os = "linux")]
|
||||
let builder = builder.decorations(false);
|
||||
|
||||
builder.build()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+162
-2
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { enable, disable, isEnabled } from "@tauri-apps/plugin-autostart";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import ProjectList from "./components/ProjectList.vue";
|
||||
import PoolList from "./components/PoolList.vue";
|
||||
@@ -13,6 +14,29 @@ const { locale } = useI18n();
|
||||
|
||||
onMounted(async () => {
|
||||
autostart.value = await isEnabled();
|
||||
|
||||
// macOS nutzt die native Ampel (Overlay); sonst eigene Fensterknöpfe im Header
|
||||
// (Linux ist dekorationslos, siehe lib.rs).
|
||||
const isMac = /Mac|Macintosh/.test(navigator.userAgent);
|
||||
document.documentElement.dataset.platform = isMac ? "mac" : "other";
|
||||
if (!isMac) {
|
||||
const win = getCurrentWebviewWindow();
|
||||
document
|
||||
.getElementById("win-min")
|
||||
?.addEventListener("click", () => win.minimize());
|
||||
document
|
||||
.getElementById("win-max")
|
||||
?.addEventListener("click", () => win.toggleMaximize());
|
||||
document
|
||||
.getElementById("win-close")
|
||||
?.addEventListener("click", () => win.close());
|
||||
for (const g of document.querySelectorAll<HTMLElement>(".grip")) {
|
||||
g.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
win.startResizeDragging(g.dataset.dir as never);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function toggleAutostart() {
|
||||
@@ -26,8 +50,8 @@ async function toggleAutostart() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<h1>ai-control</h1>
|
||||
<header data-tauri-drag-region>
|
||||
<h1 data-tauri-drag-region>ai-control</h1>
|
||||
<nav>
|
||||
<button :class="{ active: tab === 'projects' }" @click="tab = 'projects'">
|
||||
{{ $t("app.projects") }}
|
||||
@@ -49,10 +73,146 @@ async function toggleAutostart() {
|
||||
{{ $t("app.autostart") }}
|
||||
</label>
|
||||
</div>
|
||||
<div id="winbtns">
|
||||
<button class="winbtn" id="win-min" aria-label="Minimieren">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="2" y1="6.5" x2="10" y2="6.5" /></svg>
|
||||
</button>
|
||||
<button class="winbtn" id="win-max" aria-label="Maximieren">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect x="2.5" y="2.5" width="7" height="7" rx="1" /></svg>
|
||||
</button>
|
||||
<button class="winbtn close" id="win-close" aria-label="Schließen">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="2.7" y1="2.7" x2="9.3" y2="9.3" /><line x1="9.3" y1="2.7" x2="2.7" y2="9.3" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<ProjectList v-if="tab === 'projects'" />
|
||||
<PoolList v-else-if="tab === 'pools'" />
|
||||
<UsageList v-else />
|
||||
</main>
|
||||
<div id="resize-grips" aria-hidden="true">
|
||||
<div class="grip n" data-dir="North"></div>
|
||||
<div class="grip s" data-dir="South"></div>
|
||||
<div class="grip w" data-dir="West"></div>
|
||||
<div class="grip e" data-dir="East"></div>
|
||||
<div class="grip nw" data-dir="NorthWest"></div>
|
||||
<div class="grip ne" data-dir="NorthEast"></div>
|
||||
<div class="grip sw" data-dir="SouthWest"></div>
|
||||
<div class="grip se" data-dir="SouthEast"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* macOS: Platz links für die Ampel, eigene Fensterknöpfe aus. */
|
||||
:root[data-platform="mac"] header {
|
||||
padding-left: 80px;
|
||||
}
|
||||
:root[data-platform="mac"] #winbtns {
|
||||
display: none;
|
||||
}
|
||||
/* Linux/Windows: Fensterknöpfe rechtsbündig bis zum Rand. */
|
||||
:root[data-platform="other"] header {
|
||||
padding-right: 0;
|
||||
}
|
||||
#winbtns {
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
margin: -0.85rem 0 -0.85rem 0.5rem;
|
||||
}
|
||||
.winbtn {
|
||||
width: 46px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--subtext);
|
||||
cursor: default;
|
||||
transition: background 0.12s ease;
|
||||
}
|
||||
.winbtn svg {
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.2;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.winbtn:hover {
|
||||
background: var(--surface0);
|
||||
color: var(--text);
|
||||
}
|
||||
.winbtn.close:hover {
|
||||
background: #f38ba8;
|
||||
color: var(--crust);
|
||||
}
|
||||
/* Resize-Zonen für das dekorationslose Fenster (nur Linux/Windows). */
|
||||
#resize-grips {
|
||||
display: none;
|
||||
}
|
||||
:root[data-platform="other"] #resize-grips {
|
||||
display: block;
|
||||
}
|
||||
.grip {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
}
|
||||
.grip.n {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
.grip.s {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
.grip.w {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.grip.e {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 4px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.grip.nw {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
cursor: nwse-resize;
|
||||
z-index: 101;
|
||||
}
|
||||
.grip.ne {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
cursor: nesw-resize;
|
||||
z-index: 101;
|
||||
}
|
||||
.grip.sw {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
cursor: nesw-resize;
|
||||
z-index: 101;
|
||||
}
|
||||
.grip.se {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
cursor: nwse-resize;
|
||||
z-index: 101;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { LogicalSize } from "@tauri-apps/api/dpi";
|
||||
|
||||
interface Project {
|
||||
name: string;
|
||||
running: boolean;
|
||||
terminal: { theme: string | null; icon: string | null; title: string | null };
|
||||
}
|
||||
|
||||
const WIDTH = 300;
|
||||
|
||||
const projects = ref<Project[]>([]);
|
||||
const icons = ref<Record<string, string>>({});
|
||||
const wrapRef = ref<HTMLElement>();
|
||||
const win = getCurrentWebviewWindow();
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
projects.value = await invoke<Project[]>("list_projects");
|
||||
for (const p of projects.value) {
|
||||
if (p.terminal.icon && !(p.name in icons.value)) {
|
||||
icons.value[p.name] =
|
||||
(await invoke<string | null>("project_icon", { project: p.name })) ?? "";
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function pick(p: Project) {
|
||||
await invoke("start_or_focus_cmd", { project: p.name });
|
||||
await win.hide();
|
||||
}
|
||||
async function openMain() {
|
||||
await invoke("open_main_window");
|
||||
await win.hide();
|
||||
}
|
||||
async function quit() {
|
||||
await invoke("quit_app");
|
||||
}
|
||||
|
||||
// Fenster nur so hoch wie der Inhalt: bei jeder Layout-Änderung nachziehen.
|
||||
let ro: ResizeObserver | undefined;
|
||||
let timer: number;
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
timer = window.setInterval(refresh, 2000);
|
||||
if (wrapRef.value) {
|
||||
ro = new ResizeObserver(() => {
|
||||
const h = Math.ceil(wrapRef.value!.getBoundingClientRect().height);
|
||||
if (h > 0) win.setSize(new LogicalSize(WIDTH, h));
|
||||
});
|
||||
ro.observe(wrapRef.value);
|
||||
}
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.clearInterval(timer);
|
||||
ro?.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="wrapRef" class="wrap">
|
||||
<ul class="list">
|
||||
<li v-for="p in projects" :key="p.name" class="row" @click="pick(p)">
|
||||
<span class="dot" :class="{ on: p.running }"></span>
|
||||
<img v-if="icons[p.name]" class="ic" :src="icons[p.name]" />
|
||||
<span v-else class="ic ph"></span>
|
||||
<span class="nm">{{ p.name }}</span>
|
||||
</li>
|
||||
<li v-if="projects.length === 0" class="empty">Keine Projekte</li>
|
||||
</ul>
|
||||
|
||||
<footer class="ft">
|
||||
<button class="fbtn" @click="openMain">Öffnen</button>
|
||||
<button class="fbtn danger" @click="quit">Beenden</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wrap {
|
||||
width: 300px;
|
||||
box-sizing: border-box;
|
||||
background: #1e1e2e;
|
||||
color: #cdd6f4;
|
||||
border: 1px solid #313244;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
font:
|
||||
14px/1.3 system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
padding: 7px 9px;
|
||||
border-radius: 9px;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease;
|
||||
}
|
||||
.row:hover {
|
||||
background: #313244;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: #585b70;
|
||||
flex: none;
|
||||
}
|
||||
.dot.on {
|
||||
background: #a6e3a1;
|
||||
box-shadow: 0 0 7px #a6e3a1a0;
|
||||
}
|
||||
|
||||
.ic {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
flex: none;
|
||||
object-fit: cover;
|
||||
}
|
||||
.ic.ph {
|
||||
background: #313244;
|
||||
}
|
||||
|
||||
.nm {
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #585b70;
|
||||
}
|
||||
|
||||
.ft {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 5px 6px 6px;
|
||||
border-top: 1px solid #313244;
|
||||
}
|
||||
.fbtn {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: #313244;
|
||||
color: #cdd6f4;
|
||||
font: inherit;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease;
|
||||
}
|
||||
.fbtn:hover {
|
||||
background: #45475a;
|
||||
}
|
||||
.fbtn.danger {
|
||||
color: #f38ba8;
|
||||
}
|
||||
.fbtn.danger:hover {
|
||||
background: #45303a;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createApp } from "vue";
|
||||
import Popup from "./components/Popup.vue";
|
||||
|
||||
createApp(Popup).mount("#app");
|
||||
+52
-3
@@ -29,6 +29,29 @@ window.addEventListener("unhandledrejection", (e) =>
|
||||
const project = new URLSearchParams(location.search).get("project")!;
|
||||
const win = getCurrentWebviewWindow();
|
||||
|
||||
// macOS nutzt die native Ampel (Overlay-Titelbar); sonst eigene Fensterknöpfe
|
||||
// im Header (Linux ist dekorationslos, siehe terminal.rs).
|
||||
const isMac = /Mac|Macintosh/.test(navigator.userAgent);
|
||||
document.documentElement.dataset.platform = isMac ? "mac" : "other";
|
||||
if (!isMac) {
|
||||
document
|
||||
.getElementById("win-min")
|
||||
?.addEventListener("click", () => win.minimize());
|
||||
document
|
||||
.getElementById("win-max")
|
||||
?.addEventListener("click", () => win.toggleMaximize());
|
||||
document
|
||||
.getElementById("win-close")
|
||||
?.addEventListener("click", () => win.close());
|
||||
// Resize-Zonen: dekorationslose Fenster haben keinen nativen Rand.
|
||||
for (const g of document.querySelectorAll<HTMLElement>(".grip")) {
|
||||
g.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
win.startResizeDragging(g.dataset.dir as never);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fensterhintergrund beim Öffnen kommt aus terminal.rs (theme_background);
|
||||
// dort nicht gepflegte Themes fallen auf Mocha zurück (nur kurzer Moment
|
||||
// bis die Webview lädt).
|
||||
@@ -427,11 +450,15 @@ header.style.background = picked.header;
|
||||
header.style.borderBottomColor = picked.border;
|
||||
header.style.color = theme.foreground;
|
||||
|
||||
// Globale Schriftgröße (settings.json: terminalFontSize), ein Wert für alle.
|
||||
const DEFAULT_FONT_SIZE = 13;
|
||||
let fontSize = await invoke<number>("terminal_font_size");
|
||||
|
||||
const term = new Terminal({
|
||||
// Ligatur-Addon nutzt die Character-Joiner-API (Proposed API)
|
||||
allowProposedApi: true,
|
||||
fontFamily: '"JetBrains Mono", Menlo, monospace',
|
||||
fontSize: 13,
|
||||
fontSize,
|
||||
fontWeightBold: "600",
|
||||
lineHeight: 1.0,
|
||||
letterSpacing: 0,
|
||||
@@ -443,6 +470,14 @@ const term = new Terminal({
|
||||
const fit = new FitAddon();
|
||||
term.loadAddon(fit);
|
||||
|
||||
// Ctrl/Cmd + +/-/0: Schriftgröße global anpassen, sofort im aktuellen Fenster.
|
||||
function applyFontSize(px: number) {
|
||||
fontSize = Math.max(8, Math.min(24, px));
|
||||
term.options.fontSize = fontSize;
|
||||
fit.fit();
|
||||
invoke("set_terminal_font_size", { size: fontSize });
|
||||
}
|
||||
|
||||
// Shift+Enter als CSI-u-Sequenz senden (Zeilenumbruch in Claude Code,
|
||||
// auch bei nicht-leerer Zeile). Neben keydown muss auch keypress unter-
|
||||
// drückt werden, sonst sendet xterm.js zusätzlich \r und submittet damit.
|
||||
@@ -451,13 +486,27 @@ term.attachCustomKeyEventHandler((e) => {
|
||||
if (e.type === "keydown") invoke("term_write", { data: "\x1b[13;2u" });
|
||||
return false;
|
||||
}
|
||||
if ((e.ctrlKey || e.metaKey) && !e.altKey) {
|
||||
if (e.key === "+" || e.key === "=") {
|
||||
if (e.type === "keydown") applyFontSize(fontSize + 1);
|
||||
return false;
|
||||
}
|
||||
if (e.key === "-" || e.key === "_") {
|
||||
if (e.type === "keydown") applyFontSize(fontSize - 1);
|
||||
return false;
|
||||
}
|
||||
if (e.key === "0") {
|
||||
if (e.type === "keydown") applyFontSize(DEFAULT_FONT_SIZE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Font muss geladen sein, bevor WebGL den Glyphen-Atlas aufbaut —
|
||||
// beide verwendeten Gewichte (400 normal, 600 bold).
|
||||
await document.fonts.load('13px "JetBrains Mono"');
|
||||
await document.fonts.load('600 13px "JetBrains Mono"');
|
||||
await document.fonts.load(`${fontSize}px "JetBrains Mono"`);
|
||||
await document.fonts.load(`600 ${fontSize}px "JetBrains Mono"`);
|
||||
term.open(document.getElementById("term")!);
|
||||
// Reihenfolge laut Addon-Doku: Ligaturen vor WebGL aktivieren.
|
||||
term.loadAddon(new LigaturesAddon());
|
||||
|
||||
+139
-3
@@ -16,13 +16,13 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
header {
|
||||
height: 44px;
|
||||
height: 40px;
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
/* links Platz für die macOS-Ampel (Overlay-Titelbar) */
|
||||
padding: 0 16px 0 84px;
|
||||
/* Linux: schmaler Rand links; macOS: Ampel-Platz (Override unten) */
|
||||
padding: 0 0 0 16px;
|
||||
background: #181825;
|
||||
border-bottom: 1px solid #313244;
|
||||
color: #cdd6f4;
|
||||
@@ -34,6 +34,14 @@
|
||||
-webkit-user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
/* macOS: Platz links für die Ampel, eigene Fensterknöpfe aus. */
|
||||
:root[data-platform="mac"] header {
|
||||
padding-left: 84px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
:root[data-platform="mac"] #winbtns {
|
||||
display: none;
|
||||
}
|
||||
#project-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
@@ -53,6 +61,113 @@
|
||||
#pool:empty {
|
||||
display: none;
|
||||
}
|
||||
/* Eigene Fensterknöpfe (Linux): rechtsbündig, volle Header-Höhe. */
|
||||
#winbtns {
|
||||
margin-left: 8px;
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
}
|
||||
#pool:empty + #winbtns {
|
||||
margin-left: auto;
|
||||
}
|
||||
.winbtn {
|
||||
width: 44px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: default;
|
||||
transition: background 0.12s ease;
|
||||
}
|
||||
.winbtn svg {
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.2;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.winbtn:hover {
|
||||
background: #313244;
|
||||
}
|
||||
.winbtn:hover svg {
|
||||
opacity: 1;
|
||||
}
|
||||
.winbtn.close:hover {
|
||||
background: #f38ba8;
|
||||
color: #11111b;
|
||||
}
|
||||
/* Resize-Zonen für das dekorationslose Fenster (nur Linux). */
|
||||
#resize-grips {
|
||||
display: none;
|
||||
}
|
||||
:root[data-platform="other"] #resize-grips {
|
||||
display: block;
|
||||
}
|
||||
.grip {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
}
|
||||
.grip.n {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
.grip.s {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
.grip.w {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.grip.e {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 4px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.grip.nw {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
cursor: nwse-resize;
|
||||
z-index: 101;
|
||||
}
|
||||
.grip.ne {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
cursor: nesw-resize;
|
||||
z-index: 101;
|
||||
}
|
||||
.grip.sw {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
cursor: nesw-resize;
|
||||
z-index: 101;
|
||||
}
|
||||
.grip.se {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
cursor: nwse-resize;
|
||||
z-index: 101;
|
||||
}
|
||||
#term {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -75,8 +190,29 @@
|
||||
<img id="project-icon" hidden data-tauri-drag-region />
|
||||
<span id="project-name" data-tauri-drag-region></span>
|
||||
<span id="pool"></span>
|
||||
<div id="winbtns">
|
||||
<button class="winbtn" id="win-min" aria-label="Minimieren">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="2" y1="6.5" x2="10" y2="6.5" /></svg>
|
||||
</button>
|
||||
<button class="winbtn" id="win-max" aria-label="Maximieren">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect x="2.5" y="2.5" width="7" height="7" rx="1" /></svg>
|
||||
</button>
|
||||
<button class="winbtn close" id="win-close" aria-label="Schließen">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12"><line x1="2.7" y1="2.7" x2="9.3" y2="9.3" /><line x1="9.3" y1="2.7" x2="2.7" y2="9.3" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div id="term"></div>
|
||||
<div id="resize-grips" aria-hidden="true">
|
||||
<div class="grip n" data-dir="North"></div>
|
||||
<div class="grip s" data-dir="South"></div>
|
||||
<div class="grip w" data-dir="West"></div>
|
||||
<div class="grip e" data-dir="East"></div>
|
||||
<div class="grip nw" data-dir="NorthWest"></div>
|
||||
<div class="grip ne" data-dir="NorthEast"></div>
|
||||
<div class="grip sw" data-dir="SouthWest"></div>
|
||||
<div class="grip se" data-dir="SouthEast"></div>
|
||||
</div>
|
||||
<script type="module" src="/src/terminal.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -17,6 +17,7 @@ export default defineConfig(async () => ({
|
||||
input: {
|
||||
main: new URL("./index.html", import.meta.url).pathname,
|
||||
terminal: new URL("./terminal.html", import.meta.url).pathname,
|
||||
popup: new URL("./popup.html", import.meta.url).pathname,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user