#!/usr/bin/env python3 # -*- coding: utf-8 -*- # #Copyright 2018 Sodium "natoriusushio" Chloride # #Released under the MIT license # #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), #to deal in the Software without restriction, including without limitation #the rights to use, copy, modify, merge, publish, distribute, sublicense, #and/or sell copies of the Software, and to permit persons to whom #the Software is furnished to do so, subject to the following conditions: # #The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, #EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES #OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, #DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, #TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE #OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #https://opensource.org/licenses/mit-license.php #http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license import datetime import infi.systray import json import keyboard import Pinyin2Hanzi import threading import time import tkinter import traceback import win32api import win32gui import win32process # ==== definition area ==== # == variable == abort = False autophagy = False backspaceisreleased = False destroy = False end = False firstloop = True chkfghWnd = 0 boxname = "" converted = "" chkfgWndt = "" source = "" systray = None # == win32api == # pywin32 # https://github.com/mhammond/pywin32 # http://timgolden.me.uk/pywin32-docs/index.html def attach_thread_input(current, target, boolean): win32process.AttachThreadInput(current, target, boolean) def client_to_screen(target, client_x, client_y): coordinates = (client_x, client_y) screen_x, screen_y = win32gui.ClientToScreen(target, coordinates) return screen_x, screen_y def find_window(classname, windowname): result = win32gui.FindWindow(classname, windowname) return result def get_caret_pos(): result = win32gui.GetCaretPos() return result def get_current_thread_id(): result = win32api.GetCurrentThreadId() return result def get_foreground_window(): result = win32gui.GetForegroundWindow() return result def get_system_metrics(): screen_x = win32api.GetSystemMetrics(0) screen_y = win32api.GetSystemMetrics(1) return screen_x, screen_y def get_window_rect(hWnd): topleft_x, topleft_y, bottomright_x, bottomright_y \ = win32gui.GetWindowRect(hWnd) return topleft_x, topleft_y, bottomright_x, bottomright_y def get_window_text(hWnd): result = win32gui.GetWindowText(hWnd) return result def get_window_thread_process_id(hWnd): targetthreadid, targetprocessid = win32process.GetWindowThreadProcessId( hWnd) return targetthreadid, targetprocessid def set_foreground_window(hWnd): win32gui.SetForegroundWindow(hWnd) # == function == def apoptosis(boolean): global destroy destroy = boolean global end end = boolean global autophagy autophagy = boolean # keyboard # https://github.com/boppreh/keyboard def terminator(): keyboard.add_hotkey("ctrl+alt+del", apoptosis, args=[True]) def backspace_is_released(boolean): global backspaceisreleased backspaceisreleased = boolean def add_hotkey_backspace_is_released(): keyboard.add_hotkey("backspace", backspace_is_released, args=[True], trigger_on_release=True) def get_convwindow_size(): # convwindow = window for converting topleft_x =0 topleft_y =0 bottomright_x =144 bottomright_y =21 return bottomright_x, bottomright_y # I referred to these topics for writing the function below. # https://stackoverflow.com/questions/19724360/python-get-caret-position # http://timgolden.me.uk/python/win32_how_do_i/find-the-screen-resolution.html def get_coordinates(): targetwindow = get_foreground_window() targetthreadid, targetprocessid = get_window_thread_process_id( targetwindow) currentthreadid = get_current_thread_id() try: attach_thread_input(currentthreadid, targetthreadid, True) clientcaret_x, clientcaret_y = get_caret_pos() finally: attach_thread_input(currentthreadid, targetthreadid, False) if (clientcaret_x, clientcaret_y) == (None, None): screen_x, screen_y = get_system_metrics() convwin_x, convwin_y = get_convwindow_size() screencaret_x = (screen_x - convwin_x) // 2 screencaret_y = 2 * (screen_y - convwin_y) // 3 else: if (clientcaret_x, clientcaret_y) == (0, 0): # topleft=tpl, bottomright=btmr tpl_x, tpl_y, btmr_x, btmr_y = get_window_rect(targetwindow) convwin_x, convwin_y = get_convwindow_size() tmp_x = (btmr_x - tpl_x - convwin_x) // 2 tmp_y = 2 * (btmr_y - tpl_y - convwin_x) // 3 screencaret_x = tpl_x + tmp_x screencaret_y = tpl_y + tmp_y else: screencaret_x, screencaret_y = client_to_screen( targetwindow, clientcaret_x, clientcaret_y) return screencaret_x, screencaret_y def get_source(screencaret_x, screencaret_y): global destroy destroy = False label = "conv" identifier = datetime.datetime.now().strftime("%f") global boxname boxname = label+identifier root = tkinter.Tk() root.title(boxname) root.attributes("-topmost", True) root.overrideredirect(1) coordinate = "+%s+%s" % (screencaret_x, screencaret_y) root.geometry(coordinate) getentry = tkinter.Entry() getentry.config(background="azure") def get_entry(event): keyboard.release("enter") global source source = getentry.get() global destroy destroy = True def terminate(event): keyboard.release("\\") keyboard.release("ctrl") global end end = True fgw = get_foreground_window() fgwt = get_window_text(fgw) global destroy destroy = True getentry.pack() getentry.focus_set() root.bind("", get_entry) root.bind("", terminate) while destroy == False: time.sleep(0.001) # for reducing CPU usage root.update() fgw = get_foreground_window() fgwt = get_window_text(fgw) if fgwt == boxname: pass else: target = find_window("TkTopLevel", boxname) set_foreground_window(target) destroy = False getentry.destroy() root.destroy() # Pinyin2Hanzi # https://github.com/letiantian/Pinyin2Hanzi def convert_pinyin_to_hanzi(source): hmmparams = Pinyin2Hanzi.DefaultHmmParams() quotation = "\"" leftbracket = "[" rightbracket = "]" source = source.strip() source = source.replace(" ", "\", \"") source = leftbracket + quotation + source + quotation + rightbracket source = json.loads(source) try: response = Pinyin2Hanzi.viterbi(hmm_params=hmmparams, observations=source, path_num=5) except: traceback.print_exc() response = ["error"] finally: pass result = [] if response == ["error"]: result = response else: for item in response: result.append(item.path) result = str(result) result = result.replace("\', \'", "") result = result.replace("[\'", "\"") result = result.replace("\']", "\"") result = json.loads(result) return result def select_word_from_list(result, screencaret_x, screencaret_y): global destroy destroy = False counts = len(result) label = "conv" identifier = datetime.datetime.now().strftime("%f") global boxname boxname = label+identifier root = tkinter.Tk() root.title(boxname) root.attributes("-topmost", True) root.overrideredirect(1) coordinates = "+%s+%s" % (screencaret_x, screencaret_y) root.geometry(coordinates) selected = tkinter.StringVar() selected.set("") entry = tkinter.Entry(root, textvariable = selected) entry.pack() listbox = tkinter.Listbox(root) listbox.pack() listbox.focus_set() listbox.insert(tkinter.END, " ===== OPTIONS ===== ") for item in result: listbox.insert(tkinter.END, item) selected.set(listbox.get(1)) def select_next(event): keyboard.release("space") nowselected = listbox.index("active") if nowselected < counts: listbox.see(nowselected+1) listbox.activate(nowselected+1) selected.set(listbox.get(nowselected+1)) else: listbox.activate(1) listbox.see(1) selected.set(listbox.get(1)) def select_prev(event): keyboard.release("space") keyboard.release("shift") nowselected = listbox.index("active") if nowselected > 1: listbox.see(nowselected-1) listbox.activate(nowselected-1) selected.set(listbox.get(nowselected-1)) else: listbox.activate(counts) listbox.see(counts) selected.set(listbox.get(counts)) def get_entry(event): keyboard.release("enter") nowselected = listbox.index("active") global converted if nowselected > 0: converted = listbox.get("active") else: converted = listbox.get(1) global destroy destroy = True def terminate(event): keyboard.release("\\") keyboard.release("ctrl") global end end = True global destroy destroy = True listbox.bind("", select_next) listbox.bind("", select_prev) listbox.bind("", get_entry) listbox.bind("", terminate) while destroy == False: time.sleep(0.001) # for reducing CPU usage root.update() fgw = get_foreground_window() fgwt = get_window_text(fgw) if fgwt == boxname: pass else: target = find_window("TkTopLevel", boxname) set_foreground_window(target) destroy = False listbox.destroy() root.destroy() def threading_start(threaddef, threadname, targetname): threadname = threading.Thread(target=threaddef, name=targetname) threadname.start() def write_word(word): time.sleep(0.001) keyboard.write(word) def release_keys(): keyboard.release("backspace") keyboard.release("space") keyboard.release("shift") keyboard.release("ctrl") keyboard.release("enter") def unhook_all(): keyboard.unhook_all() def send_backspace(): keyboard.send("backspace") def send_enter(): keyboard.send("enter") def sleep_1ms(): time.sleep(0.001) def sleep_5cs(): time.sleep(0.05) # infi.systray # https://github.com/Infinidat/infi.systray def define_systray(): global systray systray = infi.systray.SysTrayIcon("icon-zh.ico", "now converting") def start_systray(): systray.start() def shutdown_systray(): systray.shutdown() # ==== algorithm area ==== # something like pseudocode oriented programming style ;-) print("hi") define_systray() start_systray() #print("Select window, then press shift.") # for testing #keyboard.wait("shift") # for testing chkfghWnd = get_foreground_window() # check foreground handle of window chkfgWndt = get_window_text(chkfghWnd) # check foreground window text if "conv" in chkfgWndt: print("Another process is already running!") print("Program will be aborted.") end = True abort = True while end == False: fghWnd = 0 # foreground handle of window screencaret_x = 0 screencaret_y = 0 backspaceisreleased = False split = False thread1 = None thread2 = None source = "" converted = "" optionlist = [] release_keys() unhook_all() add_hotkey_backspace_is_released() # for avoiding a confliction between this program # and the ctrl+alt+del security option window terminator() if firstloop == False: sleep_1ms() fghWnd = get_foreground_window() screencaret_x, screencaret_y = get_coordinates() thread1 = get_source(screencaret_x, screencaret_y) threading_start(thread1, "sourcethread", "sourcethread") if end == True: break if len(source) > 0: optionlist = convert_pinyin_to_hanzi(source) if len(source) > 0: if not optionlist == ["error"]: thread2 = select_word_from_list(optionlist, screencaret_x, screencaret_y) threading_start(thread2, "resultthread", "resultthread") else: converted = "" if end == True: break set_foreground_window(fghWnd) if len(converted) > 0: write_word(converted) else: if not optionlist == ["error"]: if backspaceisreleased == True: send_backspace() else: send_enter() firstloop = False if abort == False: set_foreground_window(fghWnd) release_keys() unhook_all() sleep_5cs() shutdown_systray() sleep_5cs() if autophagy == False: print("bye") else: print("adieu")