def __init__(self, parent = None): QObject.__init__(self, parent = parent) # フックマネージャーを生成 self.hook_manager = pyHook.HookManager() # KeyDown, KeyUpで呼ばれる関数を設定 self.hook_manager.KeyDown = self.on_keyboard_event self.hook_manager.KeyUp= self.on_keyboard_event self.modifiers_key_state = ModifiersKeyState() self.timer = QTimer(self) self.timer.setInterval(800) self.timer.setSingleShot(True) self.timer.timeout.connect(self.on_timeout) self.timer_running = False self.window_dict = dict() self.candidates = deque() self.key_sequences = deque() self.window_id = None self.clipboard = Clipboard() self.current_node = None self.root_node = KeyDictNode(dict()) for mode in Mode: self.root_node.set_key_dictionary(mode) for shortcuts_info in shortcuts_tuple: shortcuts_info.set_mode_shortcuts(self.root_node) self.current_exe_name = "" self._mode = Mode.NORMAL self.set_mode_node() self.waiting_return_key = "" self.func_dict = FuncDictDecorator.func_dict()
class EnVimKey(QObject): about_to_quit = pyqtSignal() changed_mode = pyqtSignal(str, str, int) about_to_search = pyqtSignal(object) def __init__(self, parent = None): QObject.__init__(self, parent = parent) # フックマネージャーを生成 self.hook_manager = pyHook.HookManager() # KeyDown, KeyUpで呼ばれる関数を設定 self.hook_manager.KeyDown = self.on_keyboard_event self.hook_manager.KeyUp= self.on_keyboard_event self.modifiers_key_state = ModifiersKeyState() self.timer = QTimer(self) self.timer.setInterval(800) self.timer.setSingleShot(True) self.timer.timeout.connect(self.on_timeout) self.timer_running = False self.window_dict = dict() self.candidates = deque() self.key_sequences = deque() self.window_id = None self.clipboard = Clipboard() self.current_node = None self.root_node = KeyDictNode(dict()) for mode in Mode: self.root_node.set_key_dictionary(mode) for shortcuts_info in shortcuts_tuple: shortcuts_info.set_mode_shortcuts(self.root_node) self.current_exe_name = "" self._mode = Mode.NORMAL self.set_mode_node() self.waiting_return_key = "" self.func_dict = FuncDictDecorator.func_dict() @FuncDictDecorator.inner_func("insert") def change_to_insert_mode(self): self._mode = Mode.INSERT self.set_mode_node() self.changed_mode.emit("Mode", "Insert", 500) print("insert") @FuncDictDecorator.inner_func("normal") def change_to_normal_mode(self): self._mode = Mode.NORMAL self.set_mode_node() self.changed_mode.emit("Mode", "Normal", 500) print("normal") @FuncDictDecorator.inner_func("visual") def change_to_visual_mode(self): self._mode = Mode.VISUAL self.set_mode_node() self.changed_mode.emit("Mode", "Visual", 500) print("visual") @FuncDictDecorator.inner_func("focus") def set_window_focus(self, search_window_name): def match_window_name(window_handle, dummy): window_title = str(win32gui.GetWindowText(window_handle)) if window_name_pattern.match(window_title): self.target_window_handle = window_handle self.target_window_handle = None window_name_pattern = re.compile(search_window_name) win32gui.EnumWindows(match_window_name, "") if self.target_window_handle: win32gui.SetForegroundWindow(self.target_window_handle) win32gui.ShowWindow(self.target_window_handle, win32con.SW_MAXIMIZE) @FuncDictDecorator.inner_func("quit") def quit_envimkey(self): self.about_to_quit.emit() @FuncDictDecorator.inner_func("copy") def copy_to_clipboard(self,text): self.before_text = self.clipboard.get() self.clipboard.set(text) @FuncDictDecorator.inner_func("rollback") def rollback_to_clipboard(self): self.clipboard.set(self.before_text) def set_mode_node(self): self.current_node = self.root_node.dictionary[self._mode] # self.print_dict(self.root_node.dictionary) def timer_start(self): print("start") self.timer_running = True self.timer.start() def timer_stop(self): print("stop") self.timer.stop() self.timer_running = False self.set_mode_node() if self.waiting_return_key: self.emulate_keys(self.waiting_return_key) self.waiting_return_key = None self.key_sequences.clear() if self.key_sequences: self.emulate_keys(KeySequences.from_sequences(self.key_sequences).action_atoms) self.key_sequences.clear() def emulate_keys(self, keys): print(keys) for action_atom in keys: if action_atom.type == ActionType.PRESS: if action_atom.extended: win32api.keybd_event(action_atom.info, 0, win32con.KEYEVENTF_EXTENDEDKEY | 0, 0) else: win32api.keybd_event(action_atom.info, 0, 0, 0) # win32api.keybd_event(action_atom.info, 0, win32con.KEYEVENTF_EXTENDEDKEY | win32con.KEYEVENTF_KEYUP, 0) win32api.keybd_event(action_atom.info, 0, win32con.KEYEVENTF_KEYUP, 0) elif action_atom.type == ActionType.DOWN: win32api.keybd_event(action_atom.info, 0, 0, 0) elif action_atom.type == ActionType.UP: win32api.keybd_event(action_atom.info, 0, win32con.KEYEVENTF_KEYUP, 0) else: func_arg = action_atom.info.args if func_arg is not None: getattr(self, self.func_dict[action_atom.info.text])(func_arg) else: getattr(self, self.func_dict[action_atom.info.text])() def search(self, key_text, exe_name): # self.print_dict(self.current_node.dictionary) print(exe_name) self.waiting_return_key = "" if not(self.timer_running): try: self.current_node = self.current_node.dictionary[exe_name] except KeyError: # マッチする実行ファイル名が登録されていない場合 self.timer_stop() return try: node = self.current_node.dictionary[key_text] self.current_node = node # ノードの辞書が空で、結果が確定する場合 if not(node.dictionary): self.key_sequences.clear() self.timer_stop() self.emulate_keys(node.return_key) return # 結果の候補が存在し、一定時間待機する必要がある場合 if node.has_return_key: self.timer_start() self.waiting_return_key = node.return_key return # マッチしたが次の入力がなければ結果が確定しない場合 self.timer_start() return # マッチするキーが存在しない場合 except KeyError: if len(self.key_sequences) == 1: self.key_sequences.clear() self.timer_stop() return SearchResultType.NOT_MATCH_BREAK self.timer_stop() return def on_keyboard_event(self, event): # 自分でエミュレートしたイベントならそのままイベントを通す if event.Injected: # print(event.KeyID) return True # デバッグ用にescを押したら終了するようにしておく if event.KeyID == 27: self.timer.stop() self.about_to_quit.emit() return False window_id = event.Window # 前回のイベントとwindow_idが異なれば if self.window_id != window_id: self.window_id = window_id # キーイベントを受け取るアプリケーションのexe名を取得 self.current_exe_name = self.get_exe_name(window_id) print(self.current_exe_name) code = event.KeyID # event.Transitionはpressなら0, releaseなら128 is_press_event = not(event.Transition) # モディファイアーが押されているかチェックし、modifiers_key_stateの状態を更新する pressed_modifier = self.modifiers_key_state.pressing(code, is_press_event) # モディファイアーが押されていれば以降の処理をしない if pressed_modifier: print("modifier", code) return True # キーがReleaseされるイベントなら以降の処理をしない if not(is_press_event): return True # キーコードとモディファイアーの状態をもとにキーシークエンスを生成 seq = KeySequence.from_code(code, self.modifiers_key_state.presses()) self.key_sequences.append(seq) search_result = self.search(seq.text, self.current_exe_name) if search_result == SearchResultType.NOT_MATCH_BREAK: return True # Falseを返すとイベントを無視し、Trueを返すとイベントを処理する return False def get_exe_name(self, window_id): try: base_exe_name = self.window_dict[window_id] except KeyError: # プロセスIDを取得 thread_id, process_id = GetWindowThreadProcessId(window_id) # プロセスハンドルを取得 process_handle = OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION, 0, process_id) # キーを受け取るアプリケーションのexe名を取得 name = GetModuleFileNameEx(process_handle, 0) # .exeを除く base_exe_name = os.path.basename(name)[:-4] self.window_dict[window_id] = base_exe_name return base_exe_name def _window_enum_callback(self, hwnd, wildcard): is_visible = win32gui.IsWindowVisible(hwnd) if not(is_visible): return title = str(win32gui.GetWindowText(hwnd)) if title != "" and title != "Program Manager": try: exe_name = self.get_exe_name(hwnd) except: exe_name = "" self.candidates.append((title, exe_name, hwnd)) def find_window_wildcard(self): self.candidates.clear() win32gui.EnumWindows(self._window_enum_callback, "") @FuncDictDecorator.inner_func("search") def search_exe(self): self.find_window_wildcard() self.about_to_search.emit(self.candidates) def key_unlock(self): # キーボードフックを解除する self.hook_manager.UnhookKeyboard() self.thread().quit() def keylock(self): # キーボードフックを開始する self.hook_manager.HookKeyboard() pythoncom.PumpWaitingMessages() def print_dict(self, d, depth = 0): def print_indent(depth, *args, **kwargs): print(" "*8*depth, *args, **kwargs) for x, y in d.items(): if len(y.dictionary): print_indent(depth, x) self.print_dict(y.dictionary, depth + 1) if y.has_return_key: print_indent(depth, x, y.return_key) def on_timeout(self): print("timeout") self.timer_stop()