Compare commits
12 Commits
bc0f4bc960
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c26e99dcf | ||
|
|
a701c0abd3 | ||
|
|
8463eb28df | ||
|
|
c23805320e | ||
|
|
66c0482ae3 | ||
|
|
9647115ad6 | ||
|
|
7fbd6ca080 | ||
|
|
4163d1cf6e | ||
|
|
fb12d5a216 | ||
|
|
9aa2ff2bd0 | ||
|
|
96a8092716 | ||
|
|
ad7ca318a7 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
src/__pycache__/config.cpython-314.pyc
|
__pycache__/
|
||||||
src/__pycache__/startup.cpython-314.pyc
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
src/__pycache__/startup.cpython-314.pyc
|
app.lock
|
||||||
src/__pycache__/config.cpython-314.pyc
|
src/__pycache__/
|
||||||
|
venv
|
||||||
|
|||||||
19
install-melli.bat
Normal file
19
install-melli.bat
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@echo off
|
||||||
|
REM install.bat
|
||||||
|
REM This script sets up a Python virtual environment and installs required libraries.
|
||||||
|
REM Make sure to run this script in the project directory.
|
||||||
|
|
||||||
|
REM Create Python virtual environment
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
REM Activate the virtual environment
|
||||||
|
call venv\Scripts\activate.bat
|
||||||
|
|
||||||
|
REM Upgrade pip
|
||||||
|
pip install --upgrade pip -i https://mirror-pypi.runflare.com/simple
|
||||||
|
|
||||||
|
REM Install required libraries from requirements.txt
|
||||||
|
pip install -r requirements.txt --no-cache-dir -i https://mirror-pypi.runflare.com/simple
|
||||||
|
|
||||||
|
echo Environment setup complete.
|
||||||
|
pause
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
keyboard==0.13.5
|
keyboard==0.13.5
|
||||||
pyperclip==1.9.0
|
pyperclip==1.9.0
|
||||||
pystray==0.19.5
|
pystray==0.19.5
|
||||||
<<<<<<< HEAD
|
|
||||||
Pillow==12.0.0
|
Pillow==12.0.0
|
||||||
=======
|
psutil>=5.8.0
|
||||||
Pillow==12.0.0
|
|
||||||
>>>>>>> 4143eb7dfbdcc5a646f23c1fc5369779d41c4f4a
|
|
||||||
|
|||||||
4
run_silent.bat
Normal file
4
run_silent.bat
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
cd /d "%~dp0"
|
||||||
|
call venv\Scripts\activate.bat
|
||||||
|
start "" pythonw src\main.pyw
|
||||||
4
run_silent.vbs
Normal file
4
run_silent.vbs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Set WshShell = CreateObject("WScript.Shell")
|
||||||
|
strPath = CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName)
|
||||||
|
WshShell.Run chr(34) & strPath & "\run_silent.bat" & Chr(34), 0
|
||||||
|
Set WshShell = Nothing
|
||||||
Binary file not shown.
117
src/main.pyw
117
src/main.pyw
@@ -11,8 +11,9 @@ import re
|
|||||||
from config import load_config, save_config, get_shortcut
|
from config import load_config, save_config, get_shortcut
|
||||||
from startup import add_to_startup, remove_from_startup, is_in_startup
|
from startup import add_to_startup, remove_from_startup, is_in_startup
|
||||||
import os
|
import os
|
||||||
import signal
|
|
||||||
import atexit
|
import atexit
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
def convert_en_characters(input_str):
|
def convert_en_characters(input_str):
|
||||||
"""
|
"""
|
||||||
@@ -138,6 +139,68 @@ def quit_app(icon, item):
|
|||||||
import os
|
import os
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
|
def restart_app(icon, item):
|
||||||
|
icon.stop()
|
||||||
|
# Unhook all keyboard hooks before restarting
|
||||||
|
keyboard.unhook_all()
|
||||||
|
cleanup_lock()
|
||||||
|
import subprocess
|
||||||
|
python = sys.executable
|
||||||
|
script = os.path.abspath(__file__)
|
||||||
|
subprocess.Popen([python, script])
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
def change_shortcut_dialog():
|
||||||
|
"""Show a dialog to capture a new keyboard shortcut."""
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("Change Shortcut")
|
||||||
|
root.geometry("400x200")
|
||||||
|
root.resizable(False, False)
|
||||||
|
|
||||||
|
# Center the window
|
||||||
|
root.update_idletasks()
|
||||||
|
x = (root.winfo_screenwidth() // 2) - (400 // 2)
|
||||||
|
y = (root.winfo_screenheight() // 2) - (200 // 2)
|
||||||
|
root.geometry(f"400x200+{x}+{y}")
|
||||||
|
|
||||||
|
captured_shortcut = {"value": None}
|
||||||
|
|
||||||
|
label = tk.Label(root, text="Press the key combination you want to use\nas a shortcut, then press ESC",
|
||||||
|
font=("Arial", 12), pady=20)
|
||||||
|
label.pack()
|
||||||
|
|
||||||
|
shortcut_label = tk.Label(root, text="Waiting for input...", font=("Arial", 14, "bold"), fg="blue")
|
||||||
|
shortcut_label.pack(pady=10)
|
||||||
|
|
||||||
|
def capture_shortcut():
|
||||||
|
try:
|
||||||
|
# Unregister current hotkey before capturing new one
|
||||||
|
keyboard.unhook_all_hotkeys()
|
||||||
|
shortcut = keyboard.read_hotkey(suppress=False)
|
||||||
|
captured_shortcut["value"] = shortcut
|
||||||
|
shortcut_label.config(text=f"Captured: {shortcut}")
|
||||||
|
root.after(1000, root.destroy)
|
||||||
|
except Exception as e:
|
||||||
|
shortcut_label.config(text=f"Error: {str(e)}", fg="red")
|
||||||
|
root.after(2000, root.destroy)
|
||||||
|
|
||||||
|
# Start capturing in a separate thread
|
||||||
|
capture_thread = threading.Thread(target=capture_shortcut, daemon=True)
|
||||||
|
capture_thread.start()
|
||||||
|
|
||||||
|
root.mainloop()
|
||||||
|
return captured_shortcut["value"]
|
||||||
|
|
||||||
|
def on_change_shortcut(icon, item):
|
||||||
|
"""Handle shortcut change request."""
|
||||||
|
new_shortcut = change_shortcut_dialog()
|
||||||
|
if new_shortcut:
|
||||||
|
config = load_config()
|
||||||
|
config["shortcut"] = new_shortcut
|
||||||
|
save_config(config)
|
||||||
|
# Always restart after shortcut change attempt to ensure hotkeys are properly re-registered
|
||||||
|
restart_app(icon, item)
|
||||||
|
|
||||||
def setup_tray(config):
|
def setup_tray(config):
|
||||||
def on_toggle_lang_change(icon, item):
|
def on_toggle_lang_change(icon, item):
|
||||||
config["lang_change"] = not item.checked
|
config["lang_change"] = not item.checked
|
||||||
@@ -153,7 +216,7 @@ def setup_tray(config):
|
|||||||
|
|
||||||
image = Image.open("assets/icon.png")
|
image = Image.open("assets/icon.png")
|
||||||
menu = pystray.Menu(
|
menu = pystray.Menu(
|
||||||
pystray.MenuItem(f'Current Shortcut: {get_shortcut()}',lambda icon, item: None),
|
pystray.MenuItem(f'Shortcut: {get_shortcut()} (click to change)', on_change_shortcut),
|
||||||
pystray.MenuItem(
|
pystray.MenuItem(
|
||||||
'change keyboard language layout after transformation',
|
'change keyboard language layout after transformation',
|
||||||
on_toggle_lang_change,
|
on_toggle_lang_change,
|
||||||
@@ -164,6 +227,7 @@ def setup_tray(config):
|
|||||||
on_toggle_startup,
|
on_toggle_startup,
|
||||||
checked=lambda item: config["run_on_startup"]
|
checked=lambda item: config["run_on_startup"]
|
||||||
),
|
),
|
||||||
|
pystray.MenuItem('Restart', restart_app),
|
||||||
pystray.MenuItem('Quit', quit_app)
|
pystray.MenuItem('Quit', quit_app)
|
||||||
)
|
)
|
||||||
icon = pystray.Icon("oopswronglang", image, "OopsWrongLang", menu)
|
icon = pystray.Icon("oopswronglang", image, "OopsWrongLang", menu)
|
||||||
@@ -178,22 +242,52 @@ def cleanup_lock():
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def is_process_running(pid):
|
||||||
|
"""Check if a process with the given PID is still running."""
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
return psutil.pid_exists(pid)
|
||||||
|
except:
|
||||||
|
# Fallback if psutil is not available
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0) # Signal 0 doesn't kill, just checks if process exists
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
def ensure_single_instance():
|
def ensure_single_instance():
|
||||||
|
"""Ensure only one instance of the app is running. Exit if another instance exists."""
|
||||||
if os.path.exists(LOCK_FILE):
|
if os.path.exists(LOCK_FILE):
|
||||||
try:
|
try:
|
||||||
|
# Read the PID from the lock file
|
||||||
with open(LOCK_FILE, 'r') as f:
|
with open(LOCK_FILE, 'r') as f:
|
||||||
pid = int(f.read().strip())
|
old_pid = int(f.read().strip())
|
||||||
|
|
||||||
if pid != os.getpid():
|
# Check if the process with that PID is still running
|
||||||
|
if is_process_running(old_pid):
|
||||||
|
# Another instance is running, show message and exit
|
||||||
|
import ctypes
|
||||||
|
ctypes.windll.user32.MessageBoxW(
|
||||||
|
0,
|
||||||
|
"Another instance of OopsWrongLang is already running.\n\nPlease close the existing instance first (check your system tray).",
|
||||||
|
"OopsWrongLang - Already Running",
|
||||||
|
0x40 # MB_ICONINFORMATION
|
||||||
|
)
|
||||||
|
os._exit(1)
|
||||||
|
else:
|
||||||
|
# Process is not running, remove the stale lock file
|
||||||
try:
|
try:
|
||||||
os.kill(pid, signal.SIGTERM)
|
os.remove(LOCK_FILE)
|
||||||
print(f"Killed existing instance with PID {pid}")
|
|
||||||
time.sleep(0.5)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
except (ValueError, OSError):
|
except (ValueError, OSError):
|
||||||
pass
|
# Lock file exists but is invalid or unreadable, clean it up and continue
|
||||||
|
try:
|
||||||
|
os.remove(LOCK_FILE)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Create lock file with our PID
|
||||||
with open(LOCK_FILE, 'w') as f:
|
with open(LOCK_FILE, 'w') as f:
|
||||||
f.write(str(os.getpid()))
|
f.write(str(os.getpid()))
|
||||||
|
|
||||||
@@ -203,7 +297,12 @@ def main():
|
|||||||
ensure_single_instance()
|
ensure_single_instance()
|
||||||
config = load_config()
|
config = load_config()
|
||||||
# Sync startup state just in case it was changed externally
|
# Sync startup state just in case it was changed externally
|
||||||
config["run_on_startup"] = is_in_startup()
|
if is_in_startup():
|
||||||
|
config["run_on_startup"] = True
|
||||||
|
# Update the registry key to point to the current location (in case the app was moved)
|
||||||
|
add_to_startup()
|
||||||
|
else:
|
||||||
|
config["run_on_startup"] = False
|
||||||
save_config(config)
|
save_config(config)
|
||||||
|
|
||||||
# Start the tray icon in a separate thread
|
# Start the tray icon in a separate thread
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import os
|
|||||||
import winreg
|
import winreg
|
||||||
|
|
||||||
APP_NAME = "OopsWrongLang"
|
APP_NAME = "OopsWrongLang"
|
||||||
RUN_BAT_PATH = os.path.abspath("run.bat")
|
# Get the directory of the current script (src)
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# The project root is one level up
|
||||||
|
project_root = os.path.dirname(current_dir)
|
||||||
|
RUN_BAT_PATH = os.path.join(project_root, "run_silent.vbs")
|
||||||
|
|
||||||
def add_to_startup():
|
def add_to_startup():
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user