Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
gui_osx.c
Go to the documentation of this file.
1 /* Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
2  ~/xemu/gui/gui_osx.c: UI implementation for MacOS of Xemu's UI abstraction layer
3  Copyright (C)2020 Hernán Di Pietro <hernan.di.pietro@gmail.com>
4  Copyright (C)2016-2022 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
5 
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
19 
20 #include <objc/objc-runtime.h>
21 #include <CoreGraphics/CGBase.h>
22 #include <CoreGraphics/CGGeometry.h>
23 
24 typedef CGPoint NSPoint;
25 
26 static id auto_release_pool;
27 static id application;
28 
29 static const unsigned long NSFileHandlingPanelOKButton = 1;
30 static const unsigned long NSFileHandlingPanelCancelButton = 0;
31 
32 #ifndef GUI_HAS_POPUP
33 #define GUI_HAS_POPUP
34 #endif
35 
36 // (!)
37 // New Apple SDKs objc_msgSend prototype changed to *force* callers
38 // to cast to proper types!. So this is ugly and verbose, but works.
39 
40 static void _xemumacgui_menu_action_handler ( id self, SEL selector, id sender )
41 {
42  id menu_obj = ((id (*) (id, SEL)) objc_msgSend)(sender, sel_registerName("representedObject"));
43  const struct menu_st* menu_item = (const struct menu_st*) ((id (*) (id, SEL)) objc_msgSend)(menu_obj, sel_registerName("pointerValue"));
44  if (menu_item && ((menu_item->type & 0xFF) == XEMUGUI_MENUID_CALLABLE)) {
45  DEBUGPRINT("GUI: menu point \"%s\" has been activated." NL, menu_item->name);
46  ((xemugui_callback_t)(menu_item->handler))(menu_item, NULL);
47  } else {
48  DEBUGPRINT("GUI: menu point is NOT activated in action handler! menu_item=%p menu_item->type=%d" NL, menu_item, menu_item ? menu_item->type : -1 );
49  }
50 }
51 
52 
53 // TODO: separator line after menu item, checked/unchecked status for menu items
54 static id _xemumacgui_r_menu_builder ( const struct menu_st desc[], const char *parent_name )
55 {
56  id menu_item, menu_state;
57  id ui_menu = ((id (*) (Class, SEL)) objc_msgSend)(objc_getClass("NSMenu"), sel_registerName("new"));
58  ((void (*) (id, SEL)) objc_msgSend)(ui_menu, sel_registerName("autorelease"));
59  for (int a = 0; desc[a].name; a++) {
60  int type = desc[a].type;
61  // Some sanity checks:
62  if (
63  ((type & 0xFF) != XEMUGUI_MENUID_SUBMENU && !desc[a].handler) ||
64  ((type & 0xFF) == XEMUGUI_MENUID_SUBMENU && (desc[a].handler || !desc[a].user_data)) ||
65  !desc[a].name || (
66  (type & 0xFF) != XEMUGUI_MENUID_SUBMENU &&
67  (type & 0xFF) != XEMUGUI_MENUID_CALLABLE
68  )
69  ) {
70  DEBUGPRINT("GUI: invalid menu entry found, skipping it (item #%d of menu \"%s\")" NL, a, parent_name);
71  continue;
72  }
73  // Queryback feature, markes entries are called on menu-build time to be able to modify themselves dynamically (ie, on/off options depending current state)
75  DEBUGGUI("GUI: query-back for \"%s\"" NL, desc[a].name);
76  ((xemugui_callback_t)(desc[a].handler))(&desc[a], &type);
77  }
79  continue;
81  menu_item = ((id (*) (Class, SEL)) objc_msgSend)(objc_getClass("NSMenuItem"), sel_registerName("separatorItem"));
82  } else {
83  menu_item = ((id (*) (Class, SEL)) objc_msgSend)(objc_getClass("NSMenuItem"), sel_registerName("alloc"));
84  ((void (*) (id, SEL)) objc_msgSend)(menu_item, sel_registerName("autorelease"));
85  id str_name = ((id (*) (Class, SEL, const char*)) objc_msgSend)(
86  objc_getClass("NSString"),
87  sel_registerName("stringWithUTF8String:"),
88  desc[a].name
89  );
90  id str_key = ((id (*) (Class, SEL, const char*)) objc_msgSend)(
91  objc_getClass("NSString"),
92  sel_registerName("stringWithUTF8String:"),
93  ""
94  );
95  ((void (*) (id, SEL, id, SEL, id))objc_msgSend)(
96  menu_item,
97  sel_registerName("initWithTitle:action:keyEquivalent:"),
98  str_name,
99  sel_registerName("menuActionHandler"),
100  str_key
101  );
102  ((void (*) (id, SEL, BOOL)) objc_msgSend)(menu_item, sel_registerName("setEnabled:"), YES);
103  id menu_object = ((id (*) (Class, SEL, id)) objc_msgSend) (objc_getClass("NSValue"), sel_registerName("valueWithPointer:"),(id) &desc[a]);
104  ((void (*) (id, SEL, id))objc_msgSend)(menu_item, sel_registerName("setRepresentedObject:"), menu_object);
105  }
107  ((id (*) (id, SEL, int)) objc_msgSend)(menu_item, sel_registerName("setState:"), 1);
108  }
109  ((void (*) (id, SEL, id))objc_msgSend)(ui_menu, sel_registerName("addItem:"), menu_item);
110  if ((type & 0xFF) == XEMUGUI_MENUID_SUBMENU) {
111  // submenus use the user_data as the submenu menu_st struct pointer!
112  id sub_menu = _xemumacgui_r_menu_builder(desc[a].user_data, desc[a].name);
113  ((void (*) (id, SEL, id, id))objc_msgSend)(ui_menu, sel_registerName("setSubmenu:forItem:"), sub_menu, menu_item);
114  }
115  }
116  return ui_menu;
117 }
118 
119 
120 static int xemuosxgui_init ( void )
121 {
122  DEBUGPRINT("GUI: macOS Cocoa initialization" NL);
123  auto_release_pool = ((id (*) (Class, SEL)) objc_msgSend)(objc_getClass("NSAutoreleasePool"), sel_registerName("new"));
124  application = ((id (*) (Class, SEL)) objc_msgSend)(objc_getClass("NSApplication"), sel_registerName("sharedApplication"));
125  id app_delegate = ((id (*) (id, SEL)) objc_msgSend)(application, sel_registerName("delegate"));
126  Class xemu_ui_delegate_class = ((Class (*) (id, SEL)) objc_msgSend)(app_delegate, sel_registerName("class"));
127  class_addMethod(xemu_ui_delegate_class, sel_registerName("menuActionHandler"), (IMP)_xemumacgui_menu_action_handler, "v@:@");
128  return 0;
129 }
130 
131 
132 static int xemuosxgui_popup ( const struct menu_st desc[] )
133 {
134  // If the SDL window is not active, make this right-click to do application activation.
135  if ( ! (((id(*)(id,SEL))objc_msgSend) (application, sel_registerName("mainWindow")))) {
136  ((void(*)(id,SEL,BOOL))objc_msgSend) (application, sel_registerName("activateIgnoringOtherApps:"), YES);
137  return 0;
138  }
139  id ui_menu = _xemumacgui_r_menu_builder(desc, XEMUGUI_MAINMENU_NAME);
140  if (!ui_menu) {
141  DEBUGPRINT("GUI: Error building menu" NL);
142  return 1;
143  }
144  NSPoint mouse_location = ((NSPoint (*) (Class, SEL)) objc_msgSend)
145  (objc_getClass("NSEvent"), sel_registerName("mouseLocation"));
146  ((BOOL (*) (id, SEL, id, NSPoint, id)) objc_msgSend)
147  (ui_menu, sel_registerName("popUpMenuPositioningItem:atLocation:inView:"), nil, mouse_location, nil);
148  return 0;
149 }
150 
151 static int xemuosxgui_file_selector ( int dialog_mode, const char *dialog_title, char *default_dir, char *selected, int path_max_size )
152 {
153  char panel_type[12] = { 0 };
154  char open_selector[10] = { 0 };
155  switch ( dialog_mode & 3 ) {
156  case XEMUGUI_FSEL_OPEN:
157  strncpy(panel_type, "NSOpenPanel", sizeof(panel_type));
158  strncpy(open_selector, "openPanel", sizeof(open_selector));
159  break;
160  case XEMUGUI_FSEL_SAVE:
161  strncpy(panel_type, "NSSavePanel", sizeof(panel_type));
162  strncpy(open_selector, "savePanel", sizeof(open_selector));
163  break;
164  default:
165  FATAL("Invalid mode for UI selector: %d", dialog_mode & 3);
166  return 1;
167  }
168  *selected = '\0';
169  id file_panel = ((id (*) (Class, SEL)) objc_msgSend)(objc_getClass(panel_type), sel_registerName(open_selector));
170  ((void (*) (id, SEL)) objc_msgSend)(file_panel, sel_registerName("autorelease"));
171  id main_window = ((id (*) (id, SEL)) objc_msgSend)(application, sel_registerName("mainWindow"));
172  if ((dialog_mode & 3) == XEMUGUI_FSEL_OPEN) {
173  ((void (*) (id, SEL, BOOL)) objc_msgSend)(file_panel, sel_registerName("setCanChooseDirectories:"), NO);
174  ((void (*) (id, SEL, BOOL)) objc_msgSend)(file_panel, sel_registerName("setAllowsMultipleSelection:"), NO);
175  }
176  id dialog_title_str = ((id (*) (Class, SEL, const char*)) objc_msgSend)
177  (objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), dialog_title);
178  ((void (*) (id, SEL, id)) objc_msgSend)(file_panel, sel_registerName("setMessage:"), dialog_title_str);
179  if (default_dir) {
180  id default_dir_str = ((id (*) (Class, SEL, const char*)) objc_msgSend)
181  (objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), default_dir);
182  id dir_url = ((id (*) (Class, SEL, id)) objc_msgSend)(objc_getClass("NSURL"), sel_registerName("fileURLWithPath:"), default_dir_str);
183  ((void (*) (id, SEL, id)) objc_msgSend) (file_panel, sel_registerName("directoryURL"), dir_url);
184  }
185  id panel_result = ((id (*) (id, SEL)) objc_msgSend)(file_panel, sel_registerName("runModal"));
186  ((void (*) (id, SEL)) objc_msgSend)(main_window, sel_registerName("makeKeyWindow")); // Ensure focus returns to Xemu window.
187  if ((unsigned long)panel_result == NSFileHandlingPanelOKButton) {
188  DEBUGPRINT("GUI: macOS panel OK button pressed" NL);
189  id filename_url;
190  if ((dialog_mode & 3) == XEMUGUI_FSEL_OPEN) {
191  id url_array = ((id (*) (id, SEL)) objc_msgSend)(file_panel, sel_registerName("URLs"));
192  filename_url = ((id (*) (id, SEL, int)) objc_msgSend)(url_array, sel_registerName("objectAtIndex:"), 0);
193  } else {
194  filename_url = ((id (*) (id, SEL)) objc_msgSend)(file_panel, sel_registerName("URL"));
195  }
196  const char* filename = (const char*)((id (*) (id, SEL)) objc_msgSend)(filename_url, sel_registerName("fileSystemRepresentation"));
197  strcpy(selected, filename);
198  store_dir_from_file_selection(default_dir, filename, dialog_mode);
199  return 0;
200  }
201  return 1;
202 }
203 
204 
205 static const struct xemugui_descriptor_st xemuosxgui_descriptor = {
206  .name = "macos",
207  .description = "MacOS native API Xemu UI implementation",
208  .init = xemuosxgui_init,
209  .shutdown = NULL,
210  .iteration = NULL,
211  .file_selector = xemuosxgui_file_selector,
212  .popup = xemuosxgui_popup,
213  .info = NULL
214 };
menu_st
Definition: emutools_gui.h:65
menu_st::user_data
const void * user_data
Definition: emutools_gui.h:69
menu_st::type
int type
Definition: emutools_gui.h:67
menu_st::handler
const xemugui_callback_t handler
Definition: emutools_gui.h:68
XEMUGUI_MENUID_CALLABLE
#define XEMUGUI_MENUID_CALLABLE
Definition: emutools_gui.h:30
id
int id
Definition: joystick.c:52
NSPoint
CGPoint NSPoint
Definition: gui_osx.c:24
xemugui_callback_t
void(* xemugui_callback_t)(const struct menu_st *desc, int *query)
Definition: emutools_gui.h:63
xemugui_descriptor_st::name
const char * name
Definition: emutools_gui.c:45
XEMUGUI_MENUFLAG_QUERYBACK
#define XEMUGUI_MENUFLAG_QUERYBACK
Definition: emutools_gui.h:40
XEMUGUI_FSEL_SAVE
#define XEMUGUI_FSEL_SAVE
Definition: emutools_gui.h:27
DEBUGPRINT
#define DEBUGPRINT(...)
Definition: emutools_basicdefs.h:171
NL
#define NL
Definition: fat32.c:37
XEMUGUI_FSEL_OPEN
#define XEMUGUI_FSEL_OPEN
Definition: emutools_gui.h:26
xemugui_descriptor_st
Definition: emutools_gui.c:44
menu_st::name
const char * name
Definition: emutools_gui.h:66
XEMUGUI_MENUID_SUBMENU
#define XEMUGUI_MENUID_SUBMENU
Definition: emutools_gui.h:31
XEMUGUI_MENUFLAG_HIDDEN
#define XEMUGUI_MENUFLAG_HIDDEN
Definition: emutools_gui.h:42
name
const char * name
Definition: joystick.c:46
XEMUGUI_MENUFLAG_CHECKED
#define XEMUGUI_MENUFLAG_CHECKED
Definition: emutools_gui.h:39
FATAL
#define FATAL(...)
Definition: xep128.h:117
XEMUGUI_MAINMENU_NAME
#define XEMUGUI_MAINMENU_NAME
Definition: emutools_gui.h:52
DEBUGGUI
#define DEBUGGUI
Definition: emutools_gui.h:22
XEMUGUI_MENUFLAG_SEPARATOR
#define XEMUGUI_MENUFLAG_SEPARATOR
Definition: emutools_gui.h:38