From 2f1d586946c1af948e8409759c9f1c5a03fca94b Mon Sep 17 00:00:00 2001 From: coolneng Date: Tue, 28 Jul 2020 13:24:50 +0200 Subject: [PATCH] Rewrite using mouse library --- mouse/__init__.py | 276 ++++++++++++++++++ mouse/__init__.pyc | Bin 0 -> 12107 bytes mouse/__main__.py | 27 ++ mouse/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 9978 bytes mouse/__pycache__/_generic.cpython-38.pyc | Bin 0 -> 2485 bytes mouse/__pycache__/_mouse_event.cpython-38.pyc | Bin 0 -> 547 bytes mouse/__pycache__/_nixcommon.cpython-38.pyc | Bin 0 -> 5385 bytes mouse/__pycache__/_nixmouse.cpython-38.pyc | Bin 0 -> 3578 bytes mouse/_generic.py | 72 +++++ mouse/_generic.pyc | Bin 0 -> 2992 bytes mouse/_mouse_event.py | 20 ++ mouse/_mouse_event.pyc | Bin 0 -> 694 bytes mouse/_mouse_tests.py | 271 +++++++++++++++++ mouse/_nixcommon.py | 171 +++++++++++ mouse/_nixcommon.pyc | Bin 0 -> 6782 bytes mouse/_nixmouse.py | 131 +++++++++ mouse/_nixmouse.pyc | Bin 0 -> 4889 bytes mouse/_winmouse.py | 216 ++++++++++++++ proarbeit.py | 24 +- shell.nix | 2 +- 20 files changed, 1196 insertions(+), 14 deletions(-) create mode 100644 mouse/__init__.py create mode 100644 mouse/__init__.pyc create mode 100644 mouse/__main__.py create mode 100644 mouse/__pycache__/__init__.cpython-38.pyc create mode 100644 mouse/__pycache__/_generic.cpython-38.pyc create mode 100644 mouse/__pycache__/_mouse_event.cpython-38.pyc create mode 100644 mouse/__pycache__/_nixcommon.cpython-38.pyc create mode 100644 mouse/__pycache__/_nixmouse.cpython-38.pyc create mode 100644 mouse/_generic.py create mode 100644 mouse/_generic.pyc create mode 100644 mouse/_mouse_event.py create mode 100644 mouse/_mouse_event.pyc create mode 100644 mouse/_mouse_tests.py create mode 100644 mouse/_nixcommon.py create mode 100644 mouse/_nixcommon.pyc create mode 100644 mouse/_nixmouse.py create mode 100644 mouse/_nixmouse.pyc create mode 100644 mouse/_winmouse.py diff --git a/mouse/__init__.py b/mouse/__init__.py new file mode 100644 index 0000000..71a9d14 --- /dev/null +++ b/mouse/__init__.py @@ -0,0 +1,276 @@ +# -*- coding: utf-8 -*- +""" +mouse +===== + +Take full control of your mouse with this small Python library. Hook global events, register hotkeys, simulate mouse movement and clicks, and much more. + +_Huge thanks to [Kirill Pavlov](http://kirillpavlov.com/) for donating the package name. If you are looking for the Cheddargetter.com client implementation, [`pip install mouse==0.5.0`](https://pypi.python.org/pypi/mouse/0.5.0)._ + +## Features + +- Global event hook on all mice devices (captures events regardless of focus). +- **Listen** and **sends** mouse events. +- Works with **Windows** and **Linux** (requires sudo). +- **Pure Python**, no C modules to be compiled. +- **Zero dependencies**. Trivial to install and deploy, just copy the files. +- **Python 2 and 3**. +- Includes **high level API** (e.g. [record](#mouse.record) and [play](#mouse.play). +- Events automatically captured in separate thread, doesn't block main program. +- Tested and documented. + +This program makes no attempt to hide itself, so don't use it for keyloggers. + +## Usage + +Install the [PyPI package](https://pypi.python.org/pypi/mouse/): + + $ sudo pip install mouse + +or clone the repository (no installation required, source files are sufficient): + + $ git clone https://github.com/boppreh/mouse + +Then check the [API docs](https://github.com/boppreh/mouse#api) to see what features are available. + + +## Known limitations: + +- Events generated under Windows don't report device id (`event.device == None`). [#21](https://github.com/boppreh/keyboard/issues/21) +- To avoid depending on X the Linux parts reads raw device files (`/dev/input/input*`) but this requries root. +- Other applications, such as some games, may register hooks that swallow all key events. In this case `mouse` will be unable to report events. +""" +# TODO +# - infinite wait +# - mouse.on_move +version = '0.7.1' + +import time as _time + +import platform as _platform +if _platform.system() == 'Windows': + from. import _winmouse as _os_mouse +elif _platform.system() == 'Linux': + from. import _nixmouse as _os_mouse +else: + raise OSError("Unsupported platform '{}'".format(_platform.system())) + +from ._mouse_event import ButtonEvent, MoveEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE +from ._generic import GenericListener as _GenericListener + +_pressed_events = set() +class _MouseListener(_GenericListener): + def init(self): + _os_mouse.init() + def pre_process_event(self, event): + if isinstance(event, ButtonEvent): + if event.event_type in (UP, DOUBLE): + _pressed_events.discard(event.button) + else: + _pressed_events.add(event.button) + return True + + def listen(self): + _os_mouse.listen(self.queue) + +_listener = _MouseListener() + +def is_pressed(button=LEFT): + """ Returns True if the given button is currently pressed. """ + _listener.start_if_necessary() + return button in _pressed_events + +def press(button=LEFT): + """ Presses the given button (but doesn't release). """ + _os_mouse.press(button) + +def release(button=LEFT): + """ Releases the given button. """ + _os_mouse.release(button) + +def click(button=LEFT): + """ Sends a click with the given button. """ + _os_mouse.press(button) + _os_mouse.release(button) + +def double_click(button=LEFT): + """ Sends a double click with the given button. """ + click(button) + click(button) + +def right_click(): + """ Sends a right click with the given button. """ + click(RIGHT) + +def wheel(delta=1): + """ Scrolls the wheel `delta` clicks. Sign indicates direction. """ + _os_mouse.wheel(delta) + +def move(x, y, absolute=True, duration=0): + """ + Moves the mouse. If `absolute`, to position (x, y), otherwise move relative + to the current position. If `duration` is non-zero, animates the movement. + """ + x = int(x) + y = int(y) + + # Requires an extra system call on Linux, but `move_relative` is measured + # in millimiters so we would lose precision. + position_x, position_y = get_position() + + if not absolute: + x = position_x + x + y = position_y + y + + if duration: + start_x = position_x + start_y = position_y + dx = x - start_x + dy = y - start_y + + if dx == 0 and dy == 0: + _time.sleep(duration) + else: + # 120 movements per second. + # Round and keep float to ensure float division in Python 2 + steps = max(1.0, float(int(duration * 120.0))) + for i in range(int(steps)+1): + move(start_x + dx*i/steps, start_y + dy*i/steps) + _time.sleep(duration/steps) + else: + _os_mouse.move_to(x, y) + +def drag(start_x, start_y, end_x, end_y, absolute=True, duration=0): + """ + Holds the left mouse button, moving from start to end position, then + releases. `absolute` and `duration` are parameters regarding the mouse + movement. + """ + if is_pressed(): + release() + move(start_x, start_y, absolute, 0) + press() + move(end_x, end_y, absolute, duration) + release() + +def on_button(callback, args=(), buttons=(LEFT, MIDDLE, RIGHT, X, X2), types=(UP, DOWN, DOUBLE)): + """ Invokes `callback` with `args` when the specified event happens. """ + if not isinstance(buttons, (tuple, list)): + buttons = (buttons,) + if not isinstance(types, (tuple, list)): + types = (types,) + + def handler(event): + if isinstance(event, ButtonEvent): + if event.event_type in types and event.button in buttons: + callback(*args) + _listener.add_handler(handler) + return handler + +def on_pressed(callback, args=()): + """ Invokes `callback` with `args` when the left button is pressed. """ + return on_button(callback, args, [LEFT], [DOWN]) + +def on_click(callback, args=()): + """ Invokes `callback` with `args` when the left button is clicked. """ + return on_button(callback, args, [LEFT], [UP]) + +def on_double_click(callback, args=()): + """ + Invokes `callback` with `args` when the left button is double clicked. + """ + return on_button(callback, args, [LEFT], [DOUBLE]) + +def on_right_click(callback, args=()): + """ Invokes `callback` with `args` when the right button is clicked. """ + return on_button(callback, args, [RIGHT], [UP]) + +def on_middle_click(callback, args=()): + """ Invokes `callback` with `args` when the middle button is clicked. """ + return on_button(callback, args, [MIDDLE], [UP]) + +def wait(button=LEFT, target_types=(UP, DOWN, DOUBLE)): + """ + Blocks program execution until the given button performs an event. + """ + from threading import Lock + lock = Lock() + lock.acquire() + handler = on_button(lock.release, (), [button], target_types) + lock.acquire() + _listener.remove_handler(handler) + +def get_position(): + """ Returns the (x, y) mouse position. """ + return _os_mouse.get_position() + +def hook(callback): + """ + Installs a global listener on all available mouses, invoking `callback` + each time it is moved, a key status changes or the wheel is spun. A mouse + event is passed as argument, with type either `mouse.ButtonEvent`, + `mouse.WheelEvent` or `mouse.MoveEvent`. + + Returns the given callback for easier development. + """ + _listener.add_handler(callback) + return callback + +def unhook(callback): + """ + Removes a previously installed hook. + """ + _listener.remove_handler(callback) + +def unhook_all(): + """ + Removes all hooks registered by this application. Note this may include + hooks installed by high level functions, such as `record`. + """ + del _listener.handlers[:] + +def record(button=RIGHT, target_types=(DOWN,)): + """ + Records all mouse events until the user presses the given button. + Then returns the list of events recorded. Pairs well with `play(events)`. + + Note: this is a blocking function. + Note: for more details on the mouse hook and events see `hook`. + """ + recorded = [] + hook(recorded.append) + wait(button=button, target_types=target_types) + unhook(recorded.append) + return recorded + +def play(events, speed_factor=1.0, include_clicks=True, include_moves=True, include_wheel=True): + """ + Plays a sequence of recorded events, maintaining the relative time + intervals. If speed_factor is <= 0 then the actions are replayed as fast + as the OS allows. Pairs well with `record()`. + + The parameters `include_*` define if events of that type should be inluded + in the replay or ignored. + """ + last_time = None + for event in events: + if speed_factor > 0 and last_time is not None: + _time.sleep((event.time - last_time) / speed_factor) + last_time = event.time + + if isinstance(event, ButtonEvent) and include_clicks: + if event.event_type == UP: + _os_mouse.release(event.button) + else: + _os_mouse.press(event.button) + elif isinstance(event, MoveEvent) and include_moves: + _os_mouse.move_to(event.x, event.y) + elif isinstance(event, WheelEvent) and include_wheel: + _os_mouse.wheel(event.delta) + +replay = play +hold = press + +if __name__ == '__main__': + print('Recording... Double click to stop and replay.') + play(record()) diff --git a/mouse/__init__.pyc b/mouse/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e8936fcea774771629d0e2498c2c6f0ce753dce GIT binary patch literal 12107 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
mRr`f>+-1;pbRvKD4XlMPY{MMjf?9>nu z9*+-B=p0@+Z;Bb;F?b#Zcmm4=IGbYj#RF4#VDcA-k%8vn6P77@D71p#ZN!G=?w;We()azZX|^y|e#&FBYwate$pX^aJNl4p{vJIodwm(PF|{-S?99? zq5*<6a(s%)RVJSiP&RR98oSRK1eSY$NC&(=@`=N0FO}LCguj0P+j5^al!7xtEJ+bc ztL74eJrDp!myTXd=;XhUHlp%?v=a^Y$=o27+Nnd`;0TT8<#3@gZM?42|_PmU=pHhq$D7TsJ<>eU+b&3Xt-_l6Z zN1kyN`s$@1MsB`ou$;>m`He`;O`8Lu1sqxUu465^>plXJT0zO9QhH??zdqtePteu^ z-x8wE zG3dUP{8_&Qi9soDdiVcBh7QQQa%iRxX<8utit$YTE15SV^A1XTh)=!ygV~2=HfCV+ z7(}h}h|D`GB93|Ze+LMalKi8-BridbC@dDASm4trL-Fe~sFXu~MuD0lu6|jlU*r^* zCbEa;Cf^7Wlqm428_E|rbzTiZe2qgt_1-1@I>RX^Vi-n{g+(EK_>uw}a6W7`u!R7F zKchC>TBd&Co3N+IzuRJMAp1}yeqqwDVzo*G)|_uaz*l2@B8213Qka3Ed<4P}O53%p zm%t`sLdq|=q7oh1f!iIuA}an+mNIXWI_%AwoRCwb`0b3(qaB~VL|hVmy;LVgDk%)5 zT&yGEo+t$aC7H}%a_cHF3hJxci(T2Z|)@NU?Oj?^1%f*q?MB`GRKu|FS945fE(<6&gLeYTWnZ;a7LYBx=(X2=H2IssQ*(o zZ8Smy$C@%QOCtYeJa~dTyN>oqkoVlj6+fn4SZ-``e6liKnf8vt>f@@MuH5hrSEjs^ y2d6626Vv1PH!(dig};YhIW#kWP(~)%&K{T=pPHVUoZ5Hv;ECr@`UjK=?|%Rz!ib0f literal 0 HcmV?d00001 diff --git a/mouse/__main__.py b/mouse/__main__.py new file mode 100644 index 0000000..1a84def --- /dev/null +++ b/mouse/__main__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +import mouse +import fileinput +import json +import sys + +class_by_name = { + 'ButtonEvent': mouse.ButtonEvent, + 'WheelEvent': mouse.WheelEvent, + 'MoveEvent': mouse.MoveEvent, +} + +def print_event_json(event): + # Could use json.dumps(event.__dict__()), but this way we guarantee semantic order. + d = event._asdict() + d['event_class'] = event.__class__.__name__ + print(json.dumps(d)) + sys.stdout.flush() +mouse.hook(print_event_json) + +def load(line): + d = json.loads(line) + class_ = class_by_name[d['event_class']] + del d['event_class'] + return class_(**d) + +mouse.play(load(line) for line in fileinput.input()) \ No newline at end of file diff --git a/mouse/__pycache__/__init__.cpython-38.pyc b/mouse/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80e1dc4810ff8e2626acd2bf5959f55fd0060f5f GIT binary patch literal 9978 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 YIXzynD-)GcrF>|z`W#YhRu!`LKjl9Wb^rhX literal 0 HcmV?d00001 diff --git a/mouse/__pycache__/_generic.cpython-38.pyc b/mouse/__pycache__/_generic.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f58ab70375696765d62678a5376aa791069365ef GIT binary patch literal 2485 zcmZ`)OK%%D5GJ_~tt4A^5~B^!G-Y~dAT+QG1i1x4g1p)yFw(|F90A34y`-&;SGzJv zsqw<@q0Y7cK{@&_@!C^t zBM;%%yHE)tXif&SM+dCOD2`d~^*q=5J-=Foo|gxMuoqJDniSk8va#P%FDb##NyA$D zh7@d@JSV~z!FwVC8J&2&reLo~H~a`^kuJ00R$s|PSpP+t?r(Of4SpKQkxWs1zAzX( zgkS#%l_WhXNRLVHg!Ftt1$$3=f$)S6Z72c}Lfa4x5kVV?rf5Oi6m78tZA&bR4zz8t z0vB1eE6-&iRhGWUw836V;e=ViJ|6uTetjLPJ#s{1zzAw;*a?mR8`y|#KI{56>L-QB zrP8*UyZy3aPx-LU1_x-@DYd?q`R*xEZJ7P{?nb{H$c?lt^FkJT8!uJ)R;EU83{{z^ z9hsSpK{?WLBi^fTxjsC!Q5aI#r2D<*P-TUQ<+~L0kfAn^V_6v6m||$D zbRgB$5^Ahu#kky;@eJi_FsV^OXp1r$u_d}ntFIPeSXgz*!x#Y$4FerSV?xosY8(2V z{_TiNd>lKkdnSO{P_Vy{0}Y&e+lV8qCr#9H^43(|cJj89w`)0~2HIr8PH>7i5!620*!aF4Pa#|weDS+c7WYe zRn>%@7TtB#EsL|=xRAImQHM7`%>@$i(PJU_ETkP8(H6tH!>X?ry?1$&3v@;k<{^q) zFyN>Y`X^)v7naq%t$wEYK$?CjxbByuT<{&qv%xTz10b9PSPKVt+Rs3i=84wp0OhsK z()@6y^;D@+oifK&b>$Q00xB||A`?31rf zkYYJbVhTD7ZS^G#@4~O$W94^P3qra@tIsdP<~VqX19Px2O#>7J%?Ft11X3D;G6iBh z2jY%KqI-bAVBRJ{M5`|rAvve{|FEpWYR4LS0gi9^ z&k*8c@NWXXmg5YboBWW&mJkz z7r&^AISbylA{odywyiiGlwy=)e<_X+MoC^v6na{HhSf4w$gUb%M>Zsqx{XyAt20zx z4-=!Wx?^lOjW&Z(i!&*}S literal 0 HcmV?d00001 diff --git a/mouse/__pycache__/_mouse_event.cpython-38.pyc b/mouse/__pycache__/_mouse_event.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5839c750fbe8990fb7301edd30af79c4ca604b8c GIT binary patch literal 547 zcmYjMO^?$s5RKg=O_QcuKE;6p*ItOU7cMKrE)BF&wq2E$uI5su9n)sz#F684w_N!z zNc<&VIq?gSIN@{=FqYrUn>QLiFT&8bO#Jj-tiBS<`reTL!!hKIxz&vUw2*KsCqRgW z6ezZ!g*MpefP*f$*oHQG;9&2Pq4pjeW4H<{s12uMh)v3_6S{k z{6{^(Cm*cA(;wzNYe2MLq&Y)Xmz-&vvrkGpGTZJHxOs+Xn$GQxF|NrS(7RKhb!q&F z+rqdJ^_nyA_Drg5lXCEPLS~;up;E4ci&`mB#CujK?XxDW)S+bBT^|K)t1M^QUpC9{ zryOKlrMhz|_Uyhx4wFG&2OGgT+o((wm88bCwv+hXTsyPz`_Wvxm*e3uiNRTp;yBUd zTGQ)OO&3?%9!{4N?GC4l3!{4<p*vTgRspn=f9H%mTi>IQzj zigqGr(MAYfuwolsNpWLrR8c8KD%UJiQ7-C=MXO_49Y6W9JZSGM7g%%lx-nZlH_)Xv M`FsA=tv>0I-?bx{7XSbN literal 0 HcmV?d00001 diff --git a/mouse/__pycache__/_nixcommon.cpython-38.pyc b/mouse/__pycache__/_nixcommon.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c614439e01fbfc5cd76955aced64214a293f4f5c GIT binary patch literal 5385 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 literal 0 HcmV?d00001 diff --git a/mouse/__pycache__/_nixmouse.cpython-38.pyc b/mouse/__pycache__/_nixmouse.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90d0e73032dfb662a86e506280d255db69c66927 GIT binary patch literal 3578 zcmbVONpsw|6$Zei*_*7*@+R-w^kghqo_)rdi9KqMRU^xeCC6=)Q%tJ?Nwmmj8$`=e ziha_Ya!OVHfOJ$<&iM(c{03Ze(l1DAQkBYkpkggaE+GpK55Ob%@VyN$>h%hN=WoH4 z@b7g({)&U653c?KTDD0E5k%0KaO_!36UG@W<{a(_N4T+@c-%{T?k6Q)N&+4vWnNAy zypmLT6=WUZ#kHi)>&XxwN>1@p$uJ*IM)*iF%14tiK9-F0@#Hi=olNkFgT2r2Gxon{ zZSTitl5_lAa-N@0F7OM7fIvxKH6lVK~1f*COhk4 z+Sk3l-UpkIM#=ciUxhz3U)lb7bKdq(7QeIo;;RMQKX|xkM%t^ZO0KrG429f?Ix;g= z+0FV&hAK^UqhhKL`&y^n=7#KQQ+c^2WnA#(C+UVPSa-hpXvuib=e~cuWc(*{v$OL} zLtBQm?iu#tnQ>>IzFcT{Cg_CysH;D|XZ(UQUMGYJT-Zn>5%!F?yrtwy2M>4j@nG;B zwCouWP1LZa(3lz)WRYm+jl1Ik^><2w?!wz`f)CV!u-nY?p4+HUYk- z3CL0WzTqrzr!_O9EO>QO5>eKR+grwe7j;GY&N!PhGoZ5G>dB0|9TCU8I-j=1e6+0E zYKyyl9mTx10@7i8Fjs5MbtApBres@W>MW?uYptg}*_|ym=OraG+>wvAX5~t|AM59U z_)Gi8J)$nb7nc0=$@E&9$mvd+#$DN6oqnd$H?pI%>7Gj4YFS2lI!XJPoDRFursbx3 zTc*C;k75xX9OV>ZHw1*R8gc2k6F31KqUs9F90NOmi5fHub`Fdt+Xy4wwvCYi8Bon& zbfm4guiXI~I0JV8X4obJU!y^MUDCle)#V(b0VqHZlv(On>RReq>TlEC@}QK1xpL=C zWrcxZ=t84vb+iDgGoX)}Kr#u0*IU2rOSSbZ1s9S^oyUdKNG>2b2gGlhiOL;?Fw4FImrPM1RuS7mty)` zBztV3a8$oQ#zIkRy=<%AF(eQK?zbMr{cLTI8MRYJY0~@VA#7DO82L+`FL_2FAzKomiPc+eiqw6veQYF3xJOb5EW z12F=r4glSD27Y%5q^cnGM_t>g3kGyZIJvvacG+t>aC1kWg4N&Y;oOBU7<_RJ=0^1B z3S0N#tFdhYCJF{+J)Q?SMk?%@!~fR<^{2OfPIgY`J~|-k1##&v{6xPsG%|FqF=3Cb zX5e(Zg4r&VSSu8sTcP_XbO?nWuf~y#0Wm{`sl(;1(3;+Xpi-FSjh|`N?`ZD#yS;X2 zox4a)8LC+L@^P~{uP{A9v4L@`=w8_vQ*c=!+<_tpRm*$Zmwjnm9VJL1&8j=FtgfO= zxv(|l0OMI*jnh`E>J|#zMsjF+Yjr=4GA+CFu$0{Z0zu-8(qYP&OI;SwOEjPp(AVIt z&>9OEW9k9O9rFbYllNr|gZ9uDw4>deoP)}~>l8uWfFO53X3slB*HquB2nuxpFM?`@ z5Ie-MKy!~O%qJ)H_j*ujvNc%A@GFdQ^$7a^Up<8|C=5o0QE>8yP}*YI&SVU0hrj$D zM*oor-y?zw6^P`3!#d=7=-KB`Z*oY7Funynjwz%=h!#W)(K;A;kSY(b+jEKp86$xB zxTN79!l*D9t*fc8koy|RO(ZAGUbo2+j?&iAhU|evQ9-E>wk}?%pmQ+)zk$R=`rl7= z{1g~f>M;m6ju`xireER`HJ*^MZl6@tdxy8O--1Yyj=;XCp85f1Ob~`i8wy*vFJQR; z7rT-5t;WyQ7bsE5`pa;YfV*F&Fm)QHB(LCQ0#a_0c8mB$Llxm{13SQ>&6AkpOf9sD z^uRZ5tYetkm{fT0P<{DJxm_B0;EWs<2`)BqEq^)};1a09f7z=oT`0I>e@-ZI^G zBE#^AJ*^Acq!ZjvQqhm)H)xXVH$X7K1oQ^IYu#@WlMlQ>J*Wg;!DBi)Xm$;FhY?0k Xw12pzQILbLSTI?0Yi>}flq&3Bk2L&v literal 0 HcmV?d00001 diff --git a/mouse/_generic.py b/mouse/_generic.py new file mode 100644 index 0000000..04f029e --- /dev/null +++ b/mouse/_generic.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +from threading import Thread, Lock +import traceback +import functools + +try: + from queue import Queue +except ImportError: + from Queue import Queue + +class GenericListener(object): + lock = Lock() + + def __init__(self): + self.handlers = [] + self.listening = False + self.queue = Queue() + + def invoke_handlers(self, event): + for handler in self.handlers: + try: + if handler(event): + # Stop processing this hotkey. + return 1 + except Exception as e: + traceback.print_exc() + + def start_if_necessary(self): + """ + Starts the listening thread if it wasn't already. + """ + self.lock.acquire() + try: + if not self.listening: + self.init() + + self.listening = True + self.listening_thread = Thread(target=self.listen) + self.listening_thread.daemon = True + self.listening_thread.start() + + self.processing_thread = Thread(target=self.process) + self.processing_thread.daemon = True + self.processing_thread.start() + finally: + self.lock.release() + + def pre_process_event(self, event): + raise NotImplementedError('This method should be implemented in the child class.') + + def process(self): + """ + Loops over the underlying queue of events and processes them in order. + """ + assert self.queue is not None + while True: + event = self.queue.get() + if self.pre_process_event(event): + self.invoke_handlers(event) + self.queue.task_done() + + def add_handler(self, handler): + """ + Adds a function to receive each event captured, starting the capturing + process if necessary. + """ + self.start_if_necessary() + self.handlers.append(handler) + + def remove_handler(self, handler): + """ Removes a previously added event handler. """ + self.handlers.remove(handler) diff --git a/mouse/_generic.pyc b/mouse/_generic.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f50aff620be211428b81f8eaeee82407731bfe18 GIT binary patch literal 2992 zcmb_e-)|d55S~5%JSU+A5>z36EFe@%paco=f>1>%EkcT{a;el1flimZO?=Mz&e>g? z2DJiF-uNr{8+qUnzL~Y1R^kcS_N`}kXLn}4{bu%Bf9(wZ?&~zC<)?%HFY&lv(G>V8 zs)(kFP83ZeI*CZ}XhceMyyL8ee^|*eI+NMVr1G0vclC@Ci zt`urT$3>e=pUxB&9r~TPlIQNDu{BxY+Yk?%{9-gzJi~5( zH#c+RZ$SFS)W(+iVd*?e>)=@RD?IKY8bchDiP&I>1jZ2tB4(k(9|4EIPqYmxF1O#}&rxnn$!1q`+@DjAX? z8%ns5`0qc-&CHigEj@3u+?;0lh4f~&tbJ;p{@%U(%xEWZ>_YqR@yZIbsUwP-eg;LIF{^d!Ar;f z4)pSKhR78(6QkQ4C^!lgF)wjLQDjvJqU$2zh#p-k8N5dWLDH&WLgXL(l z{BmAelQ5@bhN&mQBRe+TYjcv=mz1oN001X;f&vjK@eyOd06LV!<`-M?+>)!WQ*v-sK zKIG;$n(G4*>Hog9BuoHy%yIH`RUE~gIF5JYR=gGWhwNZ@Z;%TM%#wR0cq;L*r$Sb| zD>+Hp@Seq!D3Xo!lC5&nG^hL)B_=oIzQFlK0PqecpW`EV1GmC<)Ka@@2e-q&Cg+3N literal 0 HcmV?d00001 diff --git a/mouse/_mouse_event.py b/mouse/_mouse_event.py new file mode 100644 index 0000000..38b8961 --- /dev/null +++ b/mouse/_mouse_event.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from collections import namedtuple + +LEFT = 'left' +RIGHT = 'right' +MIDDLE = 'middle' +WHEEL = 'wheel' +X = 'x' +X2 = 'x2' + +UP = 'up' +DOWN = 'down' +DOUBLE = 'double' +VERTICAL = 'vertical' +HORIZONTAL = 'horizontal' + + +ButtonEvent = namedtuple('ButtonEvent', ['event_type', 'button', 'time']) +WheelEvent = namedtuple('WheelEvent', ['delta', 'time']) +MoveEvent = namedtuple('MoveEvent', ['x', 'y', 'time']) diff --git a/mouse/_mouse_event.pyc b/mouse/_mouse_event.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c80f97d052f89ef50a787f34fd5b71f834dfa07 GIT binary patch literal 694 zcmYjOO>fgc5FOh|n$HpnMI1PA%n3_exFAGJ42k3>l}SmpmniWpiIMHK>~TuC@-z6E z95}-41VPu{nb|k{X5QG|&q4QB`}rz{zpKgqE$`|F8|2r46d-dzIpClR$_4a5d4LU2 z4ZtR-CSVIx3-AQg31Ayk8?Xba1K0)C1?++90ro-lK|6p0z*E2nfJ4}N@SST81b+y4 zCZH?eseq4caeRF5G850?W^&?Vk8_tbk;#L- z&PmeRMk_iLTB>X$EtM(Bv|L$Q0Y`?_gREvv9geQdULSF*?l2n-*-4k!q^XW6F?kg+ z22wB#-Y;aLX!>EYkakzo@i+_wTuy=@lre8*zT4xNH`l@)&zCb{jORC(%&=pA3ZljI z-9;!OC-Z2k=Cj2`81*?9St-#On-TAOrh3Ike`hl7$Ht_&&bR(`X}+-9$}dW@E;l+M qKQncu{gt?te4`h|LAt)q4A!ZBV}C{UjLpz(IscyjvG>$Dcm4o<$&m5@ literal 0 HcmV?d00001 diff --git a/mouse/_mouse_tests.py b/mouse/_mouse_tests.py new file mode 100644 index 0000000..b3e9f9a --- /dev/null +++ b/mouse/_mouse_tests.py @@ -0,0 +1,271 @@ +# -*- coding: utf-8 -*- +import unittest +import time + +from ._mouse_event import MoveEvent, ButtonEvent, WheelEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE +import mouse + +class FakeOsMouse(object): + def __init__(self): + self.append = None + self.position = (0, 0) + self.queue = None + self.init = lambda: None + + def listen(self, queue): + self.listening = True + self.queue = queue + + def press(self, button): + self.append((DOWN, button)) + + def release(self, button): + self.append((UP, button)) + + def get_position(self): + return self.position + + def move_to(self, x, y): + self.append(('move', (x, y))) + self.position = (x, y) + + def wheel(self, delta): + self.append(('wheel', delta)) + + def move_relative(self, x, y): + self.position = (self.position[0] + x, self.position[1] + y) + +class TestMouse(unittest.TestCase): + @staticmethod + def setUpClass(): + mouse._os_mouse= FakeOsMouse() + mouse._listener.start_if_necessary() + assert mouse._os_mouse.listening + + def setUp(self): + self.events = [] + mouse._pressed_events.clear() + mouse._os_mouse.append = self.events.append + + def tearDown(self): + mouse.unhook_all() + # Make sure there's no spill over between tests. + self.wait_for_events_queue() + + def wait_for_events_queue(self): + mouse._listener.queue.join() + + def flush_events(self): + self.wait_for_events_queue() + events = list(self.events) + # Ugly, but requried to work in Python2. Python3 has list.clear + del self.events[:] + return events + + def press(self, button=LEFT): + mouse._os_mouse.queue.put(ButtonEvent(DOWN, button, time.time())) + self.wait_for_events_queue() + + def release(self, button=LEFT): + mouse._os_mouse.queue.put(ButtonEvent(UP, button, time.time())) + self.wait_for_events_queue() + + def double_click(self, button=LEFT): + mouse._os_mouse.queue.put(ButtonEvent(DOUBLE, button, time.time())) + self.wait_for_events_queue() + + def click(self, button=LEFT): + self.press(button) + self.release(button) + + def wheel(self, delta=1): + mouse._os_mouse.queue.put(WheelEvent(delta, time.time())) + self.wait_for_events_queue() + + def move(self, x=0, y=0): + mouse._os_mouse.queue.put(MoveEvent(x, y, time.time())) + self.wait_for_events_queue() + + def test_hook(self): + events = [] + self.press() + mouse.hook(events.append) + self.press() + mouse.unhook(events.append) + self.press() + self.assertEqual(len(events), 1) + + def test_is_pressed(self): + self.assertFalse(mouse.is_pressed()) + self.press() + self.assertTrue(mouse.is_pressed()) + self.release() + self.press(X2) + self.assertFalse(mouse.is_pressed()) + + self.assertTrue(mouse.is_pressed(X2)) + self.press(X2) + self.assertTrue(mouse.is_pressed(X2)) + self.release(X2) + self.release(X2) + self.assertFalse(mouse.is_pressed(X2)) + + def test_buttons(self): + mouse.press() + self.assertEqual(self.flush_events(), [(DOWN, LEFT)]) + mouse.release() + self.assertEqual(self.flush_events(), [(UP, LEFT)]) + mouse.click() + self.assertEqual(self.flush_events(), [(DOWN, LEFT), (UP, LEFT)]) + mouse.double_click() + self.assertEqual(self.flush_events(), [(DOWN, LEFT), (UP, LEFT), (DOWN, LEFT), (UP, LEFT)]) + mouse.right_click() + self.assertEqual(self.flush_events(), [(DOWN, RIGHT), (UP, RIGHT)]) + mouse.click(RIGHT) + self.assertEqual(self.flush_events(), [(DOWN, RIGHT), (UP, RIGHT)]) + mouse.press(X2) + self.assertEqual(self.flush_events(), [(DOWN, X2)]) + + def test_position(self): + self.assertEqual(mouse.get_position(), mouse._os_mouse.get_position()) + + def test_move(self): + mouse.move(0, 0) + self.assertEqual(mouse._os_mouse.get_position(), (0, 0)) + mouse.move(100, 500) + self.assertEqual(mouse._os_mouse.get_position(), (100, 500)) + mouse.move(1, 2, False) + self.assertEqual(mouse._os_mouse.get_position(), (101, 502)) + + mouse.move(0, 0) + mouse.move(100, 499, True, duration=0.01) + self.assertEqual(mouse._os_mouse.get_position(), (100, 499)) + mouse.move(100, 1, False, duration=0.01) + self.assertEqual(mouse._os_mouse.get_position(), (200, 500)) + mouse.move(0, 0, False, duration=0.01) + self.assertEqual(mouse._os_mouse.get_position(), (200, 500)) + + def triggers(self, fn, events, **kwargs): + self.triggered = False + def callback(): + self.triggered = True + handler = fn(callback, **kwargs) + + for event_type, arg in events: + if event_type == DOWN: + self.press(arg) + elif event_type == UP: + self.release(arg) + elif event_type == DOUBLE: + self.double_click(arg) + elif event_type == 'WHEEL': + self.wheel() + + mouse._listener.remove_handler(handler) + return self.triggered + + def test_on_button(self): + self.assertTrue(self.triggers(mouse.on_button, [(DOWN, LEFT)])) + self.assertTrue(self.triggers(mouse.on_button, [(DOWN, RIGHT)])) + self.assertTrue(self.triggers(mouse.on_button, [(DOWN, X)])) + + self.assertFalse(self.triggers(mouse.on_button, [('WHEEL', '')])) + + self.assertFalse(self.triggers(mouse.on_button, [(DOWN, X)], buttons=MIDDLE)) + self.assertTrue(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE)) + self.assertTrue(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE)) + self.assertFalse(self.triggers(mouse.on_button, [(DOWN, MIDDLE)], buttons=MIDDLE, types=UP)) + self.assertTrue(self.triggers(mouse.on_button, [(UP, MIDDLE)], buttons=MIDDLE, types=UP)) + + self.assertTrue(self.triggers(mouse.on_button, [(UP, MIDDLE)], buttons=[MIDDLE, LEFT], types=[UP, DOWN])) + self.assertTrue(self.triggers(mouse.on_button, [(DOWN, LEFT)], buttons=[MIDDLE, LEFT], types=[UP, DOWN])) + self.assertFalse(self.triggers(mouse.on_button, [(UP, X)], buttons=[MIDDLE, LEFT], types=[UP, DOWN])) + + def test_ons(self): + self.assertTrue(self.triggers(mouse.on_click, [(UP, LEFT)])) + self.assertFalse(self.triggers(mouse.on_click, [(UP, RIGHT)])) + self.assertFalse(self.triggers(mouse.on_click, [(DOWN, LEFT)])) + self.assertFalse(self.triggers(mouse.on_click, [(DOWN, RIGHT)])) + + self.assertTrue(self.triggers(mouse.on_double_click, [(DOUBLE, LEFT)])) + self.assertFalse(self.triggers(mouse.on_double_click, [(DOUBLE, RIGHT)])) + self.assertFalse(self.triggers(mouse.on_double_click, [(DOWN, RIGHT)])) + + self.assertTrue(self.triggers(mouse.on_right_click, [(UP, RIGHT)])) + self.assertTrue(self.triggers(mouse.on_middle_click, [(UP, MIDDLE)])) + + def test_wait(self): + # If this fails it blocks. Unfortunately, but I see no other way of testing. + from threading import Thread, Lock + lock = Lock() + lock.acquire() + def t(): + mouse.wait() + lock.release() + Thread(target=t).start() + self.press() + lock.acquire() + + def test_record_play(self): + from threading import Thread, Lock + lock = Lock() + lock.acquire() + def t(): + self.recorded = mouse.record(RIGHT) + lock.release() + Thread(target=t).start() + self.click() + self.wheel(5) + self.move(100, 50) + self.press(RIGHT) + lock.acquire() + + self.assertEqual(len(self.recorded), 5) + self.assertEqual(self.recorded[0]._replace(time=None), ButtonEvent(DOWN, LEFT, None)) + self.assertEqual(self.recorded[1]._replace(time=None), ButtonEvent(UP, LEFT, None)) + self.assertEqual(self.recorded[2]._replace(time=None), WheelEvent(5, None)) + self.assertEqual(self.recorded[3]._replace(time=None), MoveEvent(100, 50, None)) + self.assertEqual(self.recorded[4]._replace(time=None), ButtonEvent(DOWN, RIGHT, None)) + + mouse.play(self.recorded, speed_factor=0) + events = self.flush_events() + self.assertEqual(len(events), 5) + self.assertEqual(events[0], (DOWN, LEFT)) + self.assertEqual(events[1], (UP, LEFT)) + self.assertEqual(events[2], ('wheel', 5)) + self.assertEqual(events[3], ('move', (100, 50))) + self.assertEqual(events[4], (DOWN, RIGHT)) + + mouse.play(self.recorded) + events = self.flush_events() + self.assertEqual(len(events), 5) + self.assertEqual(events[0], (DOWN, LEFT)) + self.assertEqual(events[1], (UP, LEFT)) + self.assertEqual(events[2], ('wheel', 5)) + self.assertEqual(events[3], ('move', (100, 50))) + self.assertEqual(events[4], (DOWN, RIGHT)) + + mouse.play(self.recorded, include_clicks=False) + events = self.flush_events() + self.assertEqual(len(events), 2) + self.assertEqual(events[0], ('wheel', 5)) + self.assertEqual(events[1], ('move', (100, 50))) + + mouse.play(self.recorded, include_moves=False) + events = self.flush_events() + self.assertEqual(len(events), 4) + self.assertEqual(events[0], (DOWN, LEFT)) + self.assertEqual(events[1], (UP, LEFT)) + self.assertEqual(events[2], ('wheel', 5)) + self.assertEqual(events[3], (DOWN, RIGHT)) + + mouse.play(self.recorded, include_wheel=False) + events = self.flush_events() + self.assertEqual(len(events), 4) + self.assertEqual(events[0], (DOWN, LEFT)) + self.assertEqual(events[1], (UP, LEFT)) + self.assertEqual(events[2], ('move', (100, 50))) + self.assertEqual(events[3], (DOWN, RIGHT)) + +if __name__ == '__main__': + unittest.main() diff --git a/mouse/_nixcommon.py b/mouse/_nixcommon.py new file mode 100644 index 0000000..a77de1d --- /dev/null +++ b/mouse/_nixcommon.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +import struct +import os +import atexit +from time import time as now +from threading import Thread +from glob import glob +try: + from queue import Queue +except ImportError: + from Queue import Queue + +event_bin_format = 'llHHI' + +# Taken from include/linux/input.h +# https://www.kernel.org/doc/Documentation/input/event-codes.txt +EV_SYN = 0x00 +EV_KEY = 0x01 +EV_REL = 0x02 +EV_ABS = 0x03 +EV_MSC = 0x04 + +INVALID_ARGUMENT_ERRNO = 22 + +def make_uinput(): + import fcntl, struct + + # Requires uinput driver, but it's usually available. + uinput = open("/dev/uinput", 'wb') + UI_SET_EVBIT = 0x40045564 + fcntl.ioctl(uinput, UI_SET_EVBIT, EV_KEY) + + UI_SET_KEYBIT = 0x40045565 + try: + for i in range(0x300): + fcntl.ioctl(uinput, UI_SET_KEYBIT, i) + except OSError as e: + if e.errno != INVALID_ARGUMENT_ERRNO: + raise e + + BUS_USB = 0x03 + uinput_user_dev = "80sHHHHi64i64i64i64i" + axis = [0] * 64 * 4 + uinput.write(struct.pack(uinput_user_dev, b"Virtual Keyboard", BUS_USB, 1, 1, 1, 0, *axis)) + uinput.flush() # Without this you may get Errno 22: Invalid argument. + + UI_DEV_CREATE = 0x5501 + fcntl.ioctl(uinput, UI_DEV_CREATE) + UI_DEV_DESTROY = 0x5502 + #fcntl.ioctl(uinput, UI_DEV_DESTROY) + + return uinput + +class EventDevice(object): + def __init__(self, path): + self.path = path + self._input_file = None + self._output_file = None + + @property + def input_file(self): + if self._input_file is None: + try: + self._input_file = open(self.path, 'rb') + except IOError as e: + if e.strerror == 'Permission denied': + print('Permission denied ({}). You must be sudo to access global events.'.format(self.path)) + exit() + + def try_close(): + try: + self._input_file.close + except: + pass + atexit.register(try_close) + return self._input_file + + @property + def output_file(self): + if self._output_file is None: + self._output_file = open(self.path, 'wb') + atexit.register(self._output_file.close) + return self._output_file + + def read_event(self): + data = self.input_file.read(struct.calcsize(event_bin_format)) + seconds, microseconds, type, code, value = struct.unpack(event_bin_format, data) + return seconds + microseconds / 1e6, type, code, value, self.path + + def write_event(self, type, code, value): + integer, fraction = divmod(now(), 1) + seconds = int(integer) + microseconds = int(fraction * 1e6) + data_event = struct.pack(event_bin_format, seconds, microseconds, type, code, value) + + # Send a sync event to ensure other programs update. + sync_event = struct.pack(event_bin_format, seconds, microseconds, EV_SYN, 0, 0) + + self.output_file.write(data_event + sync_event) + self.output_file.flush() + +class AggregatedEventDevice(object): + def __init__(self, devices, output=None): + self.event_queue = Queue() + self.devices = devices + self.output = output or self.devices[0] + def start_reading(device): + while True: + self.event_queue.put(device.read_event()) + for device in self.devices: + thread = Thread(target=start_reading, args=[device]) + thread.setDaemon(True) + thread.start() + + def read_event(self): + return self.event_queue.get(block=True) + + def write_event(self, type, code, value): + self.output.write_event(type, code, value) + +import re +from collections import namedtuple +DeviceDescription = namedtuple('DeviceDescription', 'event_file is_mouse is_keyboard') +device_pattern = r"""N: Name="([^"]+?)".+?H: Handlers=([^\n]+)""" +def list_devices_from_proc(type_name): + try: + with open('/proc/bus/input/devices') as f: + description = f.read() + except FileNotFoundError: + return + + devices = {} + for name, handlers in re.findall(device_pattern, description, re.DOTALL): + path = '/dev/input/event' + re.search(r'event(\d+)', handlers).group(1) + if type_name in handlers: + yield EventDevice(path) + +def list_devices_from_by_id(type_name): + for path in glob('/dev/input/by-id/*-event-' + type_name): + yield EventDevice(path) + +def aggregate_devices(type_name): + # Some systems have multiple keyboards with different range of allowed keys + # on each one, like a notebook with a "keyboard" device exclusive for the + # power button. Instead of figuring out which keyboard allows which key to + # send events, we create a fake device and send all events through there. + uinput = make_uinput() + fake_device = EventDevice('uinput Fake Device') + fake_device._input_file = uinput + fake_device._output_file = uinput + + # We don't aggregate devices from different sources to avoid + # duplicates. + + devices_from_proc = list(list_devices_from_proc(type_name)) + if devices_from_proc: + return AggregatedEventDevice(devices_from_proc, output=fake_device) + + # breaks on mouse for virtualbox + # was getting /dev/input/by-id/usb-VirtualBox_USB_Tablet-event-mouse + devices_from_by_id = list(list_devices_from_by_id(type_name)) + if devices_from_by_id: + return AggregatedEventDevice(devices_from_by_id, output=fake_device) + + # If no keyboards were found we can only use the fake device to send keys. + return fake_device + + +def ensure_root(): + if os.geteuid() != 0: + raise ImportError('You must be root to use this library on linux.') diff --git a/mouse/_nixcommon.pyc b/mouse/_nixcommon.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1112fead7343f022d1c27be20011df2fa2f96aea GIT binary patch literal 6782 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 Bl0*Oi literal 0 HcmV?d00001 diff --git a/mouse/_winmouse.py b/mouse/_winmouse.py new file mode 100644 index 0000000..b5806a2 --- /dev/null +++ b/mouse/_winmouse.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +import ctypes +import time +from ctypes import c_short, c_char, c_uint8, c_int32, c_int, c_uint, c_uint32, c_long, byref, Structure, CFUNCTYPE, POINTER +from ctypes.wintypes import DWORD, BOOL, HHOOK, MSG, LPWSTR, WCHAR, WPARAM, LPARAM +LPMSG = POINTER(MSG) + +import atexit + +from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN, DOUBLE, WHEEL, HORIZONTAL, VERTICAL + +#user32 = ctypes.windll.user32 +user32 = ctypes.WinDLL('user32', use_last_error = True) + +class MSLLHOOKSTRUCT(Structure): + _fields_ = [("x", c_long), + ("y", c_long), + ('data', c_int32), + ('reserved', c_int32), + ("flags", DWORD), + ("time", c_int), + ] + +LowLevelMouseProc = CFUNCTYPE(c_int, WPARAM, LPARAM, POINTER(MSLLHOOKSTRUCT)) + +SetWindowsHookEx = user32.SetWindowsHookExA +#SetWindowsHookEx.argtypes = [c_int, LowLevelMouseProc, c_int, c_int] +SetWindowsHookEx.restype = HHOOK + +CallNextHookEx = user32.CallNextHookEx +#CallNextHookEx.argtypes = [c_int , c_int, c_int, POINTER(MSLLHOOKSTRUCT)] +CallNextHookEx.restype = c_int + +UnhookWindowsHookEx = user32.UnhookWindowsHookEx +UnhookWindowsHookEx.argtypes = [HHOOK] +UnhookWindowsHookEx.restype = BOOL + +GetMessage = user32.GetMessageW +GetMessage.argtypes = [LPMSG, c_int, c_int, c_int] +GetMessage.restype = BOOL + +TranslateMessage = user32.TranslateMessage +TranslateMessage.argtypes = [LPMSG] +TranslateMessage.restype = BOOL + +DispatchMessage = user32.DispatchMessageA +DispatchMessage.argtypes = [LPMSG] + +GetDoubleClickTime = user32.GetDoubleClickTime + +# Beware, as of 2016-01-30 the official docs have a very incomplete list. +# This one was compiled from experience and may be incomplete. +WM_MOUSEMOVE = 0x200 +WM_LBUTTONDOWN = 0x201 +WM_LBUTTONUP = 0x202 +WM_LBUTTONDBLCLK = 0x203 +WM_RBUTTONDOWN = 0x204 +WM_RBUTTONUP = 0x205 +WM_RBUTTONDBLCLK = 0x206 +WM_MBUTTONDOWN = 0x207 +WM_MBUTTONUP = 0x208 +WM_MBUTTONDBLCLK = 0x209 +WM_MOUSEWHEEL = 0x20A +WM_XBUTTONDOWN = 0x20B +WM_XBUTTONUP = 0x20C +WM_XBUTTONDBLCLK = 0x20D +WM_NCXBUTTONDOWN = 0x00AB +WM_NCXBUTTONUP = 0x00AC +WM_NCXBUTTONDBLCLK = 0x00AD +WM_MOUSEHWHEEL = 0x20E +WM_LBUTTONDOWN = 0x0201 +WM_LBUTTONUP = 0x0202 +WM_MOUSEMOVE = 0x0200 +WM_MOUSEWHEEL = 0x020A +WM_MOUSEHWHEEL = 0x020E +WM_RBUTTONDOWN = 0x0204 +WM_RBUTTONUP = 0x0205 + +buttons_by_wm_code = { + WM_LBUTTONDOWN: (DOWN, LEFT), + WM_LBUTTONUP: (UP, LEFT), + WM_LBUTTONDBLCLK: (DOUBLE, LEFT), + + WM_RBUTTONDOWN: (DOWN, RIGHT), + WM_RBUTTONUP: (UP, RIGHT), + WM_RBUTTONDBLCLK: (DOUBLE, RIGHT), + + WM_MBUTTONDOWN: (DOWN, MIDDLE), + WM_MBUTTONUP: (UP, MIDDLE), + WM_MBUTTONDBLCLK: (DOUBLE, MIDDLE), + + WM_XBUTTONDOWN: (DOWN, X), + WM_XBUTTONUP: (UP, X), + WM_XBUTTONDBLCLK: (DOUBLE, X), +} + +MOUSEEVENTF_ABSOLUTE = 0x8000 +MOUSEEVENTF_MOVE = 0x1 +MOUSEEVENTF_WHEEL = 0x800 +MOUSEEVENTF_HWHEEL = 0x1000 +MOUSEEVENTF_LEFTDOWN = 0x2 +MOUSEEVENTF_LEFTUP = 0x4 +MOUSEEVENTF_RIGHTDOWN = 0x8 +MOUSEEVENTF_RIGHTUP = 0x10 +MOUSEEVENTF_MIDDLEDOWN = 0x20 +MOUSEEVENTF_MIDDLEUP = 0x40 +MOUSEEVENTF_XDOWN = 0x0080 +MOUSEEVENTF_XUP = 0x0100 + +simulated_mouse_codes = { + (WHEEL, HORIZONTAL): MOUSEEVENTF_HWHEEL, + (WHEEL, VERTICAL): MOUSEEVENTF_WHEEL, + + (DOWN, LEFT): MOUSEEVENTF_LEFTDOWN, + (UP, LEFT): MOUSEEVENTF_LEFTUP, + + (DOWN, RIGHT): MOUSEEVENTF_RIGHTDOWN, + (UP, RIGHT): MOUSEEVENTF_RIGHTUP, + + (DOWN, MIDDLE): MOUSEEVENTF_MIDDLEDOWN, + (UP, MIDDLE): MOUSEEVENTF_MIDDLEUP, + + (DOWN, X): MOUSEEVENTF_XDOWN, + (UP, X): MOUSEEVENTF_XUP, +} + +NULL = c_int(0) + +WHEEL_DELTA = 120 + +init = lambda: None + +previous_button_event = None # defined in global scope +def listen(queue): + + def low_level_mouse_handler(nCode, wParam, lParam): + global previous_button_event + + struct = lParam.contents + # Can't use struct.time because it's usually zero. + t = time.time() + + if wParam == WM_MOUSEMOVE: + event = MoveEvent(struct.x, struct.y, t) + elif wParam == WM_MOUSEWHEEL: + event = WheelEvent(struct.data / (WHEEL_DELTA * (2<<15)), t) + elif wParam in buttons_by_wm_code: + type, button = buttons_by_wm_code.get(wParam, ('?', '?')) + if wParam >= WM_XBUTTONDOWN: + button = {0x10000: X, 0x20000: X2}[struct.data] + event = ButtonEvent(type, button, t) + + if (event.event_type == DOWN) and previous_button_event is not None: + # https://msdn.microsoft.com/en-us/library/windows/desktop/gg153548%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 + if event.time - previous_button_event.time <= GetDoubleClickTime() / 1000.0: + event = ButtonEvent(DOUBLE, event.button, event.time) + + previous_button_event = event + else: + # Unknown event type. + return CallNextHookEx(NULL, nCode, wParam, lParam) + + queue.put(event) + return CallNextHookEx(NULL, nCode, wParam, lParam) + + WH_MOUSE_LL = c_int(14) + mouse_callback = LowLevelMouseProc(low_level_mouse_handler) + mouse_hook = SetWindowsHookEx(WH_MOUSE_LL, mouse_callback, NULL, NULL) + + # Register to remove the hook when the interpreter exits. Unfortunately a + # try/finally block doesn't seem to work here. + atexit.register(UnhookWindowsHookEx, mouse_hook) + + msg = LPMSG() + while not GetMessage(msg, NULL, NULL, NULL): + TranslateMessage(msg) + DispatchMessage(msg) + +def _translate_button(button): + if button == X or button == X2: + return X, {X: 0x10000, X2: 0x20000}[button] + else: + return button, 0 + +def press(button=LEFT): + button, data = _translate_button(button) + code = simulated_mouse_codes[(DOWN, button)] + user32.mouse_event(code, 0, 0, data, 0) + +def release(button=LEFT): + button, data = _translate_button(button) + code = simulated_mouse_codes[(UP, button)] + user32.mouse_event(code, 0, 0, data, 0) + +def wheel(delta=1): + code = simulated_mouse_codes[(WHEEL, VERTICAL)] + user32.mouse_event(code, 0, 0, int(delta * WHEEL_DELTA), 0) + +def move_to(x, y): + user32.SetCursorPos(int(x), int(y)) + +def move_relative(x, y): + user32.mouse_event(MOUSEEVENTF_MOVE, int(x), int(y), 0, 0) + +class POINT(Structure): + _fields_ = [("x", c_long), ("y", c_long)] + +def get_position(): + point = POINT() + user32.GetCursorPos(byref(point)) + return (point.x, point.y) + +if __name__ == '__main__': + def p(e): + print(e) + listen(p) diff --git a/proarbeit.py b/proarbeit.py index 1b0abc5..dd2c7ea 100644 --- a/proarbeit.py +++ b/proarbeit.py @@ -1,23 +1,21 @@ -from pynput.mouse import Controller -from secrets import randbits +from mouse import get_position, move +from random import randint from time import sleep -def initialize_mouse(): - mouse = Controller() - return mouse - - -def erratic_movement(mouse): - x, y = mouse.position - mouse.move(x + randbits(2), y + randbits(2)) - sleep(randbits(2)) +def erratic_movement(screen_width, screen_height): + x, y = get_position() + move( + x + randint(-100, 100) % screen_width, y + randint(-100, 100) % screen_height, + ) def main(): - mouse = initialize_mouse() + screen_width = 1920 + screen_height = 1080 while True: - erratic_movement(mouse) + erratic_movement(screen_width, screen_height) + sleep(randint(2, 10)) if __name__ == "__main__": diff --git a/shell.nix b/shell.nix index 56ce4b1..c728d39 100644 --- a/shell.nix +++ b/shell.nix @@ -2,4 +2,4 @@ with pkgs; -mkShell { buildInputs = [ python38 python38Packages.pynput ]; } +mkShell { buildInputs = [ python38 ]; }