[Update/TMP] Hotkey Alt+Y will active VRCT when VRChat is the active window.

This commit is contained in:
Sakamoto Shiina
2025-01-05 14:10:48 +09:00
parent ffa1319b5b
commit 47126bf5c8
10 changed files with 830 additions and 426 deletions

1114
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,11 +11,13 @@ edition = "2021"
tauri-build = { version = "1", features = [] } tauri-build = { version = "1", features = [] }
[dependencies] [dependencies]
tauri = { version = "1", features = [ "window-set-size", "window-set-position", "window-unmaximize", "window-close", "window-maximize", "window-minimize", "window-unminimize", "window-start-dragging", "window-set-decorations", "window-set-always-on-top", "shell-sidecar", "shell-open", "devtools"] } tauri = { version = "1", features = [ "window-hide", "window-set-focus", "global-shortcut-all", "window-set-size", "window-set-position", "window-unmaximize", "window-close", "window-maximize", "window-minimize", "window-unminimize", "window-start-dragging", "window-set-decorations", "window-set-always-on-top", "shell-sidecar", "shell-open", "devtools"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
font-kit = "0.14.2" font-kit = "0.14.2"
window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" } window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" }
windows = { version = "0.38", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging", "Win32_System_Threading"] }
[features] [features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!

View File

@@ -1,9 +1,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// use tauri::command;
use tauri::Manager; use tauri::Manager;
use window_shadows::set_shadow; use window_shadows::set_shadow;
use windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, GetWindowTextW};
use std::collections::HashSet;
fn main() { fn main() {
tauri::Builder::default() tauri::Builder::default()
.setup(|app| { .setup(|app| {
@@ -13,18 +12,16 @@ fn main() {
{ main_window.open_devtools(); } { main_window.open_devtools(); }
#[cfg(any(windows, target_os = "macos"))] #[cfg(any(windows, target_os = "macos"))]
set_shadow(main_window, true).unwrap(); set_shadow(main_window, true).unwrap()
Ok(()) Ok(())
}) })
.invoke_handler(tauri::generate_handler![get_font_list]) .invoke_handler(tauri::generate_handler![get_font_list, get_active_window_title])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }
use font_kit::{source::SystemSource}; use font_kit::{source::SystemSource};
use std::collections::HashSet;
#[tauri::command] #[tauri::command]
async fn get_font_list() -> Vec<String> { async fn get_font_list() -> Vec<String> {
@@ -41,3 +38,27 @@ async fn get_font_list() -> Vec<String> {
font_families.into_iter().collect() font_families.into_iter().collect()
} }
use windows::Win32::Foundation::HWND;
#[tauri::command]
fn get_active_window_title() -> String {
unsafe {
// Get the handle of the foreground (active) window
let hwnd = GetForegroundWindow();
if hwnd == HWND(0) { // `HWND(0)` は null ハンドルを表します
return "No active window".to_string();
}
// Create a buffer to hold the window title
let mut buffer: [u16; 512] = [0; 512];
// Get the window title
let length = GetWindowTextW(hwnd, &mut buffer);
if length > 0 {
// Convert the result to a Rust string
return String::from_utf16_lossy(&buffer[..length as usize]);
}
}
"Failed to retrieve window title".to_string()
}

View File

@@ -15,8 +15,10 @@
"window": { "window": {
"all": false, "all": false,
"setAlwaysOnTop": true, "setAlwaysOnTop": true,
"setFocus": true,
"setDecorations": true, "setDecorations": true,
"close": true, "close": true,
"hide": true,
"setPosition": true, "setPosition": true,
"setSize": true, "setSize": true,
"maximize": true, "maximize": true,
@@ -25,6 +27,9 @@
"unminimize": true, "unminimize": true,
"startDragging": true "startDragging": true
}, },
"globalShortcut": {
"all": true
},
"shell": { "shell": {
"all": false, "all": false,
"open": true, "open": true,

View File

@@ -4,10 +4,9 @@ import {
useWindow, useWindow,
} from "@logics_common"; } from "@logics_common";
// import React from "react";
import { import {
KeyEventController, KeyEventController,
GlobalHotKeyController,
StartPythonController, StartPythonController,
UiLanguageController, UiLanguageController,
ConfigPageCloseTriggerController, ConfigPageCloseTriggerController,
@@ -34,6 +33,7 @@ export const App = () => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<KeyEventController /> <KeyEventController />
<GlobalHotKeyController />
<StartPythonController /> <StartPythonController />
<UiLanguageController /> <UiLanguageController />
<ConfigPageCloseTriggerController /> <ConfigPageCloseTriggerController />

View File

@@ -0,0 +1,43 @@
import { appWindow } from "@tauri-apps/api/window";
import { register, unregisterAll, isRegistered } from "@tauri-apps/api/globalShortcut";
import { invoke } from "@tauri-apps/api/tauri";
import { useEffect, useRef } from "react";
import { store } from "@store";
export const GlobalHotKeyController = () => {
const is_initialized = useRef(false);
useEffect(() => {
if (is_initialized.current) return;
const registerShortcuts = async () => {
const shortcut = "Alt+Y";
const is_already_registered = await isRegistered(shortcut);
if (is_already_registered) return;
await register(shortcut, async () => {
const activeWindowTitle = await invoke("get_active_window_title");
if (activeWindowTitle.includes("VRChat")) {
console.log("Shortcut triggered, setFocus");
appWindow.unminimize();
await appWindow.setFocus();
store.text_area_ref.current?.focus();
} else {
console.log("Active window is not VRChat.");
}
});
};
registerShortcuts();
is_initialized.current = true;
return () => {
unregisterAll().catch((error) => {
console.error("Failed to unregister shortcuts:", error);
});
};
}, []);
return null;
};

View File

@@ -3,8 +3,11 @@ import { useEffect } from "react";
export const KeyEventController = () => { export const KeyEventController = () => {
useEffect(() => { useEffect(() => {
const handleKeydown = (event) => { const handleKeydown = (event) => {
if (event.key === "F5" || (event.ctrlKey && event.key === "r") || if (
(event.metaKey && event.key === "r")) { event.key === "F5" ||
(event.ctrlKey && event.key === "r") ||
(event.metaKey && event.key === "r")
) {
event.preventDefault(); event.preventDefault();
} }
}; };

View File

@@ -1,4 +1,5 @@
export { KeyEventController } from "./KeyEventController"; export { KeyEventController } from "./KeyEventController";
export { GlobalHotKeyController } from "./GlobalHotKeyController";
export { StartPythonController } from "./StartPythonController"; export { StartPythonController } from "./StartPythonController";
export { UiLanguageController } from "./UiLanguageController"; export { UiLanguageController } from "./UiLanguageController";
export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController"; export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController";

View File

@@ -1,9 +1,11 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useLayoutEffect, useRef } from "react";
import styles from "./MessageInputBox.module.scss"; import styles from "./MessageInputBox.module.scss";
import SendMessageSvg from "@images/send_message.svg?react"; import SendMessageSvg from "@images/send_message.svg?react";
import { useMessage } from "@logics_common"; import { useMessage } from "@logics_common";
import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs"; import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs";
import { useMessageLogScroll } from "@logics_main"; import { useMessageLogScroll } from "@logics_main";
import { store } from "@store";
import { appWindow } from "@tauri-apps/api/window";
export const MessageInputBox = () => { export const MessageInputBox = () => {
const [message_history, setMessageHistory] = useState([]); const [message_history, setMessageHistory] = useState([]);
@@ -22,6 +24,12 @@ export const MessageInputBox = () => {
const { scrollToBottom } = useMessageLogScroll(); const { scrollToBottom } = useMessageLogScroll();
const log_box_ref = useRef(null);
useLayoutEffect(() => {
store.text_area_ref = log_box_ref;
}, []);
useEffect(() => { useEffect(() => {
if (currentMessageLogs.data) { if (currentMessageLogs.data) {
const sentMessages = currentMessageLogs.data const sentMessages = currentMessageLogs.data
@@ -33,6 +41,7 @@ export const MessageInputBox = () => {
const onSubmitFunction = (e) => { const onSubmitFunction = (e) => {
e.preventDefault(); e.preventDefault();
appWindow.minimize();
if (!currentMessageInputValue.data.trim()) return updateMessageInputValue(""); if (!currentMessageInputValue.data.trim()) return updateMessageInputValue("");
@@ -90,6 +99,7 @@ export const MessageInputBox = () => {
<div className={styles.container}> <div className={styles.container}>
<div className={styles.message_box_wrapper}> <div className={styles.message_box_wrapper}>
<textarea <textarea
ref={log_box_ref}
className={styles.message_box_input_area} className={styles.message_box_input_area}
onChange={onChangeFunction} onChange={onChangeFunction}
onBlur={stopTyping} onBlur={stopTyping}

View File

@@ -18,6 +18,7 @@ export const store = {
config_page: null, config_page: null,
setting_box_scroll_container: null, setting_box_scroll_container: null,
log_box_ref: null, log_box_ref: null,
text_area_ref: null,
is_applied_init_message_box_height: false, is_applied_init_message_box_height: false,
}; };