fix: prevent window from hiding when moved on Windows (#748)

* fix: prevent window from hiding when moved on Windows

* docs: update changelog

* update
This commit is contained in:
ayangweb
2025-07-09 16:30:41 +08:00
committed by GitHub
parent bc524e19db
commit 074a7c8b0a
8 changed files with 82 additions and 101 deletions

View File

@@ -17,8 +17,10 @@ Information about release notes of Coco Server is provided here.
- feat: voice input support in both search and chat modes #732
### 🐛 Bug fix
- fix(file search): apply filters before from/size parameters #741
- fix(file search): searching by name&content does not search file name #743
- fix: prevent window from hiding when moved on Windows #748
### ✈️ Improvements
@@ -316,4 +318,4 @@ Information about release notes of Coco Server is provided here.
### Bug fix
### Improvements
### Improvements

View File

@@ -145,7 +145,6 @@ pub fn run() {
server::attachment::delete_attachment,
server::transcription::transcription,
server::system_settings::get_system_settings,
simulate_mouse_click,
extension::built_in::application::get_app_list,
extension::built_in::application::get_app_search_path,
extension::built_in::application::get_app_metadata,
@@ -452,52 +451,6 @@ async fn hide_check(app_handle: AppHandle) {
window.hide().unwrap();
}
#[tauri::command]
async fn simulate_mouse_click<R: Runtime>(window: WebviewWindow<R>, is_chat_mode: bool) {
#[cfg(target_os = "windows")]
{
use enigo::{Button, Coordinate, Direction, Enigo, Mouse, Settings};
use std::{thread, time::Duration};
if let Ok(mut enigo) = Enigo::new(&Settings::default()) {
// Save the current mouse position
if let Ok((original_x, original_y)) = enigo.location() {
// Retrieve the window's outer position (top-left corner)
if let Ok(position) = window.outer_position() {
// Retrieve the window's inner size (client area)
if let Ok(size) = window.inner_size() {
// Calculate the center position of the title bar
let x = position.x + (size.width as i32 / 2);
let y = if is_chat_mode {
position.y + size.height as i32 - 50
} else {
position.y + 30
};
// Move the mouse cursor to the calculated position
if enigo.move_mouse(x, y, Coordinate::Abs).is_ok() {
// // Simulate a left mouse click
let _ = enigo.button(Button::Left, Direction::Click);
// let _ = enigo.button(Button::Left, Direction::Release);
thread::sleep(Duration::from_millis(100));
// Move the mouse cursor back to the original position
let _ = enigo.move_mouse(original_x, original_y, Coordinate::Abs);
}
}
}
}
}
}
#[cfg(not(target_os = "windows"))]
{
let _ = window;
let _ = is_chat_mode;
}
}
/// Log format:
///
/// ```text

View File

@@ -18,6 +18,7 @@ import InputControls from "./InputControls";
import { useExtensionsStore } from "@/stores/extensionsStore";
import AudioRecording from "../AudioRecording";
import { isDefaultServer } from "@/utils";
import { useTauriFocus } from "@/hooks/useTauriFocus";
interface ChatInputProps {
onSend: (message: string) => void;
@@ -99,18 +100,12 @@ export default function ChatInput({
const { setSearchValue, visibleExtensionStore, selectedExtension } =
useSearchStore();
useEffect(() => {
const handleFocus = () => {
useTauriFocus({
onFocus() {
setBlurred(false);
setModifierKeyPressed(false);
};
window.addEventListener("focus", handleFocus);
return () => {
window.removeEventListener("focus", handleFocus);
};
}, []);
},
});
const handleToggleFocus = useCallback(() => {
textareaRef.current?.focus();

View File

@@ -118,19 +118,6 @@ function SearchChat({
const isWin10 = await platformAdapter.isWindows10();
setIsWin10(isWin10);
const unlisten = platformAdapter.listenEvent("show-coco", () => {
//console.log("show-coco");
platformAdapter.invokeBackend("simulate_mouse_click", {
isChatMode: isChatModeRef.current,
});
});
return () => {
// Cleanup logic if needed
unlisten.then((fn) => fn());
};
});
useEffect(() => {

View File

@@ -0,0 +1,44 @@
import { useRef } from "react";
import { debounce, noop } from "lodash-es";
import { useMount, useUnmount } from "ahooks";
import { useAppStore } from "@/stores/appStore";
import { isMac } from "@/utils/platform";
import platformAdapter from "@/utils/platformAdapter";
interface Props {
onFocus?: () => void;
onBlur?: () => void;
}
export const useTauriFocus = (props: Props) => {
const { onFocus, onBlur } = props;
const { isTauri } = useAppStore();
const unlistenRef = useRef(noop);
useMount(async () => {
if (!isTauri) return;
const appWindow = await platformAdapter.getWebviewWindow();
const wait = isMac ? 0 : 100;
const debounced = debounce(({ payload }) => {
if (payload) {
console.log("Window focused");
onFocus?.();
} else {
console.log("Window blurred");
onBlur?.();
}
}, wait);
unlistenRef.current = await appWindow.onFocusChanged(debounced);
});
useUnmount(() => {
unlistenRef.current();
});
};

View File

@@ -1,29 +1,21 @@
import { useEffect } from "react";
import { useAppStore } from "@/stores/appStore";
import platformAdapter from "@/utils/platformAdapter";
import { useTauriFocus } from "./useTauriFocus";
export function useWindowEvents() {
const isPinned = useAppStore((state) => state.isPinned);
const visible = useAppStore((state) => state.visible);
const setBlurred = useAppStore((state) => state.setBlurred);
useEffect(() => {
const handleBlur = async () => {
console.log("Window blurred");
useTauriFocus({
onBlur() {
if (isPinned || visible) {
return setBlurred(true);
}
await platformAdapter.hideWindow();
platformAdapter.hideWindow();
console.log("Hide Coco");
};
window.addEventListener("blur", handleBlur);
// Clean up event listeners on component unmount
return () => {
window.removeEventListener("blur", handleBlur);
};
}, [isPinned, visible]);
},
});
}

View File

@@ -84,14 +84,14 @@ export const createTauriAdapter = (): TauriPlatformAdapter => {
);
return requestScreenRecordingPermission();
},
async checkMicrophonePermission() {
const { checkMicrophonePermission } = await import(
"tauri-plugin-macos-permissions-api"
);
return checkMicrophonePermission();
},
async requestMicrophonePermission() {
const { requestMicrophonePermission } = await import(
"tauri-plugin-macos-permissions-api"

View File

@@ -1,4 +1,4 @@
import * as commands from '@/commands';
import * as commands from "@/commands";
// Window operations
export const windowWrapper = {
@@ -6,12 +6,14 @@ export const windowWrapper = {
const { getCurrentWindow } = await import("@tauri-apps/api/window");
return getCurrentWindow();
},
async getWebviewWindow() {
const { getCurrentWebviewWindow } = await import("@tauri-apps/api/webviewWindow");
const { getCurrentWebviewWindow } = await import(
"@tauri-apps/api/webviewWindow"
);
return getCurrentWebviewWindow();
},
async setSize(width: number, height: number) {
const { LogicalSize } = await import("@tauri-apps/api/dpi");
const window = await this.getWebviewWindow();
@@ -27,7 +29,7 @@ export const eventWrapper = {
const { emit } = await import("@tauri-apps/api/event");
return emit(event, payload);
},
async listen(event: string, callback: Function) {
const { listen } = await import("@tauri-apps/api/event");
return listen(event, (e) => callback(e));
@@ -37,16 +39,22 @@ export const eventWrapper = {
// System functions
export const systemWrapper = {
async checkScreenPermission() {
const { checkScreenRecordingPermission } = await import("tauri-plugin-macos-permissions-api");
const { checkScreenRecordingPermission } = await import(
"tauri-plugin-macos-permissions-api"
);
return checkScreenRecordingPermission();
},
async captureScreen(id: number, type: 'monitor' | 'window') {
if (type === 'monitor') {
const { getMonitorScreenshot } = await import("tauri-plugin-screenshots-api");
async captureScreen(id: number, type: "monitor" | "window") {
if (type === "monitor") {
const { getMonitorScreenshot } = await import(
"tauri-plugin-screenshots-api"
);
return getMonitorScreenshot(id);
} else {
const { getWindowScreenshot } = await import("tauri-plugin-screenshots-api");
const { getWindowScreenshot } = await import(
"tauri-plugin-screenshots-api"
);
return getWindowScreenshot(id);
}
},
@@ -60,5 +68,5 @@ export const commandWrapper = {
return (commands as any)[commandName](...args);
}
throw new Error(`Command ${commandName} not found`);
}
};
},
};