1#Copyright 2013 Paul Barton
2#
3#This program is free software: you can redistribute it and/or modify
4#it under the terms of the GNU General Public License as published by
5#the Free Software Foundation, either version 3 of the License, or
6#(at your option) any later version.
7#
8#This program is distributed in the hope that it will be useful,
9#but WITHOUT ANY WARRANTY; without even the implied warranty of
10#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11#GNU General Public License for more details.
12#
13#You should have received a copy of the GNU General Public License
14#along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16import Quartz
17from AppKit import NSEvent, NSScreen
18from .base import PyMouseMeta, PyMouseEventMeta
19
20pressID = [None, Quartz.kCGEventLeftMouseDown,
21           Quartz.kCGEventRightMouseDown, Quartz.kCGEventOtherMouseDown]
22releaseID = [None, Quartz.kCGEventLeftMouseUp,
23             Quartz.kCGEventRightMouseUp, Quartz.kCGEventOtherMouseUp]
24
25
26class PyMouse(PyMouseMeta):
27
28    def press(self, x, y, button=1):
29        event = Quartz.CGEventCreateMouseEvent(None,
30                                        pressID[button],
31                                        (x, y),
32                                        button - 1)
33        Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
34
35    def release(self, x, y, button=1):
36        event = Quartz.CGEventCreateMouseEvent(None,
37                                        releaseID[button],
38                                        (x, y),
39                                        button - 1)
40        Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
41
42    def move(self, x, y):
43        move = Quartz.CGEventCreateMouseEvent(None, Quartz.kCGEventMouseMoved, (x, y), 0)
44        Quartz.CGEventPost(Quartz.kCGHIDEventTap, move)
45
46    def drag(self, x, y):
47        drag = Quartz.CGEventCreateMouseEvent(None, Quartz.kCGEventLeftMouseDragged, (x, y), 0)
48        Quartz.CGEventPost(Quartz.kCGHIDEventTap, drag)
49
50    def position(self):
51        loc = NSEvent.mouseLocation()
52        return loc.x, Quartz.CGDisplayPixelsHigh(0) - loc.y
53
54    def screen_size(self):
55        return NSScreen.mainScreen().frame().size.width, NSScreen.mainScreen().frame().size.height
56
57    def scroll(self, vertical=None, horizontal=None, depth=None):
58        #Local submethod for generating Mac scroll events in one axis at a time
59        def scroll_event(y_move=0, x_move=0, z_move=0, n=1):
60            for _ in range(abs(n)):
61                scrollWheelEvent = Quartz.CGEventCreateScrollWheelEvent(
62                    None,  # No source
63                    Quartz.kCGScrollEventUnitLine,  # Unit of measurement is lines
64                    3,  # Number of wheels(dimensions)
65                    y_move,
66                    x_move,
67                    z_move)
68                Quartz.CGEventPost(Quartz.kCGHIDEventTap, scrollWheelEvent)
69
70        #Execute vertical then horizontal then depth scrolling events
71        if vertical is not None:
72            vertical = int(vertical)
73            if vertical == 0:   # Do nothing with 0 distance
74                pass
75            elif vertical > 0:  # Scroll up if positive
76                scroll_event(y_move=1, n=vertical)
77            else:  # Scroll down if negative
78                scroll_event(y_move=-1, n=abs(vertical))
79        if horizontal is not None:
80            horizontal = int(horizontal)
81            if horizontal == 0:  # Do nothing with 0 distance
82                pass
83            elif horizontal > 0:  # Scroll right if positive
84                scroll_event(x_move=1, n=horizontal)
85            else:  # Scroll left if negative
86                scroll_event(x_move=-1, n=abs(horizontal))
87        if depth is not None:
88            depth = int(depth)
89            if depth == 0:  # Do nothing with 0 distance
90                pass
91            elif vertical > 0:  # Scroll "out" if positive
92                scroll_event(z_move=1, n=depth)
93            else:  # Scroll "in" if negative
94                scroll_event(z_move=-1, n=abs(depth))
95
96
97class PyMouseEvent(PyMouseEventMeta):
98    def run(self):
99        tap = Quartz.CGEventTapCreate(
100            Quartz.kCGSessionEventTap,
101            Quartz.kCGHeadInsertEventTap,
102            Quartz.kCGEventTapOptionDefault,
103            Quartz.CGEventMaskBit(Quartz.kCGEventMouseMoved) |
104            Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDown) |
105            Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseUp) |
106            Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDown) |
107            Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseUp) |
108            Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDown) |
109            Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseUp),
110            self.handler,
111            None)
112
113        loopsource = Quartz.CFMachPortCreateRunLoopSource(None, tap, 0)
114        loop = Quartz.CFRunLoopGetCurrent()
115        Quartz.CFRunLoopAddSource(loop, loopsource, Quartz.kCFRunLoopDefaultMode)
116        Quartz.CGEventTapEnable(tap, True)
117
118        while self.state:
119            Quartz.CFRunLoopRunInMode(Quartz.kCFRunLoopDefaultMode, 5, False)
120
121    def handler(self, proxy, type, event, refcon):
122        (x, y) = Quartz.CGEventGetLocation(event)
123        if type in pressID:
124            self.click(x, y, pressID.index(type), True)
125        elif type in releaseID:
126            self.click(x, y, releaseID.index(type), False)
127        else:
128            self.move(x, y)
129
130        if self.capture:
131            Quartz.CGEventSetType(event, Quartz.kCGEventNull)
132
133        return event
134