RetroArch/ui/drivers/ui_cocoa.m
Alex Kornitzer ac4b49f1c2 ui_cocoa: fix mouse input for cocoa
This brings back two lines of code that have been removed over time but
appear to be required in order for mouse input to work on macOS.
2020-04-16 22:01:40 +01:00

726 lines
20 KiB
Objective-C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2013-2014 - Jason Fetters
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <objc/objc-runtime.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#include <queues/task_queue.h>
#include <retro_timers.h>
#include "cocoa/cocoa_defines.h"
#include "cocoa/cocoa_common.h"
#include "cocoa/apple_platform.h"
#include "../ui_companion_driver.h"
#include "../../input/drivers/cocoa_input.h"
#include "../../input/drivers_keyboard/keyboard_event_apple.h"
#include "../../frontend/frontend.h"
#include "../../configuration.h"
#include "../../paths.h"
#include "../../core.h"
#include "../../retroarch.h"
#include "../../tasks/task_content.h"
#include "../../tasks/tasks_internal.h"
#include "../../verbosity.h"
static void app_terminate(void)
{
[[NSApplication sharedApplication] terminate:nil];
}
#if defined(HAVE_COCOA_METAL)
@interface RAWindow : NSWindow
@end
@implementation RAWindow
#elif defined(HAVE_COCOA)
@interface RApplication : NSApplication
@end
@implementation RApplication
#endif
- (void)sendEvent:(NSEvent *)event {
[super sendEvent:event];
cocoa_input_data_t *apple = NULL;
NSEventType event_type = event.type;
switch ((int32_t)event_type)
{
case NSEventTypeKeyDown:
case NSEventTypeKeyUp:
{
NSString* ch = event.characters;
uint32_t character = 0;
uint32_t mod = 0;
if (ch && ch.length != 0)
{
uint32_t i;
character = [ch characterAtIndex:0];
if (event.modifierFlags & NSEventModifierFlagCapsLock)
mod |= RETROKMOD_CAPSLOCK;
if (event.modifierFlags & NSEventModifierFlagShift)
mod |= RETROKMOD_SHIFT;
if (event.modifierFlags & NSEventModifierFlagControl)
mod |= RETROKMOD_CTRL;
if (event.modifierFlags & NSEventModifierFlagOption)
mod |= RETROKMOD_ALT;
if (event.modifierFlags & NSEventModifierFlagCommand)
mod |= RETROKMOD_META;
if (event.modifierFlags & NSEventModifierFlagNumericPad)
mod |= RETROKMOD_NUMLOCK;
for (i = 1; i < ch.length; i++)
apple_input_keyboard_event(event_type == NSEventTypeKeyDown,
0, [ch characterAtIndex:i], mod, RETRO_DEVICE_KEYBOARD);
}
apple_input_keyboard_event(event_type == NSEventTypeKeyDown,
event.keyCode, character, mod, RETRO_DEVICE_KEYBOARD);
}
break;
#if defined(HAVE_COCOA_METAL)
case NSEventTypeFlagsChanged:
#elif defined(HAVE_COCOA)
case NSFlagsChanged:
#endif
{
static uint32_t old_flags = 0;
uint32_t new_flags = event.modifierFlags;
bool down = (new_flags & old_flags) == old_flags;
old_flags = new_flags;
apple_input_keyboard_event(down, event.keyCode,
0, event.modifierFlags, RETRO_DEVICE_KEYBOARD);
}
break;
case NSEventTypeMouseMoved:
case NSEventTypeLeftMouseDragged:
case NSEventTypeRightMouseDragged:
case NSEventTypeOtherMouseDragged:
{
NSPoint pos;
NSPoint mouse_pos;
apple = (cocoa_input_data_t*)input_driver_get_data();
if (!apple)
return;
pos.x = 0;
pos.y = 0;
/* Relative */
apple->mouse_rel_x = (int16_t)event.deltaX;
apple->mouse_rel_y = (int16_t)event.deltaY;
/* Absolute */
#if defined(HAVE_COCOA_METAL)
pos = [apple_platform.renderView convertPoint:[event locationInWindow] fromView:nil];
#elif defined(HAVE_COCOA)
pos = [[CocoaView get] convertPoint:[event locationInWindow] fromView:nil];
#endif
apple->touches[0].screen_x = (int16_t)pos.x;
apple->touches[0].screen_y = (int16_t)pos.y;
#if defined(HAVE_COCOA_METAL)
mouse_pos = [apple_platform.renderView convertPoint:[event locationInWindow] fromView:nil];
#elif defined(HAVE_COCOA)
mouse_pos = [[CocoaView get] convertPoint:[event locationInWindow] fromView:nil];
#endif
apple->window_pos_x = (int16_t)mouse_pos.x;
apple->window_pos_y = (int16_t)mouse_pos.y;
}
break;
#if defined(HAVE_COCOA_METAL)
case NSEventTypeScrollWheel:
#elif defined(HAVE_COCOA)
case NSScrollWheel:
#endif
/* TODO/FIXME - properly implement. */
break;
case NSEventTypeLeftMouseDown:
case NSEventTypeRightMouseDown:
case NSEventTypeOtherMouseDown:
{
#ifdef HAVE_COCOA_METAL
NSPoint pos = [apple_platform.renderView convertPoint:[event locationInWindow] fromView:nil];
#else
NSPoint pos = [[CocoaView get] convertPoint:[event locationInWindow] fromView:nil];
#endif
apple = (cocoa_input_data_t*)input_driver_get_data();
if (!apple || pos.y < 0)
return;
apple->mouse_buttons |= (1 << event.buttonNumber);
apple->touch_count = 1;
}
break;
case NSEventTypeLeftMouseUp:
case NSEventTypeRightMouseUp:
case NSEventTypeOtherMouseUp:
{
#ifdef HAVE_COCOA_METAL
NSPoint pos = [apple_platform.renderView convertPoint:[event locationInWindow] fromView:nil];
#else
NSPoint pos = [[CocoaView get] convertPoint:[event locationInWindow] fromView:nil];
#endif
apple = (cocoa_input_data_t*)input_driver_get_data();
if (!apple || pos.y < 0)
return;
apple->mouse_buttons &= ~(1 << event.buttonNumber);
apple->touch_count = 0;
}
break;
default:
break;
}
}
@end
static int waiting_argc;
static char** waiting_argv;
@implementation RetroArch_OSX
@synthesize window = _window;
#ifdef HAVE_COCOA_METAL
#else
#define NS_WINDOW_COLLECTION_BEHAVIOR_FULLSCREEN_PRIMARY (1 << 17)
- (void)dealloc
{
[_window release];
[super dealloc];
}
#endif
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
unsigned i;
#ifdef HAVE_COCOA_METAL
apple_platform = self;
self.window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
_listener = [WindowListener new];
[self.window setAcceptsMouseMovedEvents: YES];
[self.window setNextResponder:_listener];
self.window.delegate = _listener;
[[self.window contentView] setAutoresizesSubviews:YES];
#else
SEL selector = NSSelectorFromString(BOXSTRING("setCollectionBehavior:"));
SEL fsselector = NSSelectorFromString(BOXSTRING("toggleFullScreen:"));
apple_platform = self;
if ([self.window respondsToSelector:selector])
{
if ([self.window respondsToSelector:fsselector])
[self.window setCollectionBehavior:NS_WINDOW_COLLECTION_BEHAVIOR_FULLSCREEN_PRIMARY];
}
[self.window setAcceptsMouseMovedEvents: YES];
[[CocoaView get] setFrame: [[self.window contentView] bounds]];
[[self.window contentView] setAutoresizesSubviews:YES];
[[self.window contentView] addSubview:[CocoaView get]];
[self.window makeFirstResponder:[CocoaView get]];
#endif
for (i = 0; i < waiting_argc; i++)
{
if (string_is_equal(waiting_argv[i], "-NSDocumentRevisionsDebugMode"))
{
waiting_argv[i] = NULL;
waiting_argv[i+1] = NULL;
waiting_argc -= 2;
}
}
if (rarch_main(waiting_argc, waiting_argv, NULL))
app_terminate();
waiting_argc = 0;
#ifdef HAVE_COCOA_METAL
[self.window makeMainWindow];
[self.window makeKeyWindow];
#endif
[self performSelectorOnMainThread:@selector(rarch_main) withObject:nil waitUntilDone:NO];
}
#pragma mark - ApplePlatform
#ifdef HAVE_COCOA_METAL
- (void)setViewType:(apple_view_type_t)vt {
if (vt == _vt)
return;
RARCH_LOG("[Cocoa]: change view type: %d ? %d\n", _vt, vt);
_vt = vt;
if (_renderView != nil)
{
_renderView.wantsLayer = NO;
_renderView.layer = nil;
[_renderView removeFromSuperview];
self.window.contentView = nil;
_renderView = nil;
}
switch (vt) {
case APPLE_VIEW_TYPE_VULKAN:
case APPLE_VIEW_TYPE_METAL:
{
MetalView *v = [MetalView new];
v.paused = YES;
v.enableSetNeedsDisplay = NO;
_renderView = v;
}
break;
case APPLE_VIEW_TYPE_OPENGL:
{
_renderView = [CocoaView get];
break;
}
case APPLE_VIEW_TYPE_NONE:
default:
return;
}
_renderView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[_renderView setFrame: [[self.window contentView] bounds]];
self.window.contentView = _renderView;
self.window.contentView.nextResponder = _listener;
}
- (apple_view_type_t)viewType {
return _vt;
}
- (id)renderView {
return _renderView;
}
- (bool)hasFocus {
return [NSApp isActive];
}
- (void)setVideoMode:(gfx_ctx_mode_t)mode {
BOOL isFullScreen = (self.window.styleMask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen;
if (mode.fullscreen && !isFullScreen)
{
[self.window toggleFullScreen:self];
return;
}
if (!mode.fullscreen && isFullScreen)
{
[self.window toggleFullScreen:self];
}
if (mode.width > 0)
{
// HACK(sgc): ensure MTKView posts a drawable resize event
[self.window setContentSize:NSMakeSize(mode.width-1, mode.height)];
}
[self.window setContentSize:NSMakeSize(mode.width, mode.height)];
}
- (void)setCursorVisible:(bool)v {
if (v)
[NSCursor unhide];
else
[NSCursor hide];
}
- (bool)setDisableDisplaySleep:(bool)disable
{
if (disable && _sleepActivity == nil)
{
_sleepActivity = [NSProcessInfo.processInfo beginActivityWithOptions:NSActivityIdleDisplaySleepDisabled reason:@"disable screen saver"];
}
else if (!disable && _sleepActivity != nil)
{
[NSProcessInfo.processInfo endActivity:_sleepActivity];
_sleepActivity = nil;
}
return YES;
}
#endif
- (void) rarch_main
{
for (;;)
{
int ret;
#ifdef HAVE_QT
const ui_application_t *application = &ui_application_qt;
#else
const ui_application_t *application = &ui_application_cocoa;
#endif
if (application)
application->process_events();
ret = runloop_iterate();
task_queue_check();
while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, FALSE) == kCFRunLoopRunHandledSource);
if (ret == -1)
{
#ifdef HAVE_QT
ui_application_qt.quit();
#endif
break;
}
}
main_exit(NULL);
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
}
- (void)applicationWillResignActive:(NSNotification *)notification
{
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
{
return YES;
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
NSApplicationTerminateReply reply = NSTerminateNow;
if (rarch_ctl(RARCH_CTL_IS_INITED, NULL))
reply = NSTerminateCancel;
command_event(CMD_EVENT_QUIT, NULL);
return reply;
}
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
{
if ((filenames.count == 1) && [filenames objectAtIndex:0])
{
struct retro_system_info *system = runloop_get_libretro_system_info();
NSString *__core = [filenames objectAtIndex:0];
const char *core_name = system->library_name;
if (core_name)
{
content_ctx_info_t content_info = {0};
task_push_load_content_with_current_core_from_companion_ui(
__core.UTF8String,
&content_info,
CORE_TYPE_PLAIN,
NULL, NULL);
}
else
path_set(RARCH_PATH_CONTENT, __core.UTF8String);
[sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
}
else
{
const ui_msg_window_t *msg_window = ui_companion_driver_get_msg_window_ptr();
if (msg_window)
{
ui_msg_window_state msg_window_state;
msg_window_state.text = strdup("Cannot open multiple files");
msg_window_state.title = strdup(msg_hash_to_str(MSG_PROGRAM));
msg_window->information(&msg_window_state);
free(msg_window_state.text);
free(msg_window_state.title);
}
[sender replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
}
}
static void open_core_handler(ui_browser_window_state_t *state, bool result)
{
rarch_system_info_t *info = runloop_get_system_info();
settings_t *settings = config_get_ptr();
bool set_supports_no_game_enable = settings->bools.set_supports_no_game_enable;
if (!state)
return;
if (string_is_empty(state->result))
return;
if (!result)
return;
path_set(RARCH_PATH_CORE, state->result);
ui_companion_event_command(CMD_EVENT_LOAD_CORE);
if (info
&& info->load_no_content
&& set_supports_no_game_enable)
{
content_ctx_info_t content_info = {0};
path_clear(RARCH_PATH_CONTENT);
task_push_load_content_with_current_core_from_companion_ui(
NULL,
&content_info,
CORE_TYPE_PLAIN,
NULL, NULL);
}
}
static void open_document_handler(
ui_browser_window_state_t *state, bool result)
{
struct retro_system_info *system = runloop_get_libretro_system_info();
const char *core_name = system ? system->library_name : NULL;
if (!state)
return;
if (string_is_empty(state->result))
return;
if (!result)
return;
path_set(RARCH_PATH_CONTENT, state->result);
if (core_name)
{
content_ctx_info_t content_info = {0};
task_push_load_content_with_current_core_from_companion_ui(
NULL,
&content_info,
CORE_TYPE_PLAIN,
NULL, NULL);
}
}
- (IBAction)openCore:(id)sender {
const ui_browser_window_t *browser = ui_companion_driver_get_browser_window_ptr();
if (browser)
{
ui_browser_window_state_t browser_state;
bool result = false;
settings_t *settings = config_get_ptr();
const char *path_dir_libretro = settings->paths.directory_libretro;
browser_state.filters = strdup("dylib");
browser_state.filters_title = strdup(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_SETTINGS));
browser_state.title = strdup(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_LIST));
browser_state.startdir = strdup(path_dir_libretro);
result = browser->open(&browser_state);
open_core_handler(&browser_state, result);
free(browser_state.filters);
free(browser_state.filters_title);
free(browser_state.title);
free(browser_state.startdir);
}
}
- (void)openDocument:(id)sender
{
const ui_browser_window_t *browser = ui_companion_driver_get_browser_window_ptr();
if (browser)
{
ui_browser_window_state_t
browser_state = {{0}};
bool result = false;
settings_t *settings = config_get_ptr();
const char *path_dir_menu_content = settings->paths.directory_menu_content;
NSString *startdir = BOXSTRING(path_dir_menu_content);
if (!startdir.length)
startdir = BOXSTRING("/");
browser_state.title = strdup(msg_hash_to_str(
MENU_ENUM_LABEL_VALUE_LOAD_CONTENT_LIST));
browser_state.startdir = strdup([startdir UTF8String]);
result = browser->open(&browser_state);
open_document_handler(&browser_state, result);
free(browser_state.startdir);
free(browser_state.title);
}
}
- (void)unloadingCore
{
}
- (IBAction)showCoresDirectory:(id)sender
{
settings_t *settings = config_get_ptr();
const char *path_dir_libretro = settings->paths.directory_libretro;
[[NSWorkspace sharedWorkspace] openFile:BOXSTRING(path_dir_libretro)];
}
- (IBAction)showPreferences:(id)sender
{
}
- (IBAction)basicEvent:(id)sender
{
enum event_command cmd;
unsigned sender_tag = (unsigned)[sender tag];
switch (sender_tag)
{
case 1:
cmd = CMD_EVENT_RESET;
break;
case 2:
cmd = CMD_EVENT_LOAD_STATE;
break;
case 3:
cmd = CMD_EVENT_SAVE_STATE;
break;
case 4:
cmd = CMD_EVENT_DISK_EJECT_TOGGLE;
break;
case 5:
cmd = CMD_EVENT_DISK_PREV;
break;
case 6:
cmd = CMD_EVENT_DISK_NEXT;
break;
case 7:
cmd = CMD_EVENT_GRAB_MOUSE_TOGGLE;
break;
case 8:
cmd = CMD_EVENT_MENU_TOGGLE;
break;
case 9:
cmd = CMD_EVENT_PAUSE_TOGGLE;
break;
case 20:
cmd = CMD_EVENT_FULLSCREEN_TOGGLE;
break;
default:
cmd = CMD_EVENT_NONE;
break;
}
if (sender_tag >= 10 && sender_tag <= 19)
{
unsigned idx = (sender_tag - (10-1));
rarch_ctl(RARCH_CTL_SET_WINDOWED_SCALE, &idx);
cmd = CMD_EVENT_RESIZE_WINDOWED_SCALE;
}
ui_companion_event_command(cmd);
}
- (void)alertDidEnd:(NSAlert *)alert returnCode:(int32_t)returnCode contextInfo:(void *)contextInfo
{
[[NSApplication sharedApplication] stopModal];
}
@end
int main(int argc, char *argv[])
{
if (argc == 2)
{
if (argv[1] != '\0')
if (!strncmp(argv[1], "-psn", 4))
argc = 1;
}
waiting_argc = argc;
waiting_argv = argv;
return NSApplicationMain(argc, (const char **) argv);
}
typedef struct ui_companion_cocoa
{
void *empty;
} ui_companion_cocoa_t;
static void ui_companion_cocoa_deinit(void *data)
{
ui_companion_cocoa_t *handle = (ui_companion_cocoa_t*)data;
app_terminate();
if (handle)
free(handle);
}
static void *ui_companion_cocoa_init(void)
{
ui_companion_cocoa_t *handle = (ui_companion_cocoa_t*)
calloc(1, sizeof(*handle));
if (!handle)
return NULL;
return handle;
}
static void ui_companion_cocoa_notify_content_loaded(void *data) { }
static void ui_companion_cocoa_toggle(void *data, bool force) { }
static void ui_companion_cocoa_event_command(void *data, enum event_command cmd)
{ }
static void ui_companion_cocoa_notify_list_pushed(void *data,
file_list_t *list, file_list_t *menu_list) { }
static void *ui_companion_cocoa_get_main_window(void *data)
{
return (BRIDGE void *)((RetroArch_OSX*)[[NSApplication sharedApplication] delegate]).window;
}
ui_companion_driver_t ui_companion_cocoa = {
ui_companion_cocoa_init,
ui_companion_cocoa_deinit,
ui_companion_cocoa_toggle,
ui_companion_cocoa_event_command,
ui_companion_cocoa_notify_content_loaded,
ui_companion_cocoa_notify_list_pushed,
NULL, /* notify_refresh */
NULL, /* msg_queue_push */
NULL, /* render_messagebox */
ui_companion_cocoa_get_main_window,
NULL, /* log_msg */
&ui_browser_window_cocoa,
&ui_msg_window_cocoa,
&ui_window_cocoa,
&ui_application_cocoa,
"cocoa",
};