From 3cd37cf458b9c4b5b244b82acc46bee1d6a81cb3 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:01:09 +0900 Subject: [PATCH 1/6] [Add] Implement clipboard functionality with support for Windows, Pyperclip, and Tkinter --- src-python/models/clipboard/clipboard.py | 221 +++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 src-python/models/clipboard/clipboard.py diff --git a/src-python/models/clipboard/clipboard.py b/src-python/models/clipboard/clipboard.py new file mode 100644 index 00000000..67b55192 --- /dev/null +++ b/src-python/models/clipboard/clipboard.py @@ -0,0 +1,221 @@ +import sys +import time +from subprocess import Popen, PIPE +import openvr + +try: + from utils import printLog +except ImportError: + def printLog(data, *args, **kwargs): + print(data, *args, **kwargs) + +# Windows-specific imports via ctypes will be used when focusing windows +if sys.platform == 'win32': + import ctypes + import ctypes.wintypes as wintypes + user32 = ctypes.WinDLL('user32', use_last_error=True) + + def find_windows_by_title_substring(substring: str): + HWND = wintypes.HWND + callback_type = ctypes.WINFUNCTYPE(wintypes.BOOL, HWND, wintypes.LPARAM) + found = [] + + def _cb(hwnd, lParam): + length = user32.GetWindowTextLengthW(hwnd) + if length == 0: + return True + buf = ctypes.create_unicode_buffer(length + 1) + # fill buffer with window title + user32.GetWindowTextW(hwnd, buf, length + 1) + title = buf.value + if substring.lower() in title.lower(): + found.append(hwnd) + return True + + user32.EnumWindows(callback_type(_cb), 0) + return found + + def find_windows_by_process_name(proc_name: str): + # iterate windows and match process id to name + HWND = wintypes.HWND + callback_type = ctypes.WINFUNCTYPE(wintypes.BOOL, HWND, wintypes.LPARAM) + found = [] + + def _cb(hwnd, lParam): + pid = wintypes.DWORD() + user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) + try: + import psutil + except Exception: + return True + try: + p = psutil.Process(pid.value) + if p.name().lower() == proc_name.lower(): + found.append(hwnd) + except Exception: + pass + return True + + user32.EnumWindows(callback_type(_cb), 0) + return found + + def focus_window(hwnd) -> bool: + try: + # Restore and set foreground + SW_RESTORE = 9 + user32.ShowWindow(hwnd, SW_RESTORE) + res = user32.SetForegroundWindow(hwnd) + return bool(res) + except Exception: + return False + +def copy_to_clipboard_windows(text: str) -> bool: + try: + p = Popen(['clip'], stdin=PIPE, shell=False) + # Write as UTF-16LE with BOM so Windows clipboard receives correct Unicode + bom_utf16le = b"\xff\xfe" + p.communicate(bom_utf16le + text.encode('utf-16le')) + return True + except Exception: + return False + +def copy_to_clipboard_pyperclip(text: str) -> bool: + try: + import pyperclip + pyperclip.copy(text) + return True + except Exception: + return False + +def copy_to_clipboard_tk(text: str) -> bool: + try: + import tkinter as tk + r = tk.Tk() + r.withdraw() + r.clipboard_clear() + r.clipboard_append(text) + r.update() + r.destroy() + return True + except Exception: + return False + +def copy_to_clipboard(text: str) -> bool: + if sys.platform == 'win32': + if copy_to_clipboard_windows(text): + return True + if copy_to_clipboard_pyperclip(text): + return True + if copy_to_clipboard_tk(text): + return True + return False + +def paste_via_pyautogui(countdown: int = 0) -> bool: + try: + import pyautogui + except Exception: + printLog('pyautogui not installed. Install with: pip install pyautogui') + return False + + printLog(f'Give focus to the target input. Pasting in {countdown} seconds...') + for i in range(countdown, 0, -1): + print(i, end=' ', flush=True) + time.sleep(1) + printLog('\nSending Ctrl+V...') + try: + # pyautogui.hotkey is a safe cross-platform way to send keys + pyautogui.hotkey('ctrl', 'v') + return True + except Exception as e: + printLog(f'pyautogui failed to send hotkey: {e}') + return False + + +class Clipboard: + def __init__(self, vr_mode: bool = True): + if not vr_mode: + self.app_name = None + else: + openvr.init(openvr.VRApplication_Background) + apps = openvr.VRApplications() + + app_count = apps.getApplicationCount() + running_apps = [] + + for i in range(app_count): + key = apps.getApplicationKeyByIndex(i) + name = apps.getApplicationPropertyString( + key, + openvr.VRApplicationProperty_Name_String + ) + running_apps.append((key, name)) + + self.app_name = None + for key, name in running_apps: + if key.startswith("steam.app"): + self.app_name = name + break + openvr.shutdown() + + def copy(self, message: str) -> bool: + """Copy `message` to clipboard. + + Args: + message: Text to copy. + + Returns: + True if copy succeeded, False otherwise. + """ + return copy_to_clipboard(message) + + def paste(self, window_name: str|None = None, countdown: int = 0) -> bool: + """Focus a window identified by `window_name`, then paste via Ctrl+V. + + Args: + window_name: Window title substring or process name to find and focus (Windows only). Required. + countdown: Seconds to wait before sending paste key. + + Returns: + True if paste command was sent, False otherwise. + """ + + if window_name is None: + window_name = self.app_name + + # focus target window (Windows only) — window_name is required + focused = False + if sys.platform == 'win32': + if not window_name: + printLog('paste: window_name is required on Windows') + return False + + # try title substring match first + wins = find_windows_by_title_substring(window_name) + for hwnd in wins: + if focus_window(hwnd): + focused = True + break + + # if not found by title, try treating window_name as process name + if not focused: + wins = find_windows_by_process_name(window_name) + for hwnd in wins: + if focus_window(hwnd): + focused = True + break + + if not focused: + printLog(f"copy_and_paste: no window found matching '{window_name}'") + return False + + # small delay to allow focus to settle + time.sleep(0.2) + + # paste + pasted = paste_via_pyautogui(countdown) + return bool(pasted) + +if __name__ == '__main__': + clipboard = Clipboard() + clipboard.copy("Sample text to copy to clipboard.") + clipboard.paste(window_name=None, countdown=0) \ No newline at end of file From 4eac9654a72de159ed5dcf26a86972a13d124986 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:25:28 +0900 Subject: [PATCH 2/6] =?UTF-8?q?clipboard=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E3=83=91=E3=82=A4=E3=83=97=E3=83=A9=E3=82=A4=E3=83=B3=E3=82=92?= =?UTF-8?q?=E6=8E=A5=E7=B6=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-python/config.py | 6 +++++ src-python/controller.py | 12 +++++++++ src-python/model.py | 25 ++++++++++++++++++ src-python/models/clipboard/clipboard.py | 32 ++++++++++++++---------- 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src-python/config.py b/src-python/config.py index 3a3e7c3b..d5e600ac 100644 --- a/src-python/config.py +++ b/src-python/config.py @@ -734,6 +734,10 @@ class Config: SELECTED_TRANSLATION_COMPUTE_DEVICE = ValidatedProperty('SELECTED_TRANSLATION_COMPUTE_DEVICE', _compute_device_validator) SELECTED_TRANSCRIPTION_COMPUTE_DEVICE = ValidatedProperty('SELECTED_TRANSCRIPTION_COMPUTE_DEVICE', _compute_device_validator) + # -- Clipboard control --- + ENABLE_COPY_TO_CLIPBOARD = ManagedProperty('ENABLE_COPY_TO_CLIPBOARD', type_=bool) + ENABLE_PASTE_FROM_CLIPBOARD = ManagedProperty('ENABLE_PASTE_FROM_CLIPBOARD', type_=bool) + def init_config(self): # Read Only self._VERSION = "3.3.1" @@ -997,6 +1001,8 @@ class Config: self._WEBSOCKET_SERVER = False self._WEBSOCKET_HOST = "127.0.0.1" self._WEBSOCKET_PORT = 2231 + self._ENABLE_COPY_TO_CLIPBOARD = False + self._ENABLE_PASTE_FROM_CLIPBOARD = False def load_config(self): if os_path.isfile(self.PATH_CONFIG) is not False: diff --git a/src-python/controller.py b/src-python/controller.py index 06df12bf..09c3585f 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -396,6 +396,12 @@ class Controller: ) model.updateOverlayLargeLog(overlay_image) + if config.ENABLE_COPY_TO_CLIPBOARD is True: + clipboard_message = self.messageFormatter("SEND", translation, message) + model.setCopyToClipboard(clipboard_message) + if config.ENABLE_PASTE_FROM_CLIPBOARD is True: + model.setPasteFromClipboard() + if model.checkWebSocketServerAlive() is True: model.websocketSendMessage( { @@ -728,6 +734,12 @@ class Controller: ) model.updateOverlayLargeLog(overlay_image) + if config.ENABLE_COPY_TO_CLIPBOARD is True: + clipboard_message = self.messageFormatter("SEND", translation, message) + model.setCopyToClipboard(clipboard_message) + if config.ENABLE_PASTE_FROM_CLIPBOARD is True: + model.setPasteFromClipboard() + if model.checkWebSocketServerAlive() is True: model.websocketSendMessage( { diff --git a/src-python/model.py b/src-python/model.py index 58f947dc..1a74ee62 100644 --- a/src-python/model.py +++ b/src-python/model.py @@ -32,6 +32,7 @@ from models.overlay.overlay import Overlay from models.overlay.overlay_image import OverlayImage from models.watchdog.watchdog import Watchdog from models.websocket.websocket_server import WebSocketServer +from models.clipboard.clipboard import Clipboard from utils import errorLogging, setupLogger class threadFnc(Thread): @@ -138,6 +139,7 @@ class Model: # default no-op callbacks for energy check functions self.check_mic_energy_fnc: Callable[[float], None] = lambda v: None self.check_speaker_energy_fnc: Callable[[float], None] = lambda v: None + self.clipboard = Clipboard() self._inited = True def ensure_initialized(self) -> None: @@ -1201,4 +1203,27 @@ class Model: errorLogging() return False + def setCopyToClipboard(self, text:str) -> bool: + self.ensure_initialized() + try: + if isinstance(self.clipboard, Clipboard): + self.clipboard.copy(text) + return True + else: + return False + except Exception: + errorLogging() + return False + + def setPasteFromClipboard(self) -> bool: + self.ensure_initialized() + try: + if isinstance(self.clipboard, Clipboard): + return self.clipboard.paste() + else: + return False + except Exception: + errorLogging() + return False + model = Model() \ No newline at end of file diff --git a/src-python/models/clipboard/clipboard.py b/src-python/models/clipboard/clipboard.py index 67b55192..c284edc6 100644 --- a/src-python/models/clipboard/clipboard.py +++ b/src-python/models/clipboard/clipboard.py @@ -1,6 +1,8 @@ import sys import time +import os from subprocess import Popen, PIPE +from psutil import process_iter import openvr try: @@ -9,6 +11,10 @@ except ImportError: def printLog(data, *args, **kwargs): print(data, *args, **kwargs) +def checkSteamvrRunning() -> bool: + _proc_name = "vrmonitor.exe" if os.name == "nt" else "vrmonitor" + return _proc_name in (p.name() for p in process_iter()) + # Windows-specific imports via ctypes will be used when focusing windows if sys.platform == 'win32': import ctypes @@ -117,11 +123,10 @@ def paste_via_pyautogui(countdown: int = 0) -> bool: printLog('pyautogui not installed. Install with: pip install pyautogui') return False - printLog(f'Give focus to the target input. Pasting in {countdown} seconds...') for i in range(countdown, 0, -1): print(i, end=' ', flush=True) time.sleep(1) - printLog('\nSending Ctrl+V...') + try: # pyautogui.hotkey is a safe cross-platform way to send keys pyautogui.hotkey('ctrl', 'v') @@ -132,8 +137,8 @@ def paste_via_pyautogui(countdown: int = 0) -> bool: class Clipboard: - def __init__(self, vr_mode: bool = True): - if not vr_mode: + def __init__(self): + if not checkSteamvrRunning(): self.app_name = None else: openvr.init(openvr.VRApplication_Background) @@ -156,6 +161,7 @@ class Clipboard: self.app_name = name break openvr.shutdown() + printLog(f"Clipboard initialized. SteamVR App Name: {self.app_name}") def copy(self, message: str) -> bool: """Copy `message` to clipboard. @@ -179,18 +185,17 @@ class Clipboard: True if paste command was sent, False otherwise. """ - if window_name is None: - window_name = self.app_name + window_name = window_name if window_name is not None else self.app_name - # focus target window (Windows only) — window_name is required - focused = False - if sys.platform == 'win32': - if not window_name: - printLog('paste: window_name is required on Windows') - return False + # If window_name is provided, attempt to focus it (Windows only). + # If window_name is None, skip focusing and paste into the currently focused window. + if window_name is not None and sys.platform == 'win32': + printLog(f"paste: attempting to focus window matching '{window_name}'") + focused = False # try title substring match first wins = find_windows_by_title_substring(window_name) + printLog(f"paste: found {wins} windows matching title substring '{window_name}'") for hwnd in wins: if focus_window(hwnd): focused = True @@ -199,6 +204,7 @@ class Clipboard: # if not found by title, try treating window_name as process name if not focused: wins = find_windows_by_process_name(window_name) + printLog(f"paste: found {wins} windows matching process name '{window_name}'") for hwnd in wins: if focus_window(hwnd): focused = True @@ -218,4 +224,4 @@ class Clipboard: if __name__ == '__main__': clipboard = Clipboard() clipboard.copy("Sample text to copy to clipboard.") - clipboard.paste(window_name=None, countdown=0) \ No newline at end of file + clipboard.paste(window_name=None, countdown=3) \ No newline at end of file From 86a6de75aec6367d9129166d027b4d0cab81927b Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:38:08 +0900 Subject: [PATCH 3/6] [Add] Implement clipboard control functions in Controller class --- src-python/controller.py | 33 +++++++++++++++++++++++++++++++++ src-python/mainloop.py | 8 ++++++++ 2 files changed, 41 insertions(+) diff --git a/src-python/controller.py b/src-python/controller.py index 09c3585f..360296ec 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -2879,6 +2879,39 @@ class Controller: model.stopWebSocketServer() return {"status":200, "result":config.WEBSOCKET_SERVER} + # Clipboard control + @staticmethod + def getCopyToClipboard(*args, **kwargs) -> dict: + return {"status":200, "result":config.ENABLE_COPY_TO_CLIPBOARD} + + @staticmethod + def setEnableCopyToClipboard(*args, **kwargs) -> dict: + if config.ENABLE_COPY_TO_CLIPBOARD is False: + config.ENABLE_COPY_TO_CLIPBOARD = True + return {"status":200, "result":config.ENABLE_COPY_TO_CLIPBOARD} + + @staticmethod + def setDisableCopyToClipboard(*args, **kwargs) -> dict: + if config.ENABLE_COPY_TO_CLIPBOARD is True: + config.ENABLE_COPY_TO_CLIPBOARD = False + return {"status":200, "result":config.ENABLE_COPY_TO_CLIPBOARD} + + @staticmethod + def getPasteFromClipboard(*args, **kwargs) -> dict: + return {"status":200, "result":config.ENABLE_PASTE_FROM_CLIPBOARD} + + @staticmethod + def setEnablePasteFromClipboard(*args, **kwargs) -> dict: + if config.ENABLE_PASTE_FROM_CLIPBOARD is False: + config.ENABLE_PASTE_FROM_CLIPBOARD = True + return {"status":200, "result":config.ENABLE_PASTE_FROM_CLIPBOARD} + + @staticmethod + def setDisablePasteFromClipboard(*args, **kwargs) -> dict: + if config.ENABLE_PASTE_FROM_CLIPBOARD is True: + config.ENABLE_PASTE_FROM_CLIPBOARD = False + return {"status":200, "result":config.ENABLE_PASTE_FROM_CLIPBOARD} + def initializationProgress(self, progress): self.run(200, self.run_mapping["initialization_progress"], progress) diff --git a/src-python/mainloop.py b/src-python/mainloop.py index 58a760a5..6fa65fc9 100644 --- a/src-python/mainloop.py +++ b/src-python/mainloop.py @@ -382,6 +382,14 @@ mapping = { "/set/enable/websocket_server": {"status": True, "variable":controller.setEnableWebSocketServer}, "/set/disable/websocket_server": {"status": True, "variable":controller.setDisableWebSocketServer}, + # Clipboard Settings + # "/get/data/copy_to_clipboard": {"status": True, "variable":controller.getCopyToClipboard}, + # "/set/enable/copy_to_clipboard": {"status": True, "variable":controller.setEnableCopyToClipboard}, + # "/set/disable/copy_to_clipboard": {"status": True, "variable":controller.setDisableCopyToClipboard}, + # "/get/data/paste_from_clipboard": {"status": True, "variable":controller.getPasteFromClipboard}, + # "/set/enable/paste_from_clipboard": {"status": True, "variable":controller.setEnablePasteFromClipboard}, + # "/set/disable/paste_from_clipboard": {"status": True, "variable":controller.setDisablePasteFromClipboard}, + # Advanced Settings "/get/data/osc_ip_address": {"status": True, "variable":controller.getOscIpAddress}, "/set/data/osc_ip_address": {"status": True, "variable":controller.setOscIpAddress}, From 7336d014692ac924217b930665859d888a04e579 Mon Sep 17 00:00:00 2001 From: misyaguziya <53165965+misyaguziya@users.noreply.github.com> Date: Fri, 9 Jan 2026 19:35:55 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[Update]=20=E3=82=AF=E3=83=AA=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=83=9C=E3=83=BC=E3=83=89=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E7=B5=B1=E5=90=88:=20=E3=82=B3=E3=83=94=E3=83=BC=E3=81=8A?= =?UTF-8?q?=E3=82=88=E3=81=B3=E3=83=9A=E3=83=BC=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=82=92=E4=B8=80=E3=81=A4=E3=81=AE=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=91=E3=83=86=E3=82=A3=E3=81=AB=E3=81=BE=E3=81=A8?= =?UTF-8?q?=E3=82=81=E3=80=81=E9=96=A2=E9=80=A3=E3=81=99=E3=82=8B=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-python/config.py | 8 +++---- src-python/controller.py | 46 ++++++++++++---------------------------- src-python/mainloop.py | 9 +++----- 3 files changed, 21 insertions(+), 42 deletions(-) diff --git a/src-python/config.py b/src-python/config.py index e7420e36..f9602a67 100644 --- a/src-python/config.py +++ b/src-python/config.py @@ -757,8 +757,7 @@ class Config: SELECTED_TRANSCRIPTION_COMPUTE_DEVICE = ValidatedProperty('SELECTED_TRANSCRIPTION_COMPUTE_DEVICE', _compute_device_validator) # -- Clipboard control --- - ENABLE_COPY_TO_CLIPBOARD = ManagedProperty('ENABLE_COPY_TO_CLIPBOARD', type_=bool) - ENABLE_PASTE_FROM_CLIPBOARD = ManagedProperty('ENABLE_PASTE_FROM_CLIPBOARD', type_=bool) + ENABLE_CLIPBOARD = ManagedProperty('ENABLE_CLIPBOARD', type_=bool) def init_config(self): # Read Only @@ -1026,10 +1025,10 @@ class Config: self._WEBSOCKET_SERVER = False self._WEBSOCKET_HOST = "127.0.0.1" self._WEBSOCKET_PORT = 2231 - self._ENABLE_COPY_TO_CLIPBOARD = False - self._ENABLE_PASTE_FROM_CLIPBOARD = False + self._ENABLE_CLIPBOARD = False def load_config(self): + self._config_data = {} if os_path.isfile(self.PATH_CONFIG) is not False: with open(self.PATH_CONFIG, 'r', encoding="utf-8") as fp: if fp.readable() and fp.seek(0, 2) > 0: @@ -1054,6 +1053,7 @@ class Config: continue except Exception: errorLogging() + self.saveConfigToFile() def revalidate_selected_models(self): diff --git a/src-python/controller.py b/src-python/controller.py index 535653b6..d233c440 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -406,11 +406,10 @@ class Controller: ) model.updateOverlayLargeLog(overlay_image) - if config.ENABLE_COPY_TO_CLIPBOARD is True: + if config.ENABLE_CLIPBOARD is True: clipboard_message = self.messageFormatter("SEND", translation, message) model.setCopyToClipboard(clipboard_message) - if config.ENABLE_PASTE_FROM_CLIPBOARD is True: - model.setPasteFromClipboard() + model.setPasteFromClipboard() if model.checkWebSocketServerAlive() is True: model.websocketSendMessage( @@ -758,11 +757,10 @@ class Controller: ) model.updateOverlayLargeLog(overlay_image) - if config.ENABLE_COPY_TO_CLIPBOARD is True: + if config.ENABLE_CLIPBOARD is True: clipboard_message = self.messageFormatter("SEND", translation, message) model.setCopyToClipboard(clipboard_message) - if config.ENABLE_PASTE_FROM_CLIPBOARD is True: - model.setPasteFromClipboard() + model.setPasteFromClipboard() if model.checkWebSocketServerAlive() is True: model.websocketSendMessage( @@ -3042,36 +3040,20 @@ class Controller: # Clipboard control @staticmethod - def getCopyToClipboard(*args, **kwargs) -> dict: - return {"status":200, "result":config.ENABLE_COPY_TO_CLIPBOARD} + def getClipboard(*args, **kwargs) -> dict: + return {"status":200, "result":config.ENABLE_CLIPBOARD} @staticmethod - def setEnableCopyToClipboard(*args, **kwargs) -> dict: - if config.ENABLE_COPY_TO_CLIPBOARD is False: - config.ENABLE_COPY_TO_CLIPBOARD = True - return {"status":200, "result":config.ENABLE_COPY_TO_CLIPBOARD} + def setEnableClipboard(*args, **kwargs) -> dict: + if config.ENABLE_CLIPBOARD is False: + config.ENABLE_CLIPBOARD = True + return {"status":200, "result":config.ENABLE_CLIPBOARD} @staticmethod - def setDisableCopyToClipboard(*args, **kwargs) -> dict: - if config.ENABLE_COPY_TO_CLIPBOARD is True: - config.ENABLE_COPY_TO_CLIPBOARD = False - return {"status":200, "result":config.ENABLE_COPY_TO_CLIPBOARD} - - @staticmethod - def getPasteFromClipboard(*args, **kwargs) -> dict: - return {"status":200, "result":config.ENABLE_PASTE_FROM_CLIPBOARD} - - @staticmethod - def setEnablePasteFromClipboard(*args, **kwargs) -> dict: - if config.ENABLE_PASTE_FROM_CLIPBOARD is False: - config.ENABLE_PASTE_FROM_CLIPBOARD = True - return {"status":200, "result":config.ENABLE_PASTE_FROM_CLIPBOARD} - - @staticmethod - def setDisablePasteFromClipboard(*args, **kwargs) -> dict: - if config.ENABLE_PASTE_FROM_CLIPBOARD is True: - config.ENABLE_PASTE_FROM_CLIPBOARD = False - return {"status":200, "result":config.ENABLE_PASTE_FROM_CLIPBOARD} + def setDisableClipboard(*args, **kwargs) -> dict: + if config.ENABLE_CLIPBOARD is True: + config.ENABLE_CLIPBOARD = False + return {"status":200, "result":config.ENABLE_CLIPBOARD} def initializationProgress(self, progress): self.run(200, self.run_mapping["initialization_progress"], progress) diff --git a/src-python/mainloop.py b/src-python/mainloop.py index f922a1f1..856f727b 100644 --- a/src-python/mainloop.py +++ b/src-python/mainloop.py @@ -403,12 +403,9 @@ mapping = { "/set/disable/websocket_server": {"status": True, "variable":controller.setDisableWebSocketServer}, # Clipboard Settings - # "/get/data/copy_to_clipboard": {"status": True, "variable":controller.getCopyToClipboard}, - # "/set/enable/copy_to_clipboard": {"status": True, "variable":controller.setEnableCopyToClipboard}, - # "/set/disable/copy_to_clipboard": {"status": True, "variable":controller.setDisableCopyToClipboard}, - # "/get/data/paste_from_clipboard": {"status": True, "variable":controller.getPasteFromClipboard}, - # "/set/enable/paste_from_clipboard": {"status": True, "variable":controller.setEnablePasteFromClipboard}, - # "/set/disable/paste_from_clipboard": {"status": True, "variable":controller.setDisablePasteFromClipboard}, + "/get/data/clipboard": {"status": True, "variable":controller.getClipboard}, + "/set/enable/clipboard": {"status": True, "variable":controller.setEnableClipboard}, + "/set/disable/clipboard": {"status": True, "variable":controller.setDisableClipboard}, # Advanced Settings "/get/data/osc_ip_address": {"status": True, "variable":controller.getOscIpAddress}, From 641565685397fe70242a4a8656131f1f8b4fea6f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:50:44 +0900 Subject: [PATCH 5/6] [Update] UI: Add Clipboard Feature as Voice Typing Mode settings. --- locales/en.yml | 3 +++ locales/ja.yml | 3 +++ locales/ko.yml | 3 +++ locales/zh-Hans.yml | 3 +++ locales/zh-Hant.yml | 3 +++ .../configs/config_page_setter/ui_config_setter.js | 8 ++++++++ .../setting_section/setting_box/vr/Vr.jsx | 13 ++++++++++++- 7 files changed, 35 insertions(+), 1 deletion(-) diff --git a/locales/en.yml b/locales/en.yml index 1b08a31b..ab281794 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -264,6 +264,9 @@ config_page: right_hand: "Right Hand" overlay_show_only_translated_messages: label: "Show Only Translated Messages" + voice_typing_mode: + label: "Voice Typing Mode" + desc: "Automatically pastes transcribed text into the active input field.\n*Utilizes standard Windows clipboard functionality." others: section_label_sounds: "Sounds" diff --git a/locales/ja.yml b/locales/ja.yml index 51ae19f4..9fa47b64 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -264,6 +264,9 @@ config_page: right_hand: "右手" overlay_show_only_translated_messages: label: "翻訳後のメッセージのみ表示する" + voice_typing_mode: + label: "音声タイピングモード" + desc: "文字起こしされたテキストを、アクティブな入力欄へと自動的に貼り付けます。\n※Windows標準のクリップボード機能を利用しています。" others: section_label_sounds: "サウンド" diff --git a/locales/ko.yml b/locales/ko.yml index bc07d6c3..b4ccc401 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -264,6 +264,9 @@ config_page: right_hand: "오른손" overlay_show_only_translated_messages: label: "번역된 메시지만 표시" + voice_typing_mode: + label: + desc: others: section_label_sounds: "사운드" diff --git a/locales/zh-Hans.yml b/locales/zh-Hans.yml index c3403912..dd33585b 100644 --- a/locales/zh-Hans.yml +++ b/locales/zh-Hans.yml @@ -264,6 +264,9 @@ config_page: right_hand: "右手" overlay_show_only_translated_messages: label: "仅显示翻译后的消息" + voice_typing_mode: + label: + desc: others: section_label_sounds: "声音" diff --git a/locales/zh-Hant.yml b/locales/zh-Hant.yml index 8362972b..db73f559 100644 --- a/locales/zh-Hant.yml +++ b/locales/zh-Hant.yml @@ -264,6 +264,9 @@ config_page: right_hand: "右手" overlay_show_only_translated_messages: label: "僅顯示翻譯後的訊息" + voice_typing_mode: + label: + desc: others: section_label_sounds: "音效" diff --git a/src-ui/logics/configs/config_page_setter/ui_config_setter.js b/src-ui/logics/configs/config_page_setter/ui_config_setter.js index 0ffb7a89..ae69cc6e 100644 --- a/src-ui/logics/configs/config_page_setter/ui_config_setter.js +++ b/src-ui/logics/configs/config_page_setter/ui_config_setter.js @@ -611,6 +611,14 @@ export const SETTINGS_ARRAY = [ logics_template_id: "toggle_enable_disable", base_endpoint_name: "overlay_show_only_translated_messages", }, + { + Category: "Vr", + Base_Name: "VoiceTypingMode", + default_value: false, + ui_template_id: "toggle", + logics_template_id: "toggle_enable_disable", + base_endpoint_name: "clipboard", + }, // Others { diff --git a/src-ui/views/app/config_page/setting_section/setting_box/vr/Vr.jsx b/src-ui/views/app/config_page/setting_section/setting_box/vr/Vr.jsx index f8e6173c..dd746045 100644 --- a/src-ui/views/app/config_page/setting_section/setting_box/vr/Vr.jsx +++ b/src-ui/views/app/config_page/setting_section/setting_box/vr/Vr.jsx @@ -524,7 +524,12 @@ const OtherControls = ({settings, onchangeFunction, ui_configs}) => { const CommonSettingsContainer = () => { const { t } = useI18n(); - const { currentOverlayShowOnlyTranslatedMessages, toggleOverlayShowOnlyTranslatedMessages } = useVr(); + const { + currentOverlayShowOnlyTranslatedMessages, + toggleOverlayShowOnlyTranslatedMessages, + currentVoiceTypingMode, + toggleVoiceTypingMode, + } = useVr(); return (