#!/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 ctypes import datetime import infi.systray import json import keyboard import romkan import socket import threading import time import tkinter import urllib import urllib.request 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 # == exception handling == exception = [",", "|", ";", " "] comma = ["、", ",", ","] semicolon = [";", ";"] space = [" ", " "] verticalbar =["|", "¦", "∥", "|", "¦", "‖"] bracket = ["[", "]", "(", ")"] # == win32api == 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 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(caretpositionglobal_x, caretpositionglobal_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" % (caretpositionglobal_x, caretpositionglobal_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() # I referred to this topic for writing the function below. # https://masatoi.github.io/2017/11/19/skk-client def convert_romaji_to_kanji(source): skkreq = "1" skksource = "" skkspacer = " " skkhost = "127.0.0.1" skkport = 1178 skkencoding = "euc_jp" hiraganasource = romkan.to_hiragana(source) katakanasource = romkan.to_katakana(source) skksource = skkreq + hiraganasource + skkspacer client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((skkhost, skkport)) client.send(skksource.encode(skkencoding)) result = client.recv(1024) client.shutdown(socket.SHUT_RDWR) client.close() if result.decode(skkencoding) == "4\n": result = [] else: result = result.decode(skkencoding) result = result.replace("1/", "[\"") result = result.replace("/\n", "\"]") result = result.replace("/", "\", \"") result = json.loads(result) if hiraganasource not in result: try: result.insert(0, hiraganasource) except: print("不正な文字列です") if katakanasource not in result: try: result.append(katakanasource) except: print("不正な文字列です") if source not in result: try: result.append(source) except: print("不正な文字列です") if source in bracket: for en, ja in zip(["(", ")", "[", "]"], ["「", "」", "「", "」"]): if source is en: if ja not in result: result.append(ja) 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, " ===== 変換候補 ===== ") 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_key(key): # keyboard.send(key) def send_backspace(): keyboard.send("backspace") def send_enter(): keyboard.send("enter") def send_plus(): ctypes.windll.user32.keybd_event(0x6B, 0, 0, 0) ctypes.windll.user32.keybd_event(0x6B, 0, 0x0002, 0) def sleep_1ms(): time.sleep(0.001) def sleep_5cs(): time.sleep(0.05) def replace_verticalbar(): source.replace("|", "|") def define_systray(): global systray systray = infi.systray.SysTrayIcon("icon.ico", "変換中") def start_systray(): systray.start() def shutdown_systray(): systray.shutdown() # ==== algorithm area ==== # pseudocode oriented programming style, "pops" ;-) print("hi") define_systray() start_systray() 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: if len(source) == 1: if source in exception: for src, alt in zip([",", "|", ";", " "], [comma, verticalbar, semicolon, space]): if source is src: optionlist = alt else: optionlist = convert_romaji_to_kanji(source) else: if "|" in source: source = replace_verticalbar() optionlist = convert_romaji_to_kanji(source) if len(source) > 0: 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: if converted is not ";": split = False while split == False: if ";" in converted: # for SKKDict with annotation converted = converted[:-1] else: split = True if converted is not "+": write_word(converted) else: # because keyboard.send("+") replies ";" on japanese environment send_plus() 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")