1/*
2 * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.lwawt.macosx;
27
28import java.awt.*;
29import java.awt.peer.*;
30
31import sun.awt.CGraphicsDevice;
32
33class CRobot implements RobotPeer {
34    private static final int MOUSE_LOCATION_UNKNOWN      = -1;
35
36    private final CGraphicsDevice fDevice;
37    private int mouseLastX = MOUSE_LOCATION_UNKNOWN;
38    private int mouseLastY = MOUSE_LOCATION_UNKNOWN;
39
40    // OS X doesn't generate dragged event as a result of button press and
41    // mouse move events. This means that we have to track buttons state
42    // in order to generate dragged events ourselves.
43    private int mouseButtonsState = 0;
44
45    /**
46     * Uses the given GraphicsDevice as the coordinate system for subsequent
47     * coordinate calls.
48     */
49    public CRobot(Robot r, CGraphicsDevice d) {
50        fDevice = d;
51        initRobot();
52    }
53
54    @Override
55    public void dispose() {
56    }
57
58    /**
59     * Moves mouse pointer to given screen coordinates.
60     * @param x X position
61     * @param y Y position
62     */
63    @Override
64    public void mouseMove(int x, int y) {
65        mouseLastX = x;
66        mouseLastY = y;
67
68        mouseEvent(fDevice.getCGDisplayID(), mouseLastX, mouseLastY,
69                   mouseButtonsState, true, true);
70    }
71
72    /**
73     * Presses one or more mouse buttons.
74     *
75     * @param buttons the button mask (combination of
76     * {@code InputEvent.BUTTON1/2/3_MASK})
77     */
78    @Override
79    public void mousePress(int buttons) {
80        mouseButtonsState |= buttons;
81        checkMousePos();
82        mouseEvent(fDevice.getCGDisplayID(), mouseLastX, mouseLastY,
83                   buttons, true, false);
84    }
85
86    /**
87     * Releases one or more mouse buttons.
88     *
89     * @param buttons the button mask (combination of
90     * {@code InputEvent.BUTTON1/2/3_MASK})
91     */
92    @Override
93    public void mouseRelease(int buttons) {
94        mouseButtonsState &= ~buttons;
95        checkMousePos();
96        mouseEvent(fDevice.getCGDisplayID(), mouseLastX, mouseLastY,
97                   buttons, false, false);
98    }
99
100    /**
101     * Set unknown mouse location, if needed.
102     */
103    private void checkMousePos() {
104        if (mouseLastX == MOUSE_LOCATION_UNKNOWN ||
105                mouseLastY == MOUSE_LOCATION_UNKNOWN) {
106
107            Rectangle deviceBounds = fDevice.getDefaultConfiguration().getBounds();
108            Point mousePos = CCursorManager.getInstance().getCursorPosition();
109
110            if (mousePos.x < deviceBounds.x) {
111                mousePos.x = deviceBounds.x;
112            }
113            else if (mousePos.x > deviceBounds.x + deviceBounds.width) {
114                mousePos.x = deviceBounds.x + deviceBounds.width;
115            }
116
117            if (mousePos.y < deviceBounds.y) {
118                mousePos.y = deviceBounds.y;
119            }
120            else if (mousePos.y > deviceBounds.y + deviceBounds.height) {
121                mousePos.y = deviceBounds.y + deviceBounds.height;
122            }
123
124            mouseLastX = mousePos.x;
125            mouseLastY = mousePos.y;
126        }
127    }
128
129    @Override
130    public native void mouseWheel(int wheelAmt);
131
132    /**
133     * Presses a given key.
134     * <p>
135     * Key codes that have more than one physical key associated with them
136     * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
137     * left or right shift key) will map to the left key.
138     * <p>
139     * Assumes that the
140     * peer implementations will throw an exception for other bogus
141     * values e.g. -1, 999999
142     *
143     * @param keycode the key to press (e.g. {@code KeyEvent.VK_A})
144     */
145    @Override
146    public void keyPress(final int keycode) {
147        keyEvent(keycode, true);
148    }
149
150    /**
151     * Releases a given key.
152     * <p>
153     * Key codes that have more than one physical key associated with them
154     * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
155     * left or right shift key) will map to the left key.
156     * <p>
157     * Assumes that the
158     * peer implementations will throw an exception for other bogus
159     * values e.g. -1, 999999
160     *
161     * @param keycode the key to release (e.g. {@code KeyEvent.VK_A})
162     */
163    @Override
164    public void keyRelease(final int keycode) {
165        keyEvent(keycode, false);
166    }
167
168    /**
169     * Returns the color of a pixel at the given screen coordinates.
170     * @param x X position of pixel
171     * @param y Y position of pixel
172     * @return color of the pixel
173     */
174    @Override
175    public int getRGBPixel(int x, int y) {
176        int c[] = new int[1];
177        double scale = fDevice.getScaleFactor();
178        getScreenPixels(new Rectangle(x, y, (int) scale, (int) scale), c);
179        return c[0];
180    }
181
182    /**
183     * Creates an image containing pixels read from the screen.
184     * @param bounds the rect to capture in screen coordinates
185     * @return the array of pixels
186     */
187    @Override
188    public int [] getRGBPixels(final Rectangle bounds) {
189        int c[] = new int[bounds.width * bounds.height];
190        getScreenPixels(bounds, c);
191
192        return c;
193    }
194
195    private native void initRobot();
196    private native void mouseEvent(int displayID, int lastX, int lastY,
197                                   int buttonsState,
198                                   boolean isButtonsDownState,
199                                   boolean isMouseMove);
200    private native void keyEvent(int javaKeyCode, boolean keydown);
201    private void getScreenPixels(Rectangle r, int[] pixels){
202        double scale = fDevice.getScaleFactor();
203        nativeGetScreenPixels(r.x, r.y, r.width, r.height, scale, pixels);
204    }
205    private native void nativeGetScreenPixels(int x, int y, int width, int height, double scale, int[] pixels);
206}
207