1/*         ______   ___    ___
2 *        /\  _  \ /\_ \  /\_ \
3 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8 *                                           /\____/
9 *                                           \_/__/
10 *
11 *      MacOS X mouse driver.
12 *
13 *      By Angelo Mottola.
14 *
15 *      See readme.txt for copyright information.
16 */
17
18
19#include "allegro.h"
20#include "allegro/internal/aintern.h"
21#include "allegro/platform/aintosx.h"
22
23#ifndef ALLEGRO_MACOSX
24#error Something is wrong with the makefile
25#endif
26
27
28static int osx_mouse_init(void);
29static void osx_mouse_exit(void);
30static void osx_mouse_position(int, int);
31static void osx_mouse_set_range(int, int, int, int);
32static void osx_mouse_get_mickeys(int *, int *);
33static void osx_enable_hardware_cursor(AL_CONST int mode);
34static int osx_select_system_cursor(AL_CONST int cursor);
35
36
37MOUSE_DRIVER mouse_macosx = {
38   MOUSE_MACOSX,
39   empty_string,
40   empty_string,
41   "MacOS X mouse",
42   osx_mouse_init,
43   osx_mouse_exit,
44   NULL,       // AL_METHOD(void, poll, (void));
45   NULL,       // AL_METHOD(void, timer_poll, (void));
46   osx_mouse_position,
47   osx_mouse_set_range,
48   NULL,       // AL_METHOD(void, set_speed, (int xspeed, int yspeed));
49   osx_mouse_get_mickeys,
50   NULL,       // AL_METHOD(int,  analyse_data, (AL_CONST char *buffer, int size));
51   osx_enable_hardware_cursor,
52   osx_select_system_cursor
53};
54
55
56/* global variable */
57int osx_mouse_warped = FALSE;
58int osx_skip_mouse_move = FALSE;
59NSTrackingRectTag osx_mouse_tracking_rect = -1;
60
61
62static NSCursor *cursor = NULL, *current_cursor = NULL;
63static NSCursor *requested_cursor = NULL;
64static unsigned char *cursor_data = NULL;
65static NSBitmapImageRep *cursor_rep = NULL;
66static NSImage *cursor_image = NULL;
67
68static int mouse_minx = 0;
69static int mouse_miny = 0;
70static int mouse_maxx = 319;
71static int mouse_maxy = 199;
72
73static int mymickey_x = 0;
74static int mymickey_y = 0;
75
76static char driver_desc[256];
77
78
79/* osx_change_cursor:
80 * Actually change the current cursor. This can be called fom any thread
81 * but ensures that the change is only called from the main thread.
82 */
83static void osx_change_cursor(NSCursor* cursor)
84{
85   _unix_lock_mutex(osx_event_mutex);
86   osx_cursor = cursor;
87   _unix_unlock_mutex(osx_event_mutex);
88   [cursor performSelectorOnMainThread: @selector(set) withObject: nil waitUntilDone: NO];
89}
90
91
92
93/* osx_mouse_handler:
94 *  Mouse "interrupt" handler for mickey-mode driver.
95 */
96void osx_mouse_handler(int ax, int ay, int x, int y, int z, int buttons)
97{
98   if (!_mouse_on)
99      mymickey_x = mymickey_y = 0;
100
101   if ((!_mouse_installed) || (!_mouse_on) || (osx_gfx_mode == OSX_GFX_NONE))
102      return;
103
104   if (osx_cursor != current_cursor) {
105      if (osx_window) {
106         NSView* vw = [osx_window contentView];
107         [osx_window invalidateCursorRectsForView: vw];
108      }
109      else {
110         [osx_cursor set];
111      }
112      current_cursor = osx_cursor;
113   }
114
115   if (osx_mouse_warped) {
116      osx_mouse_warped = FALSE;
117      return;
118   }
119
120   _mouse_b = buttons;
121
122   mymickey_x += x;
123   mymickey_y += y;
124   _mouse_x = ax;
125   _mouse_y = ay;
126   _mouse_z += z;
127
128   _mouse_x = CLAMP(mouse_minx, _mouse_x, mouse_maxx);
129   _mouse_y = CLAMP(mouse_miny, _mouse_y, mouse_maxy);
130
131   _handle_mouse_input();
132}
133
134
135
136/* osx_mouse_init:
137 *  Initializes the mickey-mode driver.
138 */
139static int osx_mouse_init(void)
140{
141   HID_DEVICE_COLLECTION devices={0,0,NULL};
142   int i, j;
143   int buttons, max_buttons = -1;
144   HID_DEVICE* device;
145
146   if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1) {
147      /* On 10.1.x mice and keyboards aren't available from the HID Manager,
148       * so we can't autodetect. We assume an 1-button mouse to always be
149       * present.
150       */
151      max_buttons = 1;
152   }
153   else {
154      osx_hid_scan(HID_MOUSE, &devices);
155      for (i = 0; i < devices.count; i++) {
156         device=&devices.devices[i];
157         buttons = 0;
158         for (j = 0; j < device->num_elements; j++) {
159            if (device->element[j].type == HID_ELEMENT_BUTTON)
160	       buttons++;
161         }
162         if (buttons > max_buttons) {
163            max_buttons = buttons;
164	    _al_sane_strncpy(driver_desc, "", sizeof(driver_desc));
165            if (device->manufacturer) {
166	       strncat(driver_desc, device->manufacturer, sizeof(driver_desc)-1);
167	       strncat(driver_desc, " ", sizeof(driver_desc)-1);
168	    }
169	    if (device->product)
170	       strncat(driver_desc, device->product, sizeof(driver_desc)-1);
171	    mouse_macosx.desc = driver_desc;
172	 }
173      }
174      osx_hid_free(&devices);
175   }
176
177   _unix_lock_mutex(osx_event_mutex);
178   osx_emulate_mouse_buttons = (max_buttons == 1) ? TRUE : FALSE;
179   _unix_unlock_mutex(osx_event_mutex);
180
181   return max_buttons;
182}
183
184
185
186/* osx_mouse_exit:
187 *  Shuts down the mickey-mode driver.
188 */
189static void osx_mouse_exit(void)
190{
191   osx_cursor = osx_blank_cursor;
192   if (cursor)
193      [cursor release];
194   if (cursor_image)
195      [cursor_image release];
196   if (cursor_rep)
197      [cursor_rep release];
198   if (cursor_data)
199      free(cursor_data);
200   cursor = NULL;
201   cursor_image = NULL;
202   cursor_rep = NULL;
203   cursor_data = NULL;
204}
205
206
207
208/* osx_mouse_position:
209 *  Sets the position of the mickey-mode mouse.
210 */
211static void osx_mouse_position(int x, int y)
212{
213   CGPoint point;
214   NSRect frame;
215   int screen_height;
216
217   _unix_lock_mutex(osx_event_mutex);
218
219   _mouse_x = point.x = x;
220   _mouse_y = point.y = y;
221
222   if (osx_window) {
223      CFNumberGetValue(CFDictionaryGetValue(CGDisplayCurrentMode(kCGDirectMainDisplay), kCGDisplayHeight), kCFNumberSInt32Type, &screen_height);
224      frame = [osx_window frame];
225      point.x += frame.origin.x;
226      point.y += (screen_height - (frame.origin.y + gfx_driver->h));
227   }
228
229   CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, point);
230
231   mymickey_x = mymickey_y = 0;
232   osx_mouse_warped = TRUE;
233
234   _unix_unlock_mutex(osx_event_mutex);
235}
236
237
238
239/* osx_mouse_set_range:
240 *  Sets the range of the mickey-mode mouse.
241 */
242static void osx_mouse_set_range(int x1, int y1, int x2, int y2)
243{
244   mouse_minx = x1;
245   mouse_miny = y1;
246   mouse_maxx = x2;
247   mouse_maxy = y2;
248
249   osx_mouse_position(CLAMP(mouse_minx, _mouse_x, mouse_maxx), CLAMP(mouse_miny, _mouse_y, mouse_maxy));
250}
251
252
253
254/* osx_mouse_get_mickeys:
255 *  Reads the mickey-mode count.
256 */
257static void osx_mouse_get_mickeys(int *mickeyx, int *mickeyy)
258{
259   _unix_lock_mutex(osx_event_mutex);
260
261   *mickeyx = mymickey_x;
262   *mickeyy = mymickey_y;
263   mymickey_x = mymickey_y = 0;
264
265   _unix_unlock_mutex(osx_event_mutex);
266}
267
268
269
270/* osx_mouse_set_sprite:
271 *  Sets the hardware cursor sprite.
272 */
273int osx_mouse_set_sprite(BITMAP *sprite, int x, int y)
274{
275   int ix, iy;
276   int sw, sh;
277
278   if (!sprite)
279      return -1;
280   sw = sprite->w;
281   sh = sprite->h;
282   if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_2) {
283      // Before MacOS X 10.3, NSCursor can handle only 16x16 cursor sprites
284      // Pad to 16x16 or fail if the sprite is already larger.
285      if (sw>16 || sh>16)
286         return -1;
287      sh = sw = 16;
288   }
289
290   // Delete the old cursor (OK to send a message to nil)
291   [cursor release];
292
293   NSBitmapImageRep* cursor_rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
294                                       pixelsWide: sw
295                                       pixelsHigh: sh
296                                       bitsPerSample: 8
297                                       samplesPerPixel: 4
298                                       hasAlpha: YES
299                                       isPlanar: NO
300                                       colorSpaceName: NSDeviceRGBColorSpace
301                                       bytesPerRow: 0
302                                       bitsPerPixel: 0];
303   int bpp = bitmap_color_depth(sprite);
304   int mask = bitmap_mask_color(sprite);
305   for (iy = 0; iy< sh; ++iy) {
306      unsigned char* ptr = [cursor_rep bitmapData] + (iy * [cursor_rep bytesPerRow]);
307      for (ix = 0; ix< sw; ++ix) {
308         int color = is_inside_bitmap(sprite, ix, iy, 1)
309            ? getpixel(sprite, ix, iy) : mask;
310         // Disable the possibility of mouse sprites with alpha for now, because
311         // this causes the built-in cursors to be invisible in 32 bit mode.
312         // int alpha = (color == mask) ? 0 : ((bpp == 32) ? geta_depth(bpp, color) : 255);
313         int alpha = (color == mask) ? 0 : 255;
314         // BitmapImageReps use premultiplied alpha
315         ptr[0] = getb_depth(bpp, color) * alpha / 255;
316         ptr[1] = getg_depth(bpp, color) * alpha / 255;
317         ptr[2] = getr_depth(bpp, color) * alpha / 255;
318         ptr[3] = alpha;
319         ptr += 4;
320      }
321   }
322   NSImage* cursor_image = [[NSImage alloc] initWithSize: NSMakeSize(sw, sh)];
323   [cursor_image addRepresentation: cursor_rep];
324   [cursor_rep release];
325   cursor = [[NSCursor alloc] initWithImage: cursor_image
326                            hotSpot: NSMakePoint(x, y)];
327   [cursor_image release];
328   osx_change_cursor(requested_cursor = cursor);
329   return 0;
330}
331
332
333
334/* osx_mouse_show:
335 *  Show the hardware cursor.
336 */
337int osx_mouse_show(BITMAP *bmp, int x, int y)
338{
339   /* Only draw on screen */
340   if (!is_same_bitmap(bmp, screen))
341      return -1;
342
343   if (!requested_cursor)
344      return -1;
345
346   osx_change_cursor(requested_cursor);
347
348   return 0;
349}
350
351
352
353/* osx_mouse_hide:
354 *  Hide the hardware cursor.
355 */
356void osx_mouse_hide(void)
357{
358   osx_change_cursor(osx_blank_cursor);
359}
360
361
362
363/* osx_mouse_move:
364 *  Get mouse move notification. Not that we need it...
365 */
366void osx_mouse_move(int x, int y)
367{
368}
369
370
371
372/* osx_enable_hardware_cursor:
373 *  Enable hardware cursor - on OSX it's always enabled.
374 */
375void osx_enable_hardware_cursor(AL_CONST int mode)
376{
377   (void)mode;
378}
379
380
381
382/* osx_select_system_cursor:
383 *  Select a system cursor - on this platform, only the I-beam and the Arrow
384 *  are available as system cursors.
385 */
386static int osx_select_system_cursor(AL_CONST int cursor)
387{
388   switch (cursor) {
389   case MOUSE_CURSOR_ARROW:
390      requested_cursor = [NSCursor arrowCursor];
391      break;
392   case MOUSE_CURSOR_EDIT:
393      requested_cursor = [NSCursor IBeamCursor];
394      break;
395   default:
396      return 0;
397   }
398   osx_change_cursor(requested_cursor);
399   return cursor;
400}
401
402
403
404/* Local variables:       */
405/* c-basic-offset: 3      */
406/* indent-tabs-mode: nil  */
407/* End:                   */
408