#!/usr/bin/env python3 # -*- coding: cp949 -*- # #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 import ctypes import datetime import infi.systray import hangul_jamo import json import keyboard import threading import time import tkinter 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 # == character map == #alphabetmap = ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p", # "a", "s", "d", "f", "g", "h", "j", "k", "l", # "z", "x", "c", "v", "b", "n", "m", # "Q", "W", "E", "R", "T", "O", "P"] #jamomap = ["げ", "じ", "ぇ", "ぁ", "さ", "に", "づ", "ち", "だ", "つ", # "け", "い", "し", "ぉ", "ぞ", "で", "っ", "た", "び", # "せ", "ぜ", "ず", "そ", "ば", "ぬ", "ぱ", # "こ", "す", "え", "あ", "ざ", "ぢ", "て"] # == 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(root, font=("Batang", 12)) 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() #def convert_alphabet_to_jamo(): # for alp, jam in zip(alphabetmap, jamomap): # keyboard.add_abbreviation(alp, jam) # str.maketrans() # https://www.tutorialspoint.com/python3/string_maketrans.htm def convert_alphabet_to_jamo(alphabet): translatetable = str.maketrans("qwertyuiopasdfghjklzxcvbnmQWERTOP", "げじぇぁさにづちだつけいしぉぞでったびせぜずそばぬぱこすえあざぢて") result = alphabet.translate(translatetable) return result def show_source(screencaret_x, screencaret_y): global source if len(source) > 0: showsource = convert_alphabet_to_jamo(source) 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) # showentry = tkinter.Entry(root, font=("Batang", 12)) showentry = tkinter.Entry() showentry.insert(tkinter.END, showsource) showentry.config(background="PaleTurquoise1") def end_entry(event): keyboard.release("enter") 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 showentry.pack() showentry.focus_set() root.bind("", end_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 showentry.destroy() root.destroy() else: pass # hangul-jamo # https://github.com/jonghwanhyeon/hangul-jamo def convert_jamo_to_hangul(jamosource): result = hangul_jamo.compose(jamosource) return result 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-ko.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 thread2 = show_source(screencaret_x, screencaret_y) threading_start(thread2, "showthread", "showthread") if end == True: break if len(source) > 0: jamosource = convert_alphabet_to_jamo(source) converted = convert_jamo_to_hangul(jamosource) else: converted = "" set_foreground_window(fghWnd) if len(converted) > 0: write_word(converted) else: 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")