Skip to content

Commit d4258cc

Browse files
committed
various control window visibility groundwork
1 parent b912aa3 commit d4258cc

File tree

11 files changed

+207
-56
lines changed

11 files changed

+207
-56
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use std::sync::Arc;
2+
use std::sync::atomic::{AtomicBool, Ordering};
3+
4+
use tauri_plugin_listener::SessionLifecycleEvent;
5+
use tauri_plugin_windows::{AppWindow, VisibilityEvent};
6+
use tauri_specta::Event;
7+
8+
pub fn setup(app_handle: &tauri::AppHandle<tauri::Wry>) {
9+
let session_active = Arc::new(AtomicBool::new(false));
10+
11+
setup_session_lifecycle_listener(app_handle, session_active.clone());
12+
setup_visibility_listener(app_handle, session_active);
13+
}
14+
15+
fn setup_session_lifecycle_listener(
16+
app_handle: &tauri::AppHandle<tauri::Wry>,
17+
session_active: Arc<AtomicBool>,
18+
) {
19+
let handle = app_handle.clone();
20+
21+
SessionLifecycleEvent::listen_any(app_handle, move |event| match event.payload {
22+
SessionLifecycleEvent::Active { .. } => {
23+
session_active.store(true, Ordering::SeqCst);
24+
25+
let main_visible = AppWindow::Main
26+
.get(&handle)
27+
.and_then(|w| w.is_visible().ok())
28+
.unwrap_or(false);
29+
30+
let _ = AppWindow::Control.show(&handle);
31+
if main_visible {
32+
let _ = AppWindow::Control.hide(&handle);
33+
}
34+
}
35+
SessionLifecycleEvent::Inactive { .. } => {
36+
session_active.store(false, Ordering::SeqCst);
37+
let _ = AppWindow::Control.destroy(&handle);
38+
}
39+
SessionLifecycleEvent::Finalizing { .. } => {}
40+
});
41+
}
42+
43+
fn setup_visibility_listener(
44+
app_handle: &tauri::AppHandle<tauri::Wry>,
45+
session_active: Arc<AtomicBool>,
46+
) {
47+
let handle = app_handle.clone();
48+
49+
VisibilityEvent::listen_any(app_handle, move |event| {
50+
if event.payload.window != AppWindow::Main {
51+
return;
52+
}
53+
54+
if !session_active.load(Ordering::SeqCst) {
55+
return;
56+
}
57+
58+
if event.payload.visible {
59+
let _ = AppWindow::Control.hide(&handle);
60+
} else {
61+
let _ = AppWindow::Control.show(&handle);
62+
}
63+
});
64+
}

apps/desktop/src-tauri/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod commands;
2+
mod control;
23
mod ext;
34
mod store;
45
mod supervisor;
@@ -158,6 +159,8 @@ pub async fn main() {
158159
let app_handle = app.handle().clone();
159160
let app_clone = app_handle.clone();
160161

162+
specta_builder.mount_events(&app_handle);
163+
161164
#[cfg(any(windows, target_os = "linux"))]
162165
{
163166
// https://v2.tauri.app/ko/plugin/deep-linking/#desktop-1
@@ -183,7 +186,7 @@ pub async fn main() {
183186
supervisor::monitor_supervisor(handle, ctx.is_exiting.clone(), app_handle.clone());
184187
}
185188

186-
specta_builder.mount_events(&app_handle);
189+
// control::setup(&app_handle);
187190

188191
Ok(())
189192
})

apps/desktop/src/components/main/sidebar/devtool.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,10 @@ function NavigationCard() {
224224
});
225225
}, []);
226226

227+
const handleShowControl = useCallback(() => {
228+
void windowsCommands.windowShow({ type: "control" });
229+
}, []);
230+
227231
return (
228232
<DevtoolCard title="Navigation">
229233
<div className="flex flex-col gap-1.5">
@@ -253,6 +257,19 @@ function NavigationCard() {
253257
>
254258
Main
255259
</button>
260+
<button
261+
type="button"
262+
onClick={handleShowControl}
263+
className={cn([
264+
"w-full px-2.5 py-1.5 rounded-md",
265+
"text-xs font-medium text-left",
266+
"border border-neutral-200 text-neutral-700",
267+
"cursor-pointer transition-colors",
268+
"hover:bg-neutral-50 hover:border-neutral-300",
269+
])}
270+
>
271+
Control
272+
</button>
256273
</div>
257274
</DevtoolCard>
258275
);

apps/desktop/src/routes/app/control.tsx

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { useQuery } from "@tanstack/react-query";
12
import { createFileRoute } from "@tanstack/react-router";
23
import { getCurrentWindow } from "@tauri-apps/api/window";
34
import { ChevronDown, Mic, MicOff, Square, X } from "lucide-react";
5+
import { useRef } from "react";
46

7+
import { commands as iconCommands } from "@hypr/plugin-icon";
58
import { Button } from "@hypr/ui/components/ui/button";
69
import { cn } from "@hypr/utils";
710

@@ -69,25 +72,75 @@ function CollapsedWidget({
6972
isActive: boolean;
7073
isFinalizing: boolean;
7174
}) {
75+
const mouseDownPos = useRef<{ x: number; y: number } | null>(null);
76+
77+
const { data: iconBase64 } = useQuery({
78+
queryKey: ["app-icon"],
79+
queryFn: async () => {
80+
const result = await iconCommands.getIcon();
81+
if (result.status === "ok") {
82+
return result.data;
83+
}
84+
return null;
85+
},
86+
staleTime: Infinity,
87+
});
88+
89+
const handleMouseDown = (e: React.MouseEvent) => {
90+
mouseDownPos.current = { x: e.clientX, y: e.clientY };
91+
};
92+
93+
const handleMouseUp = (e: React.MouseEvent) => {
94+
if (!mouseDownPos.current) return;
95+
96+
const dx = Math.abs(e.clientX - mouseDownPos.current.x);
97+
const dy = Math.abs(e.clientY - mouseDownPos.current.y);
98+
const wasDrag = dx > 5 || dy > 5;
99+
100+
mouseDownPos.current = null;
101+
102+
if (!wasDrag) {
103+
onExpand();
104+
}
105+
};
106+
107+
const handleMouseLeave = () => {
108+
mouseDownPos.current = null;
109+
};
110+
72111
return (
73112
<div
74-
onClick={onExpand}
113+
data-tauri-drag-region
114+
onMouseDown={handleMouseDown}
115+
onMouseUp={handleMouseUp}
116+
onMouseLeave={handleMouseLeave}
75117
className={cn([
76118
"h-full w-full flex items-center justify-center cursor-pointer",
77-
"bg-black/70 backdrop-blur-md rounded-full",
78119
])}
79120
>
80121
{isActive || isFinalizing ? (
81122
<div
82-
className={cn(
123+
data-tauri-drag-region
124+
className={cn([
83125
"w-3 h-3 rounded-full",
84126
isFinalizing
85127
? "bg-yellow-500 animate-pulse"
86128
: "bg-red-500 animate-pulse",
87-
)}
129+
])}
130+
/>
131+
) : iconBase64 ? (
132+
<img
133+
data-tauri-drag-region
134+
src={`data:image/png;base64,${iconBase64}`}
135+
alt="App Icon"
136+
className="w-12 h-12 rounded-xl"
137+
draggable={false}
88138
/>
89139
) : (
90-
<div className="w-3 h-3 rounded-full bg-white/40" />
140+
<div
141+
data-tauri-drag-region
142+
className="w-12 h-12 rounded-full bg-white/40"
143+
/>
91144
)}
92145
</div>
93146
);

apps/desktop/src/store/tinybase/persister/settings.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -225,16 +225,17 @@ export function createSettingsPersister<Schemas extends OptionalSchemas>(
225225
interval: setInterval(listener, FALLBACK_POLL_INTERVAL),
226226
};
227227

228-
events.settingsChanged
229-
.listen(() => {
230-
listener();
231-
})
232-
.then((unlisten) => {
233-
handle.unlisten = unlisten;
234-
})
235-
.catch((error) => {
236-
console.error("[SettingsPersister] event listen error:", error);
228+
(async () => {
229+
const settingsPath = await commands.path();
230+
const unlisten = await events.fileChanged.listen((event) => {
231+
if (event.payload.path === settingsPath) {
232+
listener();
233+
}
237234
});
235+
handle.unlisten = unlisten;
236+
})().catch((error) => {
237+
console.error("[SettingsPersister] event listen error:", error);
238+
});
238239

239240
return handle;
240241
},

plugins/listener/src/events.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use owhisper_interface::stream::StreamResponse;
33
#[macro_export]
44
macro_rules! common_event_derives {
55
($item:item) => {
6-
#[derive(serde::Serialize, Clone, specta::Type, tauri_specta::Event)]
6+
#[derive(
7+
serde::Serialize, serde::Deserialize, Clone, specta::Type, tauri_specta::Event,
8+
)]
79
$item
810
};
911
}

plugins/windows/js/bindings.gen.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,6 @@ async windowIsExists(window: AppWindow) : Promise<Result<boolean, string>> {
4545
if(e instanceof Error) throw e;
4646
else return { status: "error", error: e as any };
4747
}
48-
},
49-
async setFakeWindowBounds(name: string, bounds: OverlayBound) : Promise<Result<null, string>> {
50-
try {
51-
return { status: "ok", data: await TAURI_INVOKE("plugin:windows|set_fake_window_bounds", { name, bounds }) };
52-
} catch (e) {
53-
if(e instanceof Error) throw e;
54-
else return { status: "error", error: e as any };
55-
}
56-
},
57-
async removeFakeWindow(name: string) : Promise<Result<null, string>> {
58-
try {
59-
return { status: "ok", data: await TAURI_INVOKE("plugin:windows|remove_fake_window", { name }) };
60-
} catch (e) {
61-
if(e instanceof Error) throw e;
62-
else return { status: "error", error: e as any };
63-
}
6448
}
6549
}
6650

@@ -70,10 +54,12 @@ async removeFakeWindow(name: string) : Promise<Result<null, string>> {
7054
export const events = __makeEvents__<{
7155
navigate: Navigate,
7256
openTab: OpenTab,
57+
visibilityEvent: VisibilityEvent,
7358
windowDestroyed: WindowDestroyed
7459
}>({
7560
navigate: "plugin:windows:navigate",
7661
openTab: "plugin:windows:open-tab",
62+
visibilityEvent: "plugin:windows:visibility-event",
7763
windowDestroyed: "plugin:windows:window-destroyed"
7864
})
7965

@@ -96,11 +82,11 @@ export type ExtensionsState = { selectedExtension: string | null }
9682
export type JsonValue = null | boolean | number | string | JsonValue[] | Partial<{ [key in string]: JsonValue }>
9783
export type Navigate = { path: string; search: Partial<{ [key in string]: JsonValue }> | null }
9884
export type OpenTab = { tab: TabInput }
99-
export type OverlayBound = { x: number; y: number; width: number; height: number }
10085
export type PromptsState = { selectedTask: string | null }
10186
export type SessionsState = { view: EditorView | null; autoStart: boolean | null }
10287
export type TabInput = { type: "sessions"; id: string; state?: SessionsState | null } | { type: "contacts"; state?: ContactsState | null } | { type: "templates"; state?: TemplatesState | null } | { type: "prompts"; state?: PromptsState | null } | { type: "chat_shortcuts"; state?: ChatShortcutsState | null } | { type: "extensions"; state?: ExtensionsState | null } | { type: "humans"; id: string } | { type: "organizations"; id: string } | { type: "folders"; id: string | null } | { type: "empty" } | { type: "extension"; extensionId: string; state?: Partial<{ [key in string]: JsonValue }> | null } | { type: "calendar" } | { type: "changelog"; state: ChangelogState } | { type: "settings" } | { type: "ai"; state?: AiState | null } | { type: "data"; state?: DataState | null }
10388
export type TemplatesState = { showHomepage: boolean | null; isWebMode: boolean | null; selectedMineId: string | null; selectedWebIndex: number | null }
89+
export type VisibilityEvent = { window: AppWindow; visible: boolean }
10490
export type WindowDestroyed = { window: AppWindow }
10591

10692
/** tauri-specta globals **/

plugins/windows/src/events.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,19 @@ use crate::AppWindow;
66

77
// TODO: https://github.com/fastrepl/hyprnote/commit/150c8a1 this not worked. webview_window not found.
88
pub fn on_window_event(window: &tauri::Window<tauri::Wry>, event: &tauri::WindowEvent) {
9-
let _app = window.app_handle();
9+
let app = window.app_handle();
1010

1111
match event {
1212
tauri::WindowEvent::CloseRequested { api, .. } => {
1313
match window.label().parse::<AppWindow>() {
1414
Err(e) => tracing::warn!("window_parse_error: {:?}", e),
1515
Ok(w) => {
16-
if w == AppWindow::Main && window.hide().is_ok() {
16+
if w == AppWindow::Main && w.hide(&app).is_ok() {
1717
api.prevent_close();
1818
}
1919
if w == AppWindow::Onboarding {
2020
use tauri_plugin_sfx::SfxPluginExt;
21-
window
22-
.app_handle()
23-
.sfx()
24-
.stop(tauri_plugin_sfx::AppSounds::BGM);
21+
app.sfx().stop(tauri_plugin_sfx::AppSounds::BGM);
2522
}
2623
}
2724
}
@@ -85,6 +82,13 @@ common_event_derives! {
8582
}
8683
}
8784

85+
common_event_derives! {
86+
pub struct VisibilityEvent {
87+
pub window: AppWindow,
88+
pub visible: bool,
89+
}
90+
}
91+
8892
#[cfg(test)]
8993
mod test {
9094
use super::*;

0 commit comments

Comments
 (0)