#Boa:Frame:Frame1 import os import sys import wx import time import threading import datetime import json import traceback from pynput import mouse from pynput import keyboard from pynput.mouse import Button from pynput.keyboard import Key, KeyCode PY2 = PY3 = False try: import StringIO from wx import TaskBarIcon as wxTaskBarIcon from wx import EVT_TASKBAR_LEFT_DCLICK PY2 = True except: import io from wx.adv import TaskBarIcon as wxTaskBarIcon from wx.adv import EVT_TASKBAR_LEFT_DCLICK PY3 = True # # anther way to resolve DPI Scaling on win10 # try: # import ctypes # ctypes.c_int() # ctypes.windll.shcore.SetProcessDpiAwareness(2) # except Exception as e: # print(e) wx.NO_3D = 0 KEYS = ['F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12'] def GetMondrianStream(): if PY2: data = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\x00qIDATX\x85\xed\xd6;\n\x800\x10E\xd1{\xc5\x8d\xb9r\x97\x16\x0b\xad$\x8a\x82:\x16o\xda\x84pB2\x1f\x81Fa\x8c\x9c\x08\x04Z{\xcf\xa72\xbcv\xfa\xc5\x08 \x80r\x80\xfc\xa2\x0e\x1c\xe4\xba\xfaX\x1d\xd0\xde]S\x07\x02\xd8>\xe1wa-`\x9fQ\xe9\x86\x01\x04\x10\x00\\(Dk\x1b-\x04\xdc\x1d\x07\x14\x98;\x0bS\x7f\x7f\xf9\x13\x04\x10@\xf9X\xbe\x00\xc9 \x14K\xc1<={\x00\x00\x00\x00IEND\xaeB`\x82' stream = StringIO.StringIO(data) else: data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\x00qIDATX\x85\xed\xd6;\n\x800\x10E\xd1{\xc5\x8d\xb9r\x97\x16\x0b\xad$\x8a\x82:\x16o\xda\x84pB2\x1f\x81Fa\x8c\x9c\x08\x04Z{\xcf\xa72\xbcv\xfa\xc5\x08 \x80r\x80\xfc\xa2\x0e\x1c\xe4\xba\xfaX\x1d\xd0\xde]S\x07\x02\xd8>\xe1wa-`\x9fQ\xe9\x86\x01\x04\x10\x00\\(Dk\x1b-\x04\xdc\x1d\x07\x14\x98;\x0bS\x7f\x7f\xf9\x13\x04\x10@\xf9X\xbe\x00\xc9 \x14K\xc1<={\x00\x00\x00\x00IEND\xaeB`\x82' stream = io.BytesIO(data) return stream def GetMondrianBitmap(): stream = GetMondrianStream() image = wx.ImageFromStream(stream) return wx.BitmapFromImage(image) def GetMondrianIcon(): icon = wx.EmptyIcon() icon.CopyFromBitmap(GetMondrianBitmap()) return icon def create(parent): return Frame1(parent) [wxID_FRAME1, wxID_FRAME1BTRECORD, wxID_FRAME1BTRUN, wxID_FRAME1BTPAUSE, wxID_FRAME1BUTTON1, wxID_FRAME1CHOICE_SCRIPT, wxID_FRAME1CHOICE_START, wxID_FRAME1CHOICE_STOP, wxID_FRAME1PANEL1, wxID_FRAME1STATICTEXT1, wxID_FRAME1STATICTEXT2, wxID_FRAME1STATICTEXT3, wxID_FRAME1STATICTEXT4, wxID_FRAME1STIMES, wxID_FRAME1TEXTCTRL1, wxID_FRAME1TEXTCTRL2, wxID_FRAME1TNUMRD, wxID_FRAME1TSTOP, wxID_FRAME1STATICTEXT5, wxID_FRAME1TEXTCTRL3, ] = [wx.NewId() for _init_ctrls in range(20)] class Frame1(wx.Frame): def _init_ctrls(self, prnt): # generated method, don't edit wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt, pos=wx.Point(506, 283), size=wx.Size(366, 201), style=wx.STAY_ON_TOP | wx.DEFAULT_FRAME_STYLE, title='Keymouse Go') self.SetClientSize(wx.Size(350, 205)) self.panel1 = wx.Panel(id=wxID_FRAME1PANEL1, name='panel1', parent=self, pos=wx.Point(0, 0), size=wx.Size(350, 205), style=wx.NO_3D | wx.CAPTION) self.btrecord = wx.Button(id=wxID_FRAME1BTRECORD, label=u'\u5f55\u5236', name='btrecord', parent=self.panel1, pos=wx.Point(202, 12), size=wx.Size(56, 32), style=0) self.btrecord.Bind(wx.EVT_BUTTON, self.OnBtrecordButton, id=wxID_FRAME1BTRECORD) self.btrun = wx.Button(id=wxID_FRAME1BTRUN, label=u'\u542f\u52a8', name='btrun', parent=self.panel1, pos=wx.Point(274, 12), size=wx.Size(56, 32), style=0) self.btrun.Bind(wx.EVT_BUTTON, self.OnBtrunButton, id=wxID_FRAME1BTRUN) # 暂停/继续 功能不适合用按钮的形式来做,所以暂时隐去 # self.btpause = wx.Button(id=wxID_FRAME1BTPAUSE, label=u'\u6682\u505c', # name='btpause', parent=self.panel1, pos=wx.Point(274, 141), # size=wx.Size(56, 32), style=0) # self.btpause.Bind(wx.EVT_BUTTON, self.OnBtpauseButton, id=wxID_FRAME1BTPAUSE) self.tnumrd = wx.StaticText(id=wxID_FRAME1TNUMRD, label=u'ready..', name='tnumrd', parent=self.panel1, pos=wx.Point(17, 175), size=wx.Size(100, 36), style=0) self.button1 = wx.Button(id=wxID_FRAME1BUTTON1, label=u'test', name='button1', parent=self.panel1, pos=wx.Point(128, 296), size=wx.Size(75, 24), style=0) self.button1.Bind(wx.EVT_BUTTON, self.OnButton1Button, id=wxID_FRAME1BUTTON1) self.tstop = wx.StaticText(id=wxID_FRAME1TSTOP, label=u'If you want to stop it, Press F12', name='tstop', parent=self.panel1, pos=wx.Point(25, 332), size=wx.Size(183, 18), style=0) self.tstop.Show(False) self.stimes = wx.SpinCtrl(id=wxID_FRAME1STIMES, initial=0, max=1000, min=0, name='stimes', parent=self.panel1, pos=wx.Point(206, 101), size=wx.Size(45, 18), style=wx.SP_ARROW_KEYS) self.stimes.SetValue(1) self.label_run_times = wx.StaticText(id=wxID_FRAME1STATICTEXT2, label=u'\u6267\u884c\u6b21\u6570(0\u4e3a\u65e0\u9650\u5faa\u73af)', name='label_run_times', parent=self.panel1, pos=wx.Point(203, 61), size=wx.Size(136, 26), style=0) self.textCtrl1 = wx.TextCtrl(id=wxID_FRAME1TEXTCTRL1, name='textCtrl1', parent=self.panel1, pos=wx.Point(24, 296), size=wx.Size(40, 22), style=0, value='119') self.textCtrl2 = wx.TextCtrl(id=wxID_FRAME1TEXTCTRL2, name='textCtrl2', parent=self.panel1, pos=wx.Point(80, 296), size=wx.Size(36, 22), style=0, value='123') self.label_script = wx.StaticText(id=wxID_FRAME1STATICTEXT3, label=u'\u811a\u672c', name='label_script', parent=self.panel1, pos=wx.Point(17, 20), size=wx.Size(40, 32), style=0) self.choice_script = wx.Choice(choices=[], id=wxID_FRAME1CHOICE_SCRIPT, name=u'choice_script', parent=self.panel1, pos=wx.Point(79, 15), size=wx.Size(108, 25), style=0) self.label_start_key = wx.StaticText(id=wxID_FRAME1STATICTEXT1, label=u'\u542f\u52a8\u70ed\u952e', name='label_start_key', parent=self.panel1, pos=wx.Point(16, 63), size=wx.Size(56, 24), style=0) self.label_stop_key = wx.StaticText(id=wxID_FRAME1STATICTEXT4, label=u'\u7ec8\u6b62\u70ed\u952e', name='label_stop_key', parent=self.panel1, pos=wx.Point(16, 102), size=wx.Size(56, 32), style=0) self.choice_start = wx.Choice(choices=[], id=wxID_FRAME1CHOICE_START, name=u'choice_start', parent=self.panel1, pos=wx.Point(79, 58), size=wx.Size(108, 25), style=0) self.choice_start.SetLabel(u'') self.choice_start.SetLabelText(u'') self.choice_start.Bind(wx.EVT_CHOICE, self.OnChoice_startChoice, id=wxID_FRAME1CHOICE_START) self.choice_stop = wx.Choice(choices=[], id=wxID_FRAME1CHOICE_STOP, name=u'choice_stop', parent=self.panel1, pos=wx.Point(79, 98), size=wx.Size(108, 25), style=0) self.choice_stop.Bind(wx.EVT_CHOICE, self.OnChoice_stopChoice, id=wxID_FRAME1CHOICE_STOP) # ===== if use SetProcessDpiAwareness, comment below ===== self.label_scale = wx.StaticText(id=wxID_FRAME1STATICTEXT5, label=u'\u5c4f\u5e55\u7f29\u653e', name='staticText5', parent=self.panel1, pos=wx.Point(16, 141), size=wx.Size(56, 32), style=0) self.text_scale = wx.TextCtrl(id=wxID_FRAME1TEXTCTRL3, name='textCtrl3', parent=self.panel1, pos=wx.Point(79, 138), size=wx.Size(108, 22), style=0, value='100%') # ========================================================= def __init__(self, parent): self._init_ctrls(parent) self.SetIcon(GetMondrianIcon()) self.taskBarIcon = TaskBarIcon(self) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_ICONIZE, self.OnIconfiy) if not os.path.exists('scripts'): os.mkdir('scripts') self.scripts = os.listdir('scripts')[::-1] self.choice_script.SetItems(self.scripts) self.scripts = list(filter(lambda s: s.endswith('.txt'), self.scripts)) if self.scripts: self.choice_script.SetSelection(0) self.choice_start.SetItems(KEYS) self.choice_start.SetSelection(3) self.choice_stop.SetItems(KEYS) self.choice_stop.SetSelection(6) self.running = False self.recording = False self.record = [] self.ttt = self.now_ts # for pause-resume feature self.paused = False self.pause_event = threading.Event() # =========== create mouse listener for record =========== def on_move(x, y): if not self.recording or self.running: return True def on_scroll(x, y, dx, dy): if not self.recording or self.running: return True def on_click(x, y, button, pressed): if not self.recording or self.running: return True # ===== if use SetProcessDpiAwareness, comment below ===== try: scale = self.text_scale.GetValue() scale = scale.replace('%', '').replace('-', '').strip() scale = float(scale) scale = scale / 100.0 except: scale = 1 x = int(x / scale) y = int(y / scale) # ========================================================= print('mouse click:', x, y, button.name, pressed) delay = self.now_ts - self.ttt self.ttt = self.now_ts if not self.record: delay = 0 pos = (x, y) if button.name == 'left': message = 'mouse left ' elif button.name == 'right': message = 'mouse right ' else: return True if pressed: message += 'down' else: message += 'up' self.record.append([delay, 'EM', message, pos]) text = self.tnumrd.GetLabel() text = text.replace(' actions recorded','') text = str(eval(text)+1) text = text + ' actions recorded' self.tnumrd.SetLabel(text) return True # =========== create keyboard listener for record =========== def key_event(key, is_press): if not self.recording or self.running: return True if is_press: print('keyboard press:', key) message = 'key down' else: print('keyboard release:', key) message = 'key up' if isinstance(key, Key): print('Key:', key.name, key.value.vk) name = key.name elif isinstance(key, KeyCode): print('KeyCode:', key.char, key.vk) name = key.char else: assert False delay = self.now_ts - self.ttt self.ttt = self.now_ts if not self.record: delay = 0 self.record.append([delay, 'EK', message, name]) text = self.tnumrd.GetLabel() text = text.replace(' actions recorded', '') text = str(eval(text) + 1) text = text + ' actions recorded' self.tnumrd.SetLabel(text) return True def on_press(key): return key_event(key, True) def on_release(key): print('=====',key) if not self.recording: # listen for start/stop script start_name = 'f6' stop_name = 'f9' start_index = self.choice_start.GetSelection() start_name = KEYS[start_index].lower() stop_index = self.choice_stop.GetSelection() stop_name = KEYS[stop_index].lower() print(start_name, stop_name, key) if not isinstance(key, Key): return True if key.name == start_name and not self.running: print('script start') t = RunScriptClass(self, self.pause_event) t.start() elif key.name == stop_name and self.running: print('script stop') self.tnumrd.SetLabel('broken') return key_event(key, False) self.mouse_listener = mouse.Listener( on_move=on_move, on_scroll=on_scroll, on_click=on_click ) self.keyboard_listener = keyboard.Listener( on_press=on_press, on_release=on_release) self.mouse_listener.start() self.keyboard_listener.start() @property def now_ts(self): return int(time.time() * 1000) def get_script_path(self): i = self.choice_script.GetSelection() if i < 0: return '' script = self.scripts[i] path = os.path.join(os.getcwd(), 'scripts', script) print(path) return path def new_script_path(self): now = datetime.datetime.now() script = '%s.txt' % now.strftime('%m%d_%H%M') if script in self.scripts: script = '%s.txt' % now.strftime('%m%d_%H%M%S') self.scripts.insert(0, script) self.choice_script.SetItems(self.scripts) self.choice_script.SetSelection(0) return self.get_script_path() def OnHide(self, event): self.Hide() event.Skip() def OnIconfiy(self, event): self.Hide() event.Skip() def OnClose(self, event): try: self.mouse_listener.stop() self.keyboard_listener.stop() except: pass self.taskBarIcon.Destroy() self.Destroy() event.Skip() def OnButton1Button(self, event): event.Skip() def OnBtrecordButton(self, event): if self.recording: print('record stop') self.recording = False self.record = self.record[:-2] output = json.dumps(self.record, indent=1) output = output.replace('\r\n', '\n').replace('\r', '\n') output = output.replace('\n ', '').replace('\n ', '') output = output.replace('\n ]', ']') open(self.new_script_path(), 'w').write(output) self.btrecord.SetLabel(u'\u5f55\u5236') self.tnumrd.SetLabel('finished') self.record = [] else: print('record start') self.recording = True self.ttt = self.now_ts status = self.tnumrd.GetLabel() if 'running' in status or 'recorded' in status: return self.btrecord.SetLabel(u'\u7ed3\u675f') # 结束 self.tnumrd.SetLabel('0 actions recorded') self.choice_script.SetSelection(-1) self.record = [] event.Skip() def OnBtrunButton(self, event): print('script start by btn') t = RunScriptClass(self, self.pause_event) t.start() event.Skip() def OnBtpauseButton(self, event): print('script pause button pressed') if self.paused: print('script is resumed') self.pause_event.set() self.paused = False self.btpause.SetLabel(u'\u6682\u505c') # 暂停 else: print('script is paused') self.pause_event.clear() self.paused = True self.btpause.SetLabel(u'\u7ee7\u7eed') # 继续 event.Skip() def OnChoice_startChoice(self, event): event.Skip() def OnChoice_stopChoice(self, event): event.Skip() class RunScriptClass(threading.Thread): def __init__(self, frame: Frame1, event: threading.Event): self.frame = frame self.event = event self.event.set() super(RunScriptClass, self).__init__() def run(self): status = self.frame.tnumrd.GetLabel() if self.frame.running or self.frame.recording: return if 'running' in status or 'recorded' in status: return script_path = self.frame.get_script_path() if not script_path: self.frame.tnumrd.SetLabel('script not found, please self.record first!') return self.frame.running = True try: s = open(script_path, 'r').read() s = json.loads(s) steps = len(s) run_times = self.frame.stimes.Value running_text = '%s running..' % script_path.split('/')[-1].split('\\')[-1] self.frame.tnumrd.SetLabel(running_text) self.frame.tstop.Shown = True mouse_ctl = mouse.Controller() keyboard_ctl = keyboard.Controller() j = 0 while j < run_times or run_times == 0: j += 1 if self.frame.tnumrd.GetLabel() == 'broken' or self.frame.tnumrd.GetLabel() == 'finished': self.frame.running = False break for i in range(steps): self.event.wait() print(s[i]) # for old style script if isinstance(s[i][0], str) and isinstance(s[i][3], int): s[i].insert(0, s[i][3]) delay = s[i][0] event_type = s[i][1] message = s[i][2] action = s[i][3] message = message.lower() time.sleep(delay / 1000.0) if self.frame.tnumrd.GetLabel() == 'broken' or self.frame.tnumrd.GetLabel() == 'finished': break text = '%s [%d/%d %d/%d]' % (running_text, i+1, steps, j, run_times) self.frame.tnumrd.SetLabel(text) if event_type == 'EM': x, y = action mouse_ctl.position = (x, y) if message == 'mouse left down': mouse_ctl.press(Button.left) elif message == 'mouse left up': mouse_ctl.release(Button.left) elif message == 'mouse right down': mouse_ctl.press(Button.right) elif message == 'mouse right up': mouse_ctl.release(Button.right) else: print('unknow mouse event:', message) elif event_type == 'EK': key_name = action if len(key_name) == 1: key = key_name else: key = getattr(Key, key_name) if message == 'key down': keyboard_ctl.press(key) elif message == 'key up': keyboard_ctl.release(key) else: print('unknow keyboard event:', message) self.frame.tnumrd.SetLabel('finished') self.frame.tstop.Shown = False self.frame.running = False print('script run finish!') except Exception as e: print('run error', e) traceback.print_exc() self.frame.tnumrd.SetLabel('failed') self.frame.tstop.Shown = False self.frame.running = False class TaskBarIcon(wxTaskBarIcon): ID_About = wx.NewId() ID_Closeshow = wx.NewId() def __init__(self, frame): wxTaskBarIcon.__init__(self) self.frame = frame self.SetIcon(GetMondrianIcon()) self.Bind(EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarLeftDClick) self.Bind(wx.EVT_MENU, self.OnAbout, id=self.ID_About) self.Bind(wx.EVT_MENU, self.OnCloseshow, id=self.ID_Closeshow) def OnTaskBarLeftDClick(self, event): if self.frame.IsIconized(): self.frame.Iconize(False) if not self.frame.IsShown(): self.frame.Show(True) self.frame.Raise() def OnAbout(self, event): wx.MessageBox('https://github.com/taojy123/KeymouseGo', 'KeymouseGo') event.Skip() def OnCloseshow(self, event): self.frame.Close(True) event.Skip() def CreatePopupMenu(self): menu = wx.Menu() menu.Append(self.ID_About, 'About') menu.Append(self.ID_Closeshow, 'Exit') return menu