From ad7b7bd9c61fc61e5ae5b8434d0d687b63a755c7 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Tue, 1 Oct 2013 14:19:25 +0300 Subject: [PATCH] Basic OS X support The event tester works and reports sensible events. Some things are still missing, notably GL contexts and window resize tracking. --- Build | 37 ++++++-- source/graphics/cocoa/cocoadisplay.h | 22 +++++ source/graphics/cocoa/cocoadisplay.m | 76 ++++++++++++++++ source/graphics/cocoa/cocoaevent.h | 69 +++++++++++++++ source/graphics/cocoa/cocoaevent.m | 62 +++++++++++++ source/graphics/cocoa/cocoawindow.h | 24 +++++ source/graphics/cocoa/cocoawindow.m | 108 +++++++++++++++++++++++ source/graphics/cocoa/display.cpp | 46 ++++++++++ source/graphics/cocoa/display_platform.h | 16 ++++ source/graphics/cocoa/window.cpp | 79 +++++++++++++++++ source/graphics/cocoa/window_platform.h | 24 +++++ source/input/cocoa/keyboard.cpp | 31 +++++++ source/input/cocoa/keys.cpp | 50 +++++++++++ source/input/cocoa/mouse.cpp | 28 ++++++ 14 files changed, 664 insertions(+), 8 deletions(-) create mode 100644 source/graphics/cocoa/cocoadisplay.h create mode 100644 source/graphics/cocoa/cocoadisplay.m create mode 100644 source/graphics/cocoa/cocoaevent.h create mode 100644 source/graphics/cocoa/cocoaevent.m create mode 100644 source/graphics/cocoa/cocoawindow.h create mode 100644 source/graphics/cocoa/cocoawindow.m create mode 100644 source/graphics/cocoa/display.cpp create mode 100644 source/graphics/cocoa/display_platform.h create mode 100644 source/graphics/cocoa/window.cpp create mode 100644 source/graphics/cocoa/window_platform.h create mode 100644 source/input/cocoa/keyboard.cpp create mode 100644 source/input/cocoa/keys.cpp create mode 100644 source/input/cocoa/mouse.cpp diff --git a/Build b/Build index 91255ce..9e564ff 100644 --- a/Build +++ b/Build @@ -7,7 +7,10 @@ package "mspgui" require "sigc++-2.0"; if_arch "!windows" { - require "xlib"; + if_arch "!darwin" + { + require "xlib"; + }; }; if_arch "windows" { @@ -16,6 +19,14 @@ package "mspgui" library "gdi32"; }; }; + if_arch "darwin" + { + build_info + { + library "CoreFoundation.framework"; + library "AppKit.framework"; + }; + }; feature "devil" "Include DevIL support for loading image files"; if_feature "devil" @@ -43,12 +54,15 @@ package "mspgui" if_arch "!windows" { - feature "xrandr" "Include support for video mode switching with XRandR"; - if_feature "xrandr" + if_arch "!darwin" { - build_info + feature "xrandr" "Include support for video mode switching with XRandR"; + if_feature "xrandr" { - library "Xrandr"; + build_info + { + library "Xrandr"; + }; }; }; }; @@ -65,12 +79,19 @@ package "mspgui" overlay "wgl"; }; }; + if_arch "darwin" + { + overlay "cocoa"; + }; if_arch "!windows" { - overlay "x11"; - if_feature "opengl" + if_arch "!darwin" { - overlay "glx"; + overlay "x11"; + if_feature "opengl" + { + overlay "glx"; + }; }; }; install true; diff --git a/source/graphics/cocoa/cocoadisplay.h b/source/graphics/cocoa/cocoadisplay.h new file mode 100644 index 0000000..b0d5abc --- /dev/null +++ b/source/graphics/cocoa/cocoadisplay.h @@ -0,0 +1,22 @@ +#ifndef MSP_GRAPHICS_COCOADISPLAY_H_ +#define MSP_GRAPHICS_COCOADISPLAY_H_ + +#include "cocoaevent.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct _CocoaDisplay; +typedef struct _CocoaDisplay CocoaDisplay; + +CocoaDisplay *create_display(); +void destroy_display(CocoaDisplay *); +void queue_event(CocoaDisplay *, CocoaEvent *); +bool get_event(CocoaDisplay *, CocoaEvent *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/graphics/cocoa/cocoadisplay.m b/source/graphics/cocoa/cocoadisplay.m new file mode 100644 index 0000000..5b7b064 --- /dev/null +++ b/source/graphics/cocoa/cocoadisplay.m @@ -0,0 +1,76 @@ +#import +#import +#include "cocoadisplay.h" + +void convert_event(NSEvent *, CocoaEvent *); + +struct _CocoaDisplay +{ + NSApplication *app; + CFMutableArrayRef event_queue; +}; + +const void *copy_event(CFAllocatorRef, const void *); +void free_event(CFAllocatorRef, const void *); + +CocoaDisplay *create_display() +{ + CocoaDisplay *display = (CocoaDisplay *)malloc(sizeof(CocoaDisplay)); + display->app = [NSApplication sharedApplication]; + // Since OS X 10.6 + //[display->app setActivationPolicy:NSApplicationActivationPolicyRegular]; + [display->app finishLaunching]; + + CFArrayCallBacks callbacks; + callbacks.version = 0; + callbacks.retain = copy_event; + callbacks.release = free_event; + callbacks.copyDescription = NULL; + callbacks.equal = NULL; + display->event_queue = CFArrayCreateMutable(NULL, 0, &callbacks); + + return display; +} + +void destroy_display(CocoaDisplay *display) +{ + CFRelease(display->event_queue); + free(display); +} + +void queue_event(CocoaDisplay *display, CocoaEvent *event) +{ + CFArrayAppendValue(display->event_queue, event); +} + +bool get_event(CocoaDisplay *display, CocoaEvent *buf) +{ + if(CFArrayGetCount(display->event_queue)) + { + *buf = *(const CocoaEvent *)CFArrayGetValueAtIndex(display->event_queue, 0); + CFArrayRemoveValueAtIndex(display->event_queue, 0); + return true; + } + + NSEvent *event = [display->app nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES]; + if(event) + { + [display->app sendEvent:event]; + convert_event(event, buf); + return true; + } + + return false; +} + +const void *copy_event(CFAllocatorRef allocator, const void *event) +{ + void *copy = CFAllocatorAllocate(allocator, sizeof(CocoaEvent), 0); + memcpy(copy, event, sizeof(CocoaEvent)); + return copy; +} + +void free_event(CFAllocatorRef allocator, const void *event) +{ + CFAllocatorDeallocate(allocator, (void *)event); +} diff --git a/source/graphics/cocoa/cocoaevent.h b/source/graphics/cocoa/cocoaevent.h new file mode 100644 index 0000000..440b4ea --- /dev/null +++ b/source/graphics/cocoa/cocoaevent.h @@ -0,0 +1,69 @@ +#ifndef MSP_GRAPHICS_COCOAEVENT_H_ +#define MSP_GRAPHICS_COCOAEVENT_H_ + +#include "cocoawindow.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + LEFT_MOUSE_DOWN = 1, + LEFT_MOUSE_UP = 2, + RIGHT_MOUSE_DOWN = 3, + RIGHT_MOUSE_UP = 4, + MOUSE_MOVED = 5, + KEY_DOWN = 10, + KEY_UP = 11, + SCROLL_WHEEL = 22, + OTHER_MOUSE_DOWN = 25, + OTHER_MOUSE_UP = 26, + WINDOW_CLOSED = 1000 +} CocoaEventType; + +typedef struct +{ + CocoaEventType type; + CocoaWindow *window; +} CocoaAnyEvent; + +typedef struct +{ + CocoaEventType type; + CocoaWindow *window; + unsigned button; + bool state; +} CocoaButtonEvent; + +typedef struct +{ + CocoaEventType type; + CocoaWindow *window; + float x; + float y; +} CocoaMotionEvent; + +typedef struct +{ + CocoaEventType type; + CocoaWindow *window; + unsigned key; + bool state; + int character; +} CocoaKeyEvent; + +typedef union +{ + CocoaEventType type; + CocoaAnyEvent any; + CocoaButtonEvent button; + CocoaMotionEvent motion; + CocoaKeyEvent key; +} CocoaEvent; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/graphics/cocoa/cocoaevent.m b/source/graphics/cocoa/cocoaevent.m new file mode 100644 index 0000000..96f4aae --- /dev/null +++ b/source/graphics/cocoa/cocoaevent.m @@ -0,0 +1,62 @@ +#import +#import +#import +#include "cocoaevent.h" + +NSWindow *get_native_window(CocoaWindow *); +CocoaWindow *lookup_window(NSWindow *); + +CocoaWindow *last_window = 0; + +void convert_event(NSEvent *ev, CocoaEvent *cev) +{ + NSEventType type = [ev type]; + cev->any.type = (CocoaEventType)type; + + NSWindow *window = [ev window]; + if(window) + { + cev->any.window = lookup_window([ev window]); + last_window = cev->any.window; + } + + if(type==NSLeftMouseDown || type==NSLeftMouseUp) + { + cev->button.button = 1; + cev->button.state = (type==NSLeftMouseDown); + } + else if(type==NSRightMouseDown || type==NSRightMouseUp) + { + cev->button.button = 3; + cev->button.state = (type==NSRightMouseDown); + } + else if(type==NSOtherMouseDown || type==NSOtherMouseUp) + { + cev->button.button = [ev buttonNumber]; + cev->button.state = (type==NSOtherMouseDown); + } + else if(type==NSMouseMoved) + { + NSPoint pt = [ev locationInWindow]; + if(!window && last_window) + { + /* If the pointer leaves the window, any subsequent MouseMoved events + are reported to a null window until the window is clicked. */ + window = get_native_window(last_window); + pt = [window convertScreenToBase:pt]; + cev->any.window = last_window; + } + cev->motion.x = pt.x; + cev->motion.y = pt.y; + } + else if(type==NSKeyDown) + { + NSString *chars = [[ev charactersIgnoringModifiers] uppercaseString]; + if([chars length]==1) + cev->key.key = [chars characterAtIndex:0]; + + chars = [ev characters]; + if([chars length]==1) + cev->key.character = [chars characterAtIndex:0]; + } +} diff --git a/source/graphics/cocoa/cocoawindow.h b/source/graphics/cocoa/cocoawindow.h new file mode 100644 index 0000000..364eed9 --- /dev/null +++ b/source/graphics/cocoa/cocoawindow.h @@ -0,0 +1,24 @@ +#ifndef MSP_GRAPHICS_COCOAWINDOW_H_ +#define MSP_GRAPHICS_COCOAWINDOW_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct _CocoaDisplay; + +struct _CocoaWindow; +typedef struct _CocoaWindow CocoaWindow; + +CocoaWindow *create_window(struct _CocoaDisplay *, unsigned, unsigned, bool, bool); +void destroy_window(CocoaWindow *); +void set_window_title(CocoaWindow *, const char *); +void set_window_size(CocoaWindow *, unsigned, unsigned); +void show_window(CocoaWindow *); +void hide_window(CocoaWindow *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/graphics/cocoa/cocoawindow.m b/source/graphics/cocoa/cocoawindow.m new file mode 100644 index 0000000..6929520 --- /dev/null +++ b/source/graphics/cocoa/cocoawindow.m @@ -0,0 +1,108 @@ +#import +#import +#include "cocoadisplay.h" +#include "cocoawindow.h" + +@interface WindowDelegate: NSObject + +- (void)windowWillClose:(NSNotification *)notification; + +@end + +struct _CocoaWindow +{ + CocoaDisplay *display; + __strong NSWindow *window; + NSUInteger style; +}; + +CFMutableDictionaryRef windows = NULL; +CFMutableArrayRef event_queue = NULL; + +CocoaWindow *create_window(CocoaDisplay *display, unsigned width, unsigned height, bool fullscreen, bool resizable) +{ + NSRect rect = NSMakeRect(0, 0, width, height); + NSUInteger style = NSTitledWindowMask|NSClosableWindowMask; + if(resizable) + style |= NSResizableWindowMask; + NSWindow *window = [NSWindow alloc]; + window = [window initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO]; + [window setAcceptsMouseMovedEvents:YES]; + + (void)fullscreen; + + CocoaWindow *wrapper = (CocoaWindow *)malloc(sizeof(CocoaWindow)); + wrapper->display = display; + wrapper->window = window; + wrapper->style = style; + + [window setDelegate:[[WindowDelegate alloc] init]]; + + if(!windows) + windows = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); + CFDictionaryAddValue(windows, window, wrapper); + + return wrapper; +} + +void destroy_window(CocoaWindow *window) +{ + CFDictionaryRemoveValue(windows, window->window); + if(!CFDictionaryGetCount(windows)) + { + CFRelease(windows); + windows = NULL; + } + + [window->window release]; + free(window); +} + +NSWindow *get_native_window(CocoaWindow *window) +{ + return window->window; +} + +CocoaWindow *lookup_window(NSWindow *window) +{ + return (CocoaWindow *)CFDictionaryGetValue(windows, window); +} + +void set_window_title(CocoaWindow *window, const char *title) +{ + [window->window setTitle:[NSString stringWithCString:title encoding:NSUTF8StringEncoding]]; +} + +void set_window_size(CocoaWindow *window, unsigned width, unsigned height) +{ + NSRect rect = NSMakeRect(0, 0, width, height); + rect = [NSWindow frameRectForContentRect:rect styleMask:window->style]; + [window->window setFrame:rect display:YES]; +} + +void show_window(CocoaWindow *window) +{ + [window->window makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:NO]; +} + +void hide_window(CocoaWindow *window) +{ + (void)window; +} + +@implementation WindowDelegate + +- (void)windowWillClose:(NSNotification *)notification +{ + CocoaWindow *window = lookup_window((NSWindow *)[notification object]); + if(window) + { + CocoaEvent event; + event.any.type = WINDOW_CLOSED; + event.any.window = window; + queue_event(window->display, &event); + } +} + +@end diff --git a/source/graphics/cocoa/display.cpp b/source/graphics/cocoa/display.cpp new file mode 100644 index 0000000..5be28cd --- /dev/null +++ b/source/graphics/cocoa/display.cpp @@ -0,0 +1,46 @@ +#include +#include "cocoadisplay.h" +#include "display.h" +#include "display_private.h" + +using namespace std; + +namespace Msp { +namespace Graphics { + +Display::Display(const std::string &): + priv(new Private) +{ + priv->display = create_display(); +} + +Display::~Display() +{ + destroy_display(priv->display); + delete priv; +} + +void Display::set_mode(const VideoMode &, bool) +{ + throw runtime_error("video mode switching not supported"); +} + +bool Display::process_events() +{ + Window::Event event; + if(!get_event(priv->display, &event.cevent)) + return false; + + map::iterator i = priv->windows.find(event.cevent.any.window); + if(i!=priv->windows.end()) + i->second->event(event); + + return true; +} + +void Display::check_error() +{ +} + +} // namespace Graphics +} // namespace Msp diff --git a/source/graphics/cocoa/display_platform.h b/source/graphics/cocoa/display_platform.h new file mode 100644 index 0000000..2c59e06 --- /dev/null +++ b/source/graphics/cocoa/display_platform.h @@ -0,0 +1,16 @@ +#ifndef MSP_GRAPHICS_DISPLAY_PLATFORM_H_ +#define MSP_GRAPHICS_DISPLAY_PLATFORM_H_ + +#include "cocoadisplay.h" + +namespace Msp { +namespace Graphics { + +typedef CocoaDisplay *DisplayHandle; +typedef int MonitorHandle; +typedef int ModeHandle; + +} // namespace Graphics +} // namespace Msp + +#endif diff --git a/source/graphics/cocoa/window.cpp b/source/graphics/cocoa/window.cpp new file mode 100644 index 0000000..321934c --- /dev/null +++ b/source/graphics/cocoa/window.cpp @@ -0,0 +1,79 @@ +#include "cocoadisplay.h" +#include "cocoawindow.h" +#include "display.h" +#include "display_private.h" +#include "window.h" +#include "window_private.h" + +namespace Msp { +namespace Graphics { + +void Window::platform_init() +{ + CocoaDisplay *dpy = display.get_private().display; + priv->window = create_window(dpy, options.width, options.height, options.fullscreen, options.resizable); +} + +void Window::platform_cleanup() +{ + destroy_window(priv->window); +} + +void Window::set_title(const std::string &title) +{ + set_window_title(priv->window, title.c_str()); +} + +void Window::platform_reconfigure(bool /*fullscreen_changed*/) +{ + set_window_size(priv->window, options.width, options.height); +} + +void Window::show_cursor(bool) +{ +} + +void Window::warp_pointer(int, int) +{ +} + +void Window::platform_show() +{ + show_window(priv->window); +} + +void Window::platform_hide() +{ + hide_window(priv->window); +} + +bool Window::event(const Event &ev) +{ + switch(ev.cevent.type) + { + case LEFT_MOUSE_DOWN: + case LEFT_MOUSE_UP: + case RIGHT_MOUSE_DOWN: + case RIGHT_MOUSE_UP: + case KEY_DOWN: + case KEY_UP: + case OTHER_MOUSE_DOWN: + case OTHER_MOUSE_UP: + signal_input_event.emit(ev); + break; + case MOUSE_MOVED: + if(ev.cevent.motion.x>=0 && ev.cevent.motion.x=0 && ev.cevent.motion.y +#include "keyboard.h" +#include "keys_private.h" + +using namespace std; + +namespace Msp { +namespace Input { + +string Keyboard::get_button_name(unsigned btn) const +{ + return Device::get_button_name(btn); +} + +void Keyboard::input_event(const Graphics::Window::Event &event) +{ + switch(event.cevent.type) + { + case KEY_DOWN: + case KEY_UP: + if(unsigned key = key_from_sys(event.cevent.key.key)) + set_button_state(key, event.cevent.type==KEY_DOWN, true); + if(event.cevent.type==KEY_DOWN && event.cevent.key.character>0) + signal_character.emit(event.cevent.key.character); + break; + default:; + } +} + +} // namespace Inpus +} // namespace Msp diff --git a/source/input/cocoa/keys.cpp b/source/input/cocoa/keys.cpp new file mode 100644 index 0000000..7d64398 --- /dev/null +++ b/source/input/cocoa/keys.cpp @@ -0,0 +1,50 @@ +#include "keys.h" + +namespace Msp { +namespace Input { + +unsigned sys_keymap[N_KEYS_]= +{ + 0, 0, 0, 0, 0, 0, 0, 0, + 8, 9, 10, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 27, 0, 0, 0, 0, + + ' ', 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 0, 0, 0, 0, 0, 0, + + 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0xF702, 0xF703, 0xF700, 0xF701, 0, 0, 0, 0, + 0xF729, 0xF72B, 0xF72C, 0xF72D, 0xF727, 0xF728, 0, 0, + 0, 0xF704, 0xF705, 0xF706, 0xF707, 0xF708, 0xF709, 0xF70A, + 0xF70B, 0xF70C, 0xF70D, 0xF70E, 0xF70F, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0xF72F, 0xF739, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0xF730, 0xF72E, 0xF735, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +} // namespace Input +} // namespace Msp diff --git a/source/input/cocoa/mouse.cpp b/source/input/cocoa/mouse.cpp new file mode 100644 index 0000000..b1d3619 --- /dev/null +++ b/source/input/cocoa/mouse.cpp @@ -0,0 +1,28 @@ +#include +#include "mouse.h" + +namespace Msp { +namespace Input { + +void Mouse::input_event(const Graphics::Window::Event &event) +{ + switch(event.cevent.type) + { + case LEFT_MOUSE_DOWN: + case LEFT_MOUSE_UP: + case RIGHT_MOUSE_DOWN: + case RIGHT_MOUSE_UP: + case OTHER_MOUSE_DOWN: + case OTHER_MOUSE_UP: + set_button_state(event.cevent.button.button, event.cevent.button.state, true); + break; + case MOUSE_MOVED: + set_axis_value(0, event.cevent.motion.x*2.0/window.get_width()-1.0, true); + set_axis_value(1, event.cevent.motion.y*2.0/window.get_height()-1.0, true); + break; + default:; + } +} + +} // namespace Input +} // namespace Msp -- 2.43.0