1// Copyright (c) 2015 Sergio Gonzalez. All rights reserved. 2// License: https://github.com/serge-rgb/milton#license 3 4#include <mach-o/dyld.h> 5 6#define Rect MacRect 7// #define ALIGN MacALIGN 8#include <Cocoa/Cocoa.h> 9#undef Rect 10#undef ALIGN 11 12#include <errno.h> 13#include <limits.h> 14#include <string.h> 15#include <time.h> 16#include <sys/stat.h> 17#include <sys/time.h> 18#include "platform.h" 19#include "memory.h" 20 21#include <dlfcn.h> 22 23 24#define MAX_PATH PATH_MAX 25 26 27#include <AppKit/AppKit.h> 28 29void 30platform_init(PlatformState* platform, SDL_SysWMinfo* sysinfo) 31{ 32 33} 34 35void 36platform_deinit(PlatformState* platform) 37{ 38 39} 40 41void 42platform_event_tick() 43{ 44} 45 46void 47platform_setup_cursor(Arena* arena, PlatformState* platform) 48{ 49 50} 51 52void 53platform_cursor_set_position(PlatformState* platform, v2i pos) 54{ 55 // TODO: Implement 56} 57 58v2i 59platform_cursor_get_position(PlatformState* platform) 60{ 61 // TODO: Implement 62 return v2i{}; 63} 64 65 66void* 67platform_get_gl_proc(char* name) 68{ 69 static void* image = NULL; 70 71 if (NULL == image) { 72 image = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_LAZY); 73 } 74 return (image ? dlsym(image, (const char *)name) : NULL); 75 76} 77 78char* 79mac_panel(NSSavePanel *panel, FileKind kind) 80{ 81 switch (kind) { 82 case FileKind_IMAGE: 83 panel.allowedFileTypes = @[(NSString*)kUTTypeJPEG, (NSString*)kUTTypePNG]; 84 break; 85 case FileKind_MILTON_CANVAS: 86 panel.allowedFileTypes = @[@"mlt"]; 87 break; 88 default: 89 break; 90 } 91 if ([panel runModal] != NSModalResponseOK) { 92 return nullptr; 93 } 94 return strdup(panel.URL.path.fileSystemRepresentation); 95} 96 97NSAlert* 98mac_alert(const char* info, const char* title) 99{ 100 NSAlert *alert = [[NSAlert alloc] init]; 101 alert.messageText = [NSString stringWithUTF8String:title ? title : ""]; 102 alert.informativeText = [NSString stringWithUTF8String:info ? info : ""]; 103 return alert; 104} 105 106void 107platform_dialog_mac(char* info, char* title) 108{ 109 @autoreleasepool { 110 [mac_alert(info, title) runModal]; 111 } 112} 113 114int32_t 115platform_dialog_yesno_mac(char* info, char* title) 116{ 117 @autoreleasepool { 118 NSAlert *alert = mac_alert(info, title); 119 [alert addButtonWithTitle:NSLocalizedString(@"Yes", nil)]; 120 [alert addButtonWithTitle:NSLocalizedString(@"No", nil)]; 121 return [alert runModal] == NSAlertFirstButtonReturn; 122 } 123} 124 125YesNoCancelAnswer 126platform_dialog_yesnocancel(char* info, char* title) 127{ 128 @autoreleasepool { 129 NSAlert *alert = mac_alert(info, title); 130 [alert addButtonWithTitle:NSLocalizedString(@"Yes", nil)]; 131 [alert addButtonWithTitle:NSLocalizedString(@"No", nil)]; 132 [alert addButtonWithTitle:NSLocalizedString(@"Cancel", nil)]; 133 NSModalResponse result = [alert runModal]; 134 if (result == NSAlertFirstButtonReturn) { 135 return YesNoCancelAnswer::YES_; 136 } 137 else if (result == NSAlertSecondButtonReturn) { 138 return YesNoCancelAnswer::NO_; 139 } 140 return YesNoCancelAnswer::CANCEL_; 141 } 142} 143 144char* 145platform_open_dialog_mac(FileKind kind) 146{ 147 @autoreleasepool { 148 return mac_panel([NSOpenPanel openPanel], kind); 149 } 150} 151 152char* 153platform_save_dialog_mac(FileKind kind) 154{ 155 @autoreleasepool { 156 return mac_panel([NSSavePanel savePanel], kind); 157 } 158} 159 160EasyTabResult 161platform_handle_sysevent(PlatformState* platform, SDL_SysWMEvent* sysevent) 162{ 163 mlt_assert(sysevent->msg->subsystem == SDL_SYSWM_COCOA); 164 return EASYTAB_EVENT_NOT_HANDLED; 165} 166 167void 168platform_open_link_mac(char* link) 169{ 170 @autoreleasepool { 171 NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:link]]; 172 [[NSWorkspace sharedWorkspace] openURL:url]; 173 } 174} 175 176NSWindow* 177macos_get_window(PlatformState* ps) 178{ 179 NSWindow* result = NULL; 180 SDL_Window* window = ps->window; 181 if (window) { 182 SDL_SysWMinfo info = {}; 183 if (SDL_GetWindowWMInfo(window, &info)) { 184 if (info.subsystem == SDL_SYSWM_COCOA) { 185 NSWindow* nsw = info.info.cocoa.window; 186 result = nsw; 187 } 188 } 189 } 190 return result; 191 192} 193 194void 195platform_point_to_pixel(PlatformState* ps, v2l* inout) 196{ 197 NSRect rect; 198 rect.origin = {}; 199 rect.size.width = inout->w; 200 rect.size.height = inout->h; 201 auto* window = macos_get_window(ps); 202 NSRect backing = [window convertRectToBacking:rect]; 203 204 inout->x = backing.size.width; 205 inout->y = backing.size.height; 206} 207 208void 209platform_point_to_pixel_i(PlatformState* ps, v2i* inout) 210{ 211 v2l long_inout = { inout->x, inout->y }; 212 platform_point_to_pixel(ps, &long_inout); 213 *inout = (v2i){(int)long_inout.x, (int)long_inout.y}; 214} 215 216void 217platform_pixel_to_point(PlatformState* ps, v2l* inout) 218{ 219 NSRect rect; 220 rect.origin = {}; 221 rect.size.width = inout->w; 222 rect.size.height = inout->h; 223 auto* window = macos_get_window(ps); 224 NSRect pointrect = [window convertRectFromBacking:rect]; 225 226 inout->x = pointrect.size.width; 227 inout->y = pointrect.size.height; 228 229} 230 231// IMPLEMENT ==== 232float 233perf_count_to_sec(u64 counter) 234{ 235 // Input as nanoseconds 236 return (float)counter * 1e-9; 237} 238u64 239perf_counter() 240{ 241 // clock_gettime() on macOS is only supported on macOS Sierra and later. 242 // For older macOS operating systems, mach_absolute_time() will be need to be used. 243 timespec tp; 244 int res = clock_gettime(CLOCK_REALTIME, &tp); 245 246 // TODO: Check errno and provide more informations 247 if ( res ) { 248 milton_log("Something went wrong with clock_gettime\n"); 249 } 250 251 return tp.tv_nsec; 252} 253 254b32 255platform_delete_file_at_config(PATH_CHAR* fname, int error_tolerance) 256{ 257 char fname_at_config[MAX_PATH]; 258 strncpy(fname_at_config, fname, MAX_PATH); 259 platform_fname_at_config(fname_at_config, MAX_PATH*sizeof(char)); 260 int res = remove(fname_at_config); 261 b32 result = true; 262 if (res != 0) 263 { 264 result = false; 265 // Delete failed for some reason. 266 if ((error_tolerance == DeleteErrorTolerance_OK_NOT_EXIST) && 267 (errno == EEXIST || errno == ENOENT)) 268 { 269 result = true; 270 } 271 } 272 273 return result; 274} 275 276void 277platform_dialog(char* info, char* title) 278{ 279 extern void platform_dialog_mac(char*, char*); 280 platform_dialog_mac(info, title); 281 return; 282} 283 284b32 285platform_dialog_yesno(char* info, char* title) 286{ 287 extern b32 platform_dialog_yesno_mac(char*, char*); 288 platform_dialog_yesno_mac(info, title); 289 return false; 290} 291 292void 293platform_fname_at_config(PATH_CHAR* fname, size_t len) 294{ 295 NSBundle* bundle = [NSBundle mainBundle]; 296 const char* respath = [[bundle resourcePath] UTF8String]; 297 298 char* string_copy = (char*)mlt_calloc(1, len, "Strings"); 299 if ( string_copy ) { 300 strncpy(string_copy, fname, len); 301 302 b32 respath_failed = false; 303 if (respath) { 304 // Create the Resources directory if it doesn't exist. 305 int mkerr = mkdir(respath, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); 306 if (mkerr == 0) { 307 milton_log("Created Resources path.\n"); 308 } 309 else if (mkerr == -1) { 310 int err = errno; 311 if (err != EEXIST) { 312 milton_log("mkdir failed with unexpected error %d\n", mkerr); 313 respath_failed = true; 314 } 315 } 316 } 317 if (!respath || respath_failed) { 318 char* home = getenv("HOME"); 319 snprintf(fname, len, "%s/.milton", home); 320 mkdir(fname, S_IRWXU); 321 snprintf(fname, len, "%s/%s", fname, string_copy); 322 } 323 else { 324 snprintf(fname, len, "%s/%s", respath, string_copy); 325 } 326 327 mlt_free(string_copy, "Strings"); 328 } 329} 330 331void 332platform_fname_at_exe(PATH_CHAR* fname, size_t len) 333{ 334 u32 bufsize = (u32)len; 335 char buffer[MAX_PATH] = {}; 336 strncpy(buffer, fname, MAX_PATH); 337 _NSGetExecutablePath(fname, &bufsize); 338 { // Remove the executable name 339 PATH_CHAR* last_slash = fname; 340 for(PATH_CHAR* iter = fname; 341 *iter != '\0'; 342 ++iter) 343 { 344 if (*iter == '/') 345 { 346 last_slash = iter; 347 } 348 } 349 *(last_slash+1) = '\0'; 350 } 351 strncat(fname, "/", len); 352 strncat(fname, buffer, len); 353 return; 354} 355 356FILE* 357platform_fopen(const PATH_CHAR* fname, const PATH_CHAR* mode) 358{ 359 FILE* fd = fopen(fname, mode); 360 return fd; 361} 362 363 364b32 365platform_move_file(PATH_CHAR* src, PATH_CHAR* dest) 366{ 367 int res = rename(src, dest); 368 369 return res == 0; 370} 371 372float 373platform_ui_scale(PlatformState* p) 374{ 375 int foo = 0; 376 377 int display_w = 0; 378 int framebuffer_w = 0; 379 380 SDL_GetWindowSize(p->window, &display_w, &foo); 381 SDL_GL_GetDrawableSize(p->window, &framebuffer_w, &foo); 382 383 float scale = display_w > 0 ? framebuffer_w / display_w : 1; 384 385 return scale; 386} 387 388PATH_CHAR* 389platform_open_dialog(FileKind kind) 390{ 391 extern PATH_CHAR* platform_open_dialog_mac(FileKind); 392 return platform_open_dialog_mac(kind); 393} 394void 395platform_open_link(char* link) 396{ 397 extern void platform_open_link_mac(char*); 398 platform_open_link_mac(link); 399} 400PATH_CHAR* 401platform_save_dialog(FileKind kind) 402{ 403 extern PATH_CHAR* platform_save_dialog_mac(FileKind); 404 return platform_save_dialog_mac(kind); 405} 406// ==== 407 408WallTime 409platform_get_walltime() 410{ 411 WallTime wt = {}; 412 struct timeval tv; 413 gettimeofday(&tv, NULL); 414 struct tm *time; 415 time = localtime(&tv.tv_sec); 416 wt.h = time->tm_hour; 417 wt.m = time->tm_min; 418 wt.s = time->tm_sec; 419 wt.ms = tv.tv_usec / 1000; 420 return wt; 421} 422