mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
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:
@@ -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
|
- feat: voice input support in both search and chat modes #732
|
||||||
|
|
||||||
### 🐛 Bug fix
|
### 🐛 Bug fix
|
||||||
|
|
||||||
- fix(file search): apply filters before from/size parameters #741
|
- fix(file search): apply filters before from/size parameters #741
|
||||||
- fix(file search): searching by name&content does not search file name #743
|
- fix(file search): searching by name&content does not search file name #743
|
||||||
|
- fix: prevent window from hiding when moved on Windows #748
|
||||||
|
|
||||||
### ✈️ Improvements
|
### ✈️ Improvements
|
||||||
|
|
||||||
@@ -316,4 +318,4 @@ Information about release notes of Coco Server is provided here.
|
|||||||
|
|
||||||
### Bug fix
|
### Bug fix
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|||||||
@@ -145,7 +145,6 @@ pub fn run() {
|
|||||||
server::attachment::delete_attachment,
|
server::attachment::delete_attachment,
|
||||||
server::transcription::transcription,
|
server::transcription::transcription,
|
||||||
server::system_settings::get_system_settings,
|
server::system_settings::get_system_settings,
|
||||||
simulate_mouse_click,
|
|
||||||
extension::built_in::application::get_app_list,
|
extension::built_in::application::get_app_list,
|
||||||
extension::built_in::application::get_app_search_path,
|
extension::built_in::application::get_app_search_path,
|
||||||
extension::built_in::application::get_app_metadata,
|
extension::built_in::application::get_app_metadata,
|
||||||
@@ -452,52 +451,6 @@ async fn hide_check(app_handle: AppHandle) {
|
|||||||
window.hide().unwrap();
|
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:
|
/// Log format:
|
||||||
///
|
///
|
||||||
/// ```text
|
/// ```text
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import InputControls from "./InputControls";
|
|||||||
import { useExtensionsStore } from "@/stores/extensionsStore";
|
import { useExtensionsStore } from "@/stores/extensionsStore";
|
||||||
import AudioRecording from "../AudioRecording";
|
import AudioRecording from "../AudioRecording";
|
||||||
import { isDefaultServer } from "@/utils";
|
import { isDefaultServer } from "@/utils";
|
||||||
|
import { useTauriFocus } from "@/hooks/useTauriFocus";
|
||||||
|
|
||||||
interface ChatInputProps {
|
interface ChatInputProps {
|
||||||
onSend: (message: string) => void;
|
onSend: (message: string) => void;
|
||||||
@@ -99,18 +100,12 @@ export default function ChatInput({
|
|||||||
const { setSearchValue, visibleExtensionStore, selectedExtension } =
|
const { setSearchValue, visibleExtensionStore, selectedExtension } =
|
||||||
useSearchStore();
|
useSearchStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useTauriFocus({
|
||||||
const handleFocus = () => {
|
onFocus() {
|
||||||
setBlurred(false);
|
setBlurred(false);
|
||||||
setModifierKeyPressed(false);
|
setModifierKeyPressed(false);
|
||||||
};
|
},
|
||||||
|
});
|
||||||
window.addEventListener("focus", handleFocus);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("focus", handleFocus);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleToggleFocus = useCallback(() => {
|
const handleToggleFocus = useCallback(() => {
|
||||||
textareaRef.current?.focus();
|
textareaRef.current?.focus();
|
||||||
|
|||||||
@@ -118,19 +118,6 @@ function SearchChat({
|
|||||||
const isWin10 = await platformAdapter.isWindows10();
|
const isWin10 = await platformAdapter.isWindows10();
|
||||||
|
|
||||||
setIsWin10(isWin10);
|
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(() => {
|
useEffect(() => {
|
||||||
|
|||||||
44
src/hooks/useTauriFocus.ts
Normal file
44
src/hooks/useTauriFocus.ts
Normal 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
|
import { useTauriFocus } from "./useTauriFocus";
|
||||||
|
|
||||||
export function useWindowEvents() {
|
export function useWindowEvents() {
|
||||||
const isPinned = useAppStore((state) => state.isPinned);
|
const isPinned = useAppStore((state) => state.isPinned);
|
||||||
const visible = useAppStore((state) => state.visible);
|
const visible = useAppStore((state) => state.visible);
|
||||||
const setBlurred = useAppStore((state) => state.setBlurred);
|
const setBlurred = useAppStore((state) => state.setBlurred);
|
||||||
|
|
||||||
useEffect(() => {
|
useTauriFocus({
|
||||||
const handleBlur = async () => {
|
onBlur() {
|
||||||
console.log("Window blurred");
|
|
||||||
if (isPinned || visible) {
|
if (isPinned || visible) {
|
||||||
return setBlurred(true);
|
return setBlurred(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await platformAdapter.hideWindow();
|
platformAdapter.hideWindow();
|
||||||
|
|
||||||
console.log("Hide Coco");
|
console.log("Hide Coco");
|
||||||
};
|
},
|
||||||
|
});
|
||||||
window.addEventListener("blur", handleBlur);
|
|
||||||
|
|
||||||
// Clean up event listeners on component unmount
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("blur", handleBlur);
|
|
||||||
};
|
|
||||||
}, [isPinned, visible]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,14 +84,14 @@ export const createTauriAdapter = (): TauriPlatformAdapter => {
|
|||||||
);
|
);
|
||||||
return requestScreenRecordingPermission();
|
return requestScreenRecordingPermission();
|
||||||
},
|
},
|
||||||
|
|
||||||
async checkMicrophonePermission() {
|
async checkMicrophonePermission() {
|
||||||
const { checkMicrophonePermission } = await import(
|
const { checkMicrophonePermission } = await import(
|
||||||
"tauri-plugin-macos-permissions-api"
|
"tauri-plugin-macos-permissions-api"
|
||||||
);
|
);
|
||||||
return checkMicrophonePermission();
|
return checkMicrophonePermission();
|
||||||
},
|
},
|
||||||
|
|
||||||
async requestMicrophonePermission() {
|
async requestMicrophonePermission() {
|
||||||
const { requestMicrophonePermission } = await import(
|
const { requestMicrophonePermission } = await import(
|
||||||
"tauri-plugin-macos-permissions-api"
|
"tauri-plugin-macos-permissions-api"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as commands from '@/commands';
|
import * as commands from "@/commands";
|
||||||
|
|
||||||
// Window operations
|
// Window operations
|
||||||
export const windowWrapper = {
|
export const windowWrapper = {
|
||||||
@@ -6,12 +6,14 @@ export const windowWrapper = {
|
|||||||
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
||||||
return getCurrentWindow();
|
return getCurrentWindow();
|
||||||
},
|
},
|
||||||
|
|
||||||
async getWebviewWindow() {
|
async getWebviewWindow() {
|
||||||
const { getCurrentWebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
const { getCurrentWebviewWindow } = await import(
|
||||||
|
"@tauri-apps/api/webviewWindow"
|
||||||
|
);
|
||||||
return getCurrentWebviewWindow();
|
return getCurrentWebviewWindow();
|
||||||
},
|
},
|
||||||
|
|
||||||
async setSize(width: number, height: number) {
|
async setSize(width: number, height: number) {
|
||||||
const { LogicalSize } = await import("@tauri-apps/api/dpi");
|
const { LogicalSize } = await import("@tauri-apps/api/dpi");
|
||||||
const window = await this.getWebviewWindow();
|
const window = await this.getWebviewWindow();
|
||||||
@@ -27,7 +29,7 @@ export const eventWrapper = {
|
|||||||
const { emit } = await import("@tauri-apps/api/event");
|
const { emit } = await import("@tauri-apps/api/event");
|
||||||
return emit(event, payload);
|
return emit(event, payload);
|
||||||
},
|
},
|
||||||
|
|
||||||
async listen(event: string, callback: Function) {
|
async listen(event: string, callback: Function) {
|
||||||
const { listen } = await import("@tauri-apps/api/event");
|
const { listen } = await import("@tauri-apps/api/event");
|
||||||
return listen(event, (e) => callback(e));
|
return listen(event, (e) => callback(e));
|
||||||
@@ -37,16 +39,22 @@ export const eventWrapper = {
|
|||||||
// System functions
|
// System functions
|
||||||
export const systemWrapper = {
|
export const systemWrapper = {
|
||||||
async checkScreenPermission() {
|
async checkScreenPermission() {
|
||||||
const { checkScreenRecordingPermission } = await import("tauri-plugin-macos-permissions-api");
|
const { checkScreenRecordingPermission } = await import(
|
||||||
|
"tauri-plugin-macos-permissions-api"
|
||||||
|
);
|
||||||
return checkScreenRecordingPermission();
|
return checkScreenRecordingPermission();
|
||||||
},
|
},
|
||||||
|
|
||||||
async captureScreen(id: number, type: 'monitor' | 'window') {
|
async captureScreen(id: number, type: "monitor" | "window") {
|
||||||
if (type === 'monitor') {
|
if (type === "monitor") {
|
||||||
const { getMonitorScreenshot } = await import("tauri-plugin-screenshots-api");
|
const { getMonitorScreenshot } = await import(
|
||||||
|
"tauri-plugin-screenshots-api"
|
||||||
|
);
|
||||||
return getMonitorScreenshot(id);
|
return getMonitorScreenshot(id);
|
||||||
} else {
|
} else {
|
||||||
const { getWindowScreenshot } = await import("tauri-plugin-screenshots-api");
|
const { getWindowScreenshot } = await import(
|
||||||
|
"tauri-plugin-screenshots-api"
|
||||||
|
);
|
||||||
return getWindowScreenshot(id);
|
return getWindowScreenshot(id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -60,5 +68,5 @@ export const commandWrapper = {
|
|||||||
return (commands as any)[commandName](...args);
|
return (commands as any)[commandName](...args);
|
||||||
}
|
}
|
||||||
throw new Error(`Command ${commandName} not found`);
|
throw new Error(`Command ${commandName} not found`);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user