Merge branch 'copy_and_paste' into develop
# Conflicts: # src-python/config.py # src-python/model.py
This commit is contained in:
@@ -264,6 +264,9 @@ config_page:
|
|||||||
right_hand: "Right Hand"
|
right_hand: "Right Hand"
|
||||||
overlay_show_only_translated_messages:
|
overlay_show_only_translated_messages:
|
||||||
label: "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:
|
others:
|
||||||
section_label_sounds: "Sounds"
|
section_label_sounds: "Sounds"
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ config_page:
|
|||||||
right_hand: "右手"
|
right_hand: "右手"
|
||||||
overlay_show_only_translated_messages:
|
overlay_show_only_translated_messages:
|
||||||
label: "翻訳後のメッセージのみ表示する"
|
label: "翻訳後のメッセージのみ表示する"
|
||||||
|
voice_typing_mode:
|
||||||
|
label: "音声タイピングモード"
|
||||||
|
desc: "文字起こしされたテキストを、アクティブな入力欄へと自動的に貼り付けます。\n※Windows標準のクリップボード機能を利用しています。"
|
||||||
|
|
||||||
others:
|
others:
|
||||||
section_label_sounds: "サウンド"
|
section_label_sounds: "サウンド"
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ config_page:
|
|||||||
right_hand: "오른손"
|
right_hand: "오른손"
|
||||||
overlay_show_only_translated_messages:
|
overlay_show_only_translated_messages:
|
||||||
label: "번역된 메시지만 표시"
|
label: "번역된 메시지만 표시"
|
||||||
|
voice_typing_mode:
|
||||||
|
label:
|
||||||
|
desc:
|
||||||
|
|
||||||
others:
|
others:
|
||||||
section_label_sounds: "사운드"
|
section_label_sounds: "사운드"
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ config_page:
|
|||||||
right_hand: "右手"
|
right_hand: "右手"
|
||||||
overlay_show_only_translated_messages:
|
overlay_show_only_translated_messages:
|
||||||
label: "仅显示翻译后的消息"
|
label: "仅显示翻译后的消息"
|
||||||
|
voice_typing_mode:
|
||||||
|
label:
|
||||||
|
desc:
|
||||||
|
|
||||||
others:
|
others:
|
||||||
section_label_sounds: "声音"
|
section_label_sounds: "声音"
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ config_page:
|
|||||||
right_hand: "右手"
|
right_hand: "右手"
|
||||||
overlay_show_only_translated_messages:
|
overlay_show_only_translated_messages:
|
||||||
label: "僅顯示翻譯後的訊息"
|
label: "僅顯示翻譯後的訊息"
|
||||||
|
voice_typing_mode:
|
||||||
|
label:
|
||||||
|
desc:
|
||||||
|
|
||||||
others:
|
others:
|
||||||
section_label_sounds: "音效"
|
section_label_sounds: "音效"
|
||||||
|
|||||||
@@ -759,6 +759,9 @@ class Config:
|
|||||||
SELECTED_TRANSLATION_COMPUTE_DEVICE = ValidatedProperty('SELECTED_TRANSLATION_COMPUTE_DEVICE', _compute_device_validator)
|
SELECTED_TRANSLATION_COMPUTE_DEVICE = ValidatedProperty('SELECTED_TRANSLATION_COMPUTE_DEVICE', _compute_device_validator)
|
||||||
SELECTED_TRANSCRIPTION_COMPUTE_DEVICE = ValidatedProperty('SELECTED_TRANSCRIPTION_COMPUTE_DEVICE', _compute_device_validator)
|
SELECTED_TRANSCRIPTION_COMPUTE_DEVICE = ValidatedProperty('SELECTED_TRANSCRIPTION_COMPUTE_DEVICE', _compute_device_validator)
|
||||||
|
|
||||||
|
# -- Clipboard control ---
|
||||||
|
ENABLE_CLIPBOARD = ManagedProperty('ENABLE_CLIPBOARD', type_=bool)
|
||||||
|
|
||||||
def init_config(self):
|
def init_config(self):
|
||||||
# Read Only
|
# Read Only
|
||||||
self._VERSION = "3.3.2"
|
self._VERSION = "3.3.2"
|
||||||
@@ -1025,11 +1028,11 @@ class Config:
|
|||||||
self._WEBSOCKET_SERVER = False
|
self._WEBSOCKET_SERVER = False
|
||||||
self._WEBSOCKET_HOST = "127.0.0.1"
|
self._WEBSOCKET_HOST = "127.0.0.1"
|
||||||
self._WEBSOCKET_PORT = 2231
|
self._WEBSOCKET_PORT = 2231
|
||||||
|
self._ENABLE_CLIPBOARD = False
|
||||||
## Telemetry
|
self._ENABLE_TELEMETRY = True
|
||||||
self._ENABLE_TELEMETRY = True # デフォルト有効
|
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
|
self._config_data = {}
|
||||||
if os_path.isfile(self.PATH_CONFIG) is not False:
|
if os_path.isfile(self.PATH_CONFIG) is not False:
|
||||||
with open(self.PATH_CONFIG, 'r', encoding="utf-8") as fp:
|
with open(self.PATH_CONFIG, 'r', encoding="utf-8") as fp:
|
||||||
if fp.readable() and fp.seek(0, 2) > 0:
|
if fp.readable() and fp.seek(0, 2) > 0:
|
||||||
@@ -1054,6 +1057,7 @@ class Config:
|
|||||||
continue
|
continue
|
||||||
except Exception:
|
except Exception:
|
||||||
errorLogging()
|
errorLogging()
|
||||||
|
|
||||||
self.saveConfigToFile()
|
self.saveConfigToFile()
|
||||||
|
|
||||||
def revalidate_selected_models(self):
|
def revalidate_selected_models(self):
|
||||||
|
|||||||
@@ -422,6 +422,11 @@ class Controller:
|
|||||||
)
|
)
|
||||||
model.updateOverlayLargeLog(overlay_image)
|
model.updateOverlayLargeLog(overlay_image)
|
||||||
|
|
||||||
|
if config.ENABLE_CLIPBOARD is True:
|
||||||
|
clipboard_message = self.messageFormatter("SEND", translation, message)
|
||||||
|
model.setCopyToClipboard(clipboard_message)
|
||||||
|
model.setPasteFromClipboard()
|
||||||
|
|
||||||
if model.checkWebSocketServerAlive() is True:
|
if model.checkWebSocketServerAlive() is True:
|
||||||
model.websocketSendMessage(
|
model.websocketSendMessage(
|
||||||
{
|
{
|
||||||
@@ -774,6 +779,11 @@ class Controller:
|
|||||||
)
|
)
|
||||||
model.updateOverlayLargeLog(overlay_image)
|
model.updateOverlayLargeLog(overlay_image)
|
||||||
|
|
||||||
|
if config.ENABLE_CLIPBOARD is True:
|
||||||
|
clipboard_message = self.messageFormatter("SEND", translation, message)
|
||||||
|
model.setCopyToClipboard(clipboard_message)
|
||||||
|
model.setPasteFromClipboard()
|
||||||
|
|
||||||
if model.checkWebSocketServerAlive() is True:
|
if model.checkWebSocketServerAlive() is True:
|
||||||
model.websocketSendMessage(
|
model.websocketSendMessage(
|
||||||
{
|
{
|
||||||
@@ -3068,6 +3078,23 @@ class Controller:
|
|||||||
model.stopWebSocketServer()
|
model.stopWebSocketServer()
|
||||||
return {"status":200, "result":config.WEBSOCKET_SERVER}
|
return {"status":200, "result":config.WEBSOCKET_SERVER}
|
||||||
|
|
||||||
|
# Clipboard control
|
||||||
|
@staticmethod
|
||||||
|
def getClipboard(*args, **kwargs) -> dict:
|
||||||
|
return {"status":200, "result":config.ENABLE_CLIPBOARD}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def setEnableClipboard(*args, **kwargs) -> dict:
|
||||||
|
if config.ENABLE_CLIPBOARD is False:
|
||||||
|
config.ENABLE_CLIPBOARD = True
|
||||||
|
return {"status":200, "result":config.ENABLE_CLIPBOARD}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
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):
|
def initializationProgress(self, progress):
|
||||||
self.run(200, self.run_mapping["initialization_progress"], progress)
|
self.run(200, self.run_mapping["initialization_progress"], progress)
|
||||||
|
|
||||||
|
|||||||
@@ -407,6 +407,11 @@ mapping = {
|
|||||||
"/set/enable/websocket_server": {"status": True, "variable":controller.setEnableWebSocketServer},
|
"/set/enable/websocket_server": {"status": True, "variable":controller.setEnableWebSocketServer},
|
||||||
"/set/disable/websocket_server": {"status": True, "variable":controller.setDisableWebSocketServer},
|
"/set/disable/websocket_server": {"status": True, "variable":controller.setDisableWebSocketServer},
|
||||||
|
|
||||||
|
# Clipboard Settings
|
||||||
|
"/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
|
# Advanced Settings
|
||||||
"/get/data/osc_ip_address": {"status": True, "variable":controller.getOscIpAddress},
|
"/get/data/osc_ip_address": {"status": True, "variable":controller.getOscIpAddress},
|
||||||
"/set/data/osc_ip_address": {"status": True, "variable":controller.setOscIpAddress},
|
"/set/data/osc_ip_address": {"status": True, "variable":controller.setOscIpAddress},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from models.overlay.overlay import Overlay
|
|||||||
from models.overlay.overlay_image import OverlayImage
|
from models.overlay.overlay_image import OverlayImage
|
||||||
from models.watchdog.watchdog import Watchdog
|
from models.watchdog.watchdog import Watchdog
|
||||||
from models.websocket.websocket_server import WebSocketServer
|
from models.websocket.websocket_server import WebSocketServer
|
||||||
|
from models.clipboard.clipboard import Clipboard
|
||||||
from models.telemetry import Telemetry
|
from models.telemetry import Telemetry
|
||||||
from utils import errorLogging, setupLogger
|
from utils import errorLogging, setupLogger
|
||||||
|
|
||||||
@@ -141,8 +142,7 @@ class Model:
|
|||||||
# default no-op callbacks for energy check functions
|
# default no-op callbacks for energy check functions
|
||||||
self.check_mic_energy_fnc: Callable[[float], None] = lambda v: None
|
self.check_mic_energy_fnc: Callable[[float], None] = lambda v: None
|
||||||
self.check_speaker_energy_fnc: Callable[[float], None] = lambda v: None
|
self.check_speaker_energy_fnc: Callable[[float], None] = lambda v: None
|
||||||
|
self.clipboard = Clipboard()
|
||||||
# Telemetry 初期化(Model 内でインスタンスを保持)
|
|
||||||
self.telemetry = Telemetry()
|
self.telemetry = Telemetry()
|
||||||
|
|
||||||
self._inited = True
|
self._inited = True
|
||||||
@@ -1296,6 +1296,29 @@ class Model:
|
|||||||
errorLogging()
|
errorLogging()
|
||||||
return False
|
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
|
||||||
|
|
||||||
def telemetryInit(self, enabled: bool, app_version: str):
|
def telemetryInit(self, enabled: bool, app_version: str):
|
||||||
"""Model 内で Telemetry を初期化"""
|
"""Model 内で Telemetry を初期化"""
|
||||||
self.telemetry.init(enabled=enabled, app_version=app_version)
|
self.telemetry.init(enabled=enabled, app_version=app_version)
|
||||||
|
|||||||
275
src-python/models/clipboard/clipboard.py
Normal file
275
src-python/models/clipboard/clipboard.py
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from psutil import process_iter
|
||||||
|
import openvr
|
||||||
|
|
||||||
|
try:
|
||||||
|
from utils import printLog
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
for i in range(countdown, 0, -1):
|
||||||
|
print(i, end=' ', flush=True)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.is_enabled = True
|
||||||
|
self._vr_monitor_thread = None
|
||||||
|
self._stop_monitoring = False
|
||||||
|
self.app_name = None
|
||||||
|
|
||||||
|
self._initialize()
|
||||||
|
|
||||||
|
def _initialize(self):
|
||||||
|
"""Initialize clipboard by starting VR monitor thread."""
|
||||||
|
self._stop_monitoring = False
|
||||||
|
self._vr_monitor_thread = threading.Thread(target=self._monitor_steamvr, daemon=True)
|
||||||
|
self._vr_monitor_thread.start()
|
||||||
|
self.app_name = None
|
||||||
|
printLog("Clipboard initialized. Waiting for SteamVR.")
|
||||||
|
|
||||||
|
def _monitor_steamvr(self):
|
||||||
|
"""Monitor SteamVR startup in background thread."""
|
||||||
|
printLog("Clipboard: VR monitor thread started.")
|
||||||
|
while not self._stop_monitoring:
|
||||||
|
if checkSteamvrRunning():
|
||||||
|
printLog("Clipboard: SteamVR detected. Setting up app info.")
|
||||||
|
self._setup_vr_app_name()
|
||||||
|
break
|
||||||
|
time.sleep(10)
|
||||||
|
printLog("Clipboard: VR monitor thread ended.")
|
||||||
|
|
||||||
|
def _setup_vr_app_name(self):
|
||||||
|
"""Setup VR application name from OpenVR."""
|
||||||
|
try:
|
||||||
|
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()
|
||||||
|
except Exception as e:
|
||||||
|
printLog(f"Clipboard: Error setting up VR app name: {e}")
|
||||||
|
self.app_name = None
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
"""Enable clipboard functionality. Reinitialize the class."""
|
||||||
|
printLog("Clipboard: Enabling clipboard functionality.")
|
||||||
|
self.is_enabled = True
|
||||||
|
self._initialize()
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
"""Disable clipboard functionality. Stop VR monitoring."""
|
||||||
|
printLog("Clipboard: Disabling clipboard functionality.")
|
||||||
|
self.is_enabled = False
|
||||||
|
self._stop_monitoring = True
|
||||||
|
if self._vr_monitor_thread is not None and self._vr_monitor_thread.is_alive():
|
||||||
|
self._vr_monitor_thread.join(timeout=1)
|
||||||
|
self._vr_monitor_thread = None
|
||||||
|
|
||||||
|
def copy(self, message: str) -> bool:
|
||||||
|
"""Copy `message` to clipboard.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Text to copy.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if copy succeeded, False otherwise.
|
||||||
|
"""
|
||||||
|
if not self.is_enabled:
|
||||||
|
return False
|
||||||
|
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 not self.is_enabled:
|
||||||
|
return False
|
||||||
|
|
||||||
|
window_name = window_name if window_name is not None else self.app_name
|
||||||
|
|
||||||
|
# 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
|
||||||
|
break
|
||||||
|
|
||||||
|
# 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
|
||||||
|
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=3)
|
||||||
@@ -611,6 +611,14 @@ export const SETTINGS_ARRAY = [
|
|||||||
logics_template_id: "toggle_enable_disable",
|
logics_template_id: "toggle_enable_disable",
|
||||||
base_endpoint_name: "overlay_show_only_translated_messages",
|
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
|
// Others
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -524,7 +524,12 @@ const OtherControls = ({settings, onchangeFunction, ui_configs}) => {
|
|||||||
|
|
||||||
const CommonSettingsContainer = () => {
|
const CommonSettingsContainer = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { currentOverlayShowOnlyTranslatedMessages, toggleOverlayShowOnlyTranslatedMessages } = useVr();
|
const {
|
||||||
|
currentOverlayShowOnlyTranslatedMessages,
|
||||||
|
toggleOverlayShowOnlyTranslatedMessages,
|
||||||
|
currentVoiceTypingMode,
|
||||||
|
toggleVoiceTypingMode,
|
||||||
|
} = useVr();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.common_container}>
|
<div className={styles.common_container}>
|
||||||
@@ -534,6 +539,12 @@ const CommonSettingsContainer = () => {
|
|||||||
variable={currentOverlayShowOnlyTranslatedMessages}
|
variable={currentOverlayShowOnlyTranslatedMessages}
|
||||||
toggleFunction={toggleOverlayShowOnlyTranslatedMessages}
|
toggleFunction={toggleOverlayShowOnlyTranslatedMessages}
|
||||||
/>
|
/>
|
||||||
|
<CheckboxContainer
|
||||||
|
label={t("config_page.vr.voice_typing_mode.label")}
|
||||||
|
desc={t("config_page.vr.voice_typing_mode.desc")}
|
||||||
|
variable={currentVoiceTypingMode}
|
||||||
|
toggleFunction={toggleVoiceTypingMode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user