/* * Mr. 4th Dimention - Allen Webster * * 28.06.2017 * * Mac Objective C layer for 4coder * */ // TOP #define IS_OBJC_LAYER #include "4coder_base_types.h" #include "4coder_API/4coder_version.h" #include "4ed_cursor_codes.h" #include "4ed_linked_node_macros.h" #undef global #undef external #define external #include "osx_objective_c_to_cpp_links.h" #include <CoreServices/CoreServices.h> #import <Cocoa/Cocoa.h> #import <CoreVideo/CVDisplayLink.h> #import <IOKit/hid/IOHIDLib.h> #import <OpenGL/OpenGL.h> #import <OpenGL/gl.h> #include <sys/types.h> #include <sys/event.h> #include <sys/time.h> #include <time.h> #include <string.h> void osx_post_to_clipboard(char *str){ NSPasteboard *board = [NSPasteboard generalPasteboard]; NSString *utf8_type = @"public.utf8-plain-text"; NSArray<NSString*> *typesArray = [NSArray arrayWithObjects: utf8_type, nil]; [board declareTypes:typesArray owner:nil]; NSString *paste_string = [NSString stringWithUTF8String:str]; [board setString:paste_string forType:utf8_type]; osx_objc.just_posted_to_clipboard = true; } void osx_error_dialogue(char *str){ NSAlert *alert = [[NSAlert alloc] init]; [alert addButtonWithTitle:@"OK"]; NSString *text = [NSString stringWithUTF8String:str]; [alert setMessageText:text]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; } // // Entry point, OpenGL window setup. // @interface AppDelegate : NSObject <NSApplicationDelegate> @end @interface My4coderView : NSOpenGLView{ @public //CVDisplayLinkRef displayLink; } - (void)requestDisplay; - (void)checkClipboard; - (CVReturn)getFrame; - (void)drawRect:(NSRect)bounds; @end #define DISPLINK_SIG(n) CVReturn n(CVDisplayLinkRef link, const CVTimeStamp *now, const CVTimeStamp *output, CVOptionFlags flags_in, CVOptionFlags *flags_out, void *context) static DISPLINK_SIG(osx_display_link); static OSX_Keyboard_Modifiers osx_mods_nsevent_to_struct(NSEventModifierFlags flags){ OSX_Keyboard_Modifiers mods = {}; mods.shift = ((flags & NSEventModifierFlagShift) != 0); mods.command = ((flags & NSEventModifierFlagCommand) != 0); mods.control = ((flags & NSEventModifierFlagControl) != 0); mods.option = ((flags & NSEventModifierFlagOption) != 0); mods.caps = ((flags & NSEventModifierFlagCapsLock) != 0); return(mods); } @implementation My4coderView - (void)keyDown:(NSEvent *)event{ NSString *real = [event charactersIgnoringModifiers]; NSString *with_mods = [event characters]; b32 is_dead_key = false; if (real && !with_mods){ is_dead_key = true; } OSX_Keyboard_Modifiers mods = osx_get_modifiers(); // TODO(allen): Not ideal solution, look for realer text // input on Mac. This just makes sure we're getting good // results for unmodified keys when cmnd and ctrl aren't down. NSString *which = with_mods; if (mods.command || mods.control){ which = real; } u32 length = which.length; for (u32 i = 0; i < length; ++i){ unichar c = [which characterAtIndex:i]; osx_character_input(c, mods); } } - (void)mouseDown:(NSEvent*)event{ NSPoint m = [event locationInWindow]; osx_mouse(m.x, m.y, MouseType_Press); } - (void)mouseDragged:(NSEvent*)event{ NSPoint m = [event locationInWindow]; osx_mouse(m.x, m.y, MouseType_Move); } - (void)mouseMoved:(NSEvent*)event{ NSPoint m = [event locationInWindow]; osx_mouse(m.x, m.y, MouseType_Move); } - (void)mouseUp:(NSEvent*)event{ NSPoint m = [event locationInWindow]; osx_mouse(m.x, m.y, MouseType_Release); } - (void)scrollWheel:(NSEvent*)event{ float dx = event.scrollingDeltaX; float dy = event.scrollingDeltaY; osx_mouse_wheel(dx, dy); } - (BOOL)windowShouldClose:(NSWindow*)sender{ osx_try_to_close(); return(NO); } - (void)requestDisplay{ CGRect cg_rect = CGRectMake(0, 0, osx_objc.width, osx_objc.height); NSRect rect = NSRectFromCGRect(cg_rect); [self setNeedsDisplayInRect:rect]; } static i32 did_update_for_clipboard = true; - (void)checkClipboard{ NSPasteboard *board = [NSPasteboard generalPasteboard]; if (board.changeCount != osx_objc.prev_clipboard_change_count && did_update_for_clipboard){ [self requestDisplay]; did_update_for_clipboard = false; } } - (CVReturn)getFrame{ did_update_for_clipboard = true; @autoreleasepool { if (osx_objc.running){ osx_objc.has_clipboard_item = false; NSPasteboard *board = [NSPasteboard generalPasteboard]; if (board.changeCount != osx_objc.prev_clipboard_change_count){ if (!osx_objc.just_posted_to_clipboard){ NSString *utf8_type = @"public.utf8-plain-text"; NSArray *array = [NSArray arrayWithObjects: utf8_type, nil]; NSString *has_string = [board availableTypeFromArray:array]; if (has_string != nil){ NSData *data = [board dataForType: utf8_type]; if (data != nil){ u32 copy_length = data.length; if (copy_length > 0){ if (copy_length + 1 > osx_objc.clipboard_max){ osx_free(osx_objc.clipboard_data, osx_objc.clipboard_max); osx_objc.clipboard_max = l_round_up_u32(copy_length + 1, KB(4)); osx_objc.clipboard_data = osx_allocate(osx_objc.clipboard_max); } if (copy_length + 1 < osx_objc.clipboard_max){ osx_objc.clipboard_size = copy_length; [data getBytes: osx_objc.clipboard_data length: copy_length]; ((char*)osx_objc.clipboard_data)[copy_length] = 0; osx_objc.has_clipboard_item = true; } } } } } else{ osx_objc.just_posted_to_clipboard = false; } osx_objc.prev_clipboard_change_count = board.changeCount; } osx_step(); } } return kCVReturnSuccess; } - (void)reshape { [super reshape]; NSRect rect = [self bounds]; osx_resize(rect.size.width, rect.size.height); } - (void)init_gl { if (osx_objc.gl_is_initialized){ return; } [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; NSOpenGLPixelFormatAttribute attrs[] = { NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, NSOpenGLPFAColorSize, 24, NSOpenGLPFAAlphaSize, 8, NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, 0 }; NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; if(format == nil){ fprintf(stderr, "Error creating OpenGLPixelFormat\n"); exit(1); } NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:nil]; [self setPixelFormat:format]; [self setOpenGLContext:context]; [context makeCurrentContext]; osx_objc.gl_is_initialized = true; } - (id)init { self = [super init]; if(self == nil) { return nil; } [self init_gl]; return self; } - (void)drawRect: (NSRect) bounds{ [self getFrame]; } - (void)awakeFromNib { [self init_gl]; } - (void)prepareOpenGL { [super prepareOpenGL]; [[self openGLContext] makeCurrentContext]; GLint swapInt = 1; [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; } - (void)dealloc { [super dealloc]; } - (BOOL)acceptsFirstResponder { return YES; } - (BOOL)becomeFirstResponder { return YES; } - (BOOL)resignFirstResponder { return YES; } @end @implementation AppDelegate - (void)applicationDidFinishLaunching:(id)sender { } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender { return YES; } - (void)applicationWillTerminate:(NSApplication*)sender { } @end ////////////////// typedef struct File_Change_Node File_Change_Node; struct File_Change_Node{ File_Change_Node *next; char *name; i32 len; }; typedef struct{ File_Change_Node *first; File_Change_Node *last; volatile i64 lock; } File_Change_Queue; static File_Change_Queue file_queue = {}; File_Change_Node* file_change_node(char *name){ i32 len = strlen(name); void *block = malloc(len + 1 + sizeof(File_Change_Node)); File_Change_Node *node = (File_Change_Node*)block; memset(node, 0, sizeof(*node)); node->name = (char*)(node + 1); node->len = len; memcpy(node->name, name, len + 1); return(node); } void file_change_node_free(File_Change_Node *node){ free(node); } #define file_queue_lock() for(;;){i64 v=__sync_val_compare_and_swap(&file_queue.lock,0,1);if(v==0){break;}} #define file_queue_unlock() __sync_lock_test_and_set(&file_queue.lock, 0) void file_watch_callback(ConstFSEventStreamRef stream, void *callbackInfo, size_t numEvents, void *evPaths, const FSEventStreamEventFlags *evFlags, const FSEventStreamEventId *evIds){ char **paths = (char**)evPaths; for (int i = 0; i < numEvents; ++i){ File_Change_Node *node = file_change_node(paths[i]); file_queue_lock(); sll_push(file_queue.first, file_queue.last, node); file_queue_unlock(); } } ////////////////// typedef struct{ FSEventStreamRef stream; } File_Watching_Handle; File_Watching_Handle schedule_file_watching(char *f){ File_Watching_Handle handle = {}; CFStringRef arg = CFStringCreateWithCString(0, f, kCFStringEncodingUTF8); CFArrayRef paths = CFArrayCreate(0, (const void**)&arg, 1, 0); void *callbackInfo = 0; CFAbsoluteTime latency = 2.0; handle.stream = FSEventStreamCreate(0, &file_watch_callback, 0, paths, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagFileEvents); FSEventStreamScheduleWithRunLoop(handle.stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode); if (!FSEventStreamStart(handle.stream)){ fprintf(stdout, "BAD SCHED: %s\n", f); } return(handle); } void unschedule_file_watching(File_Watching_Handle handle){ FSEventStreamStop(handle.stream); FSEventStreamInvalidate(handle.stream); FSEventStreamRelease(handle.stream); } typedef struct File_Table_Entry{ u64 hash; void *name; i32 counter; File_Watching_Handle handle; } File_Table_Entry; typedef struct File_Change_Table{ File_Table_Entry *table; i32 count; i32 size; } File_Change_Table; static File_Change_Table file_change_table = {}; void* osx_file_name_prefixed_length(char *name){ i32 len = 0; for (; name[len] != 0; ++len); char *name_stored = (char*)malloc(4 + l_round_up_u32(len + 1, 4)); *(i32*)name_stored = len; memcpy(name_stored + 4, name, len); name_stored[4 + len] = 0; return(name_stored); } b32 osx_name_prefixed_match(void *a, void *b){ b32 result = false; i32 *len_a = (i32*)a; i32 *len_b = (i32*)b; if (*len_a == *len_b){ char *str_a = (char*)(len_a + 1); char *str_b = (char*)(len_b + 1); if (strncmp(str_a, str_b, *len_a) == 0){ result = true; } } return(result); } File_Table_Entry* osx_file_listener_lookup_and_return_pointer(u64 hash, void *name){ File_Table_Entry *result = 0; i32 index = (i32)(hash % file_change_table.size); i32 first_index = index; for (;;){ File_Table_Entry *entry = &file_change_table.table[index]; if (entry->hash == hash){ if (osx_name_prefixed_match(name, entry->name)){ result = entry; break; } } if (entry->name == 0){ break; } index = (index + 1)%file_change_table.size; if (index == first_index){ break; } } return(result); } void osx_file_listener_table_entry_erase(File_Table_Entry *entry){ free(entry->name); unschedule_file_watching(entry->handle); memset(entry, 0, sizeof(*entry)); entry->name = (void*)1; } b32 osx_file_listener_lookup_and_decrement(u64 hash, void *name){ b32 found = false; File_Table_Entry *entry = osx_file_listener_lookup_and_return_pointer(hash, name); if (entry != 0){ found = true; --entry->counter; if (entry->counter <= 0){ osx_file_listener_table_entry_erase(entry); } } return(found); } b32 osx_file_listener_hash(u64 hash, void *name, i32 counter, File_Watching_Handle **handle_address_out){ b32 result = 0; if (file_change_table.count*6 < file_change_table.size*5){ i32 index = (i32)(hash % file_change_table.size); i32 first_index = index; for (;;){ File_Table_Entry *entry = &file_change_table.table[index]; if (entry->name == 0 || entry->name == (void*)1){ entry->hash = hash; entry->name = name; entry->counter = counter; *handle_address_out = &entry->handle; result = true; ++file_change_table.count; break; } index = (index + 1)%file_change_table.size; if (index == first_index){ break; } } if (!result){ //LOG("file change listener table error: could not find a free slot in the table\n"); } } return(result); } void osx_file_listener_grow_table(i32 size){ if (file_change_table.size < size){ File_Table_Entry *old_table = file_change_table.table; i32 old_size = file_change_table.size; file_change_table.table = (File_Table_Entry*)osx_allocate(size*sizeof(File_Table_Entry)); memset(file_change_table.table, 0, size*sizeof(File_Table_Entry)); file_change_table.size = size; for (i32 i = 0; i < old_size; ++i){ void *name = file_change_table.table[i].name; if (name != 0 && name != (void*)1){ File_Table_Entry *e = &file_change_table.table[i]; File_Watching_Handle *handle_address = 0; osx_file_listener_hash(e->hash, e->name, e->counter, &handle_address); *handle_address = e->handle; } } if (old_table != 0){ osx_free(old_table, old_size*sizeof(File_Table_Entry)); } } } void osx_file_listener_double_table(){ osx_file_listener_grow_table(file_change_table.size*2); } b32 osx_file_listener_insert_or_increment_always(u64 hash, void *name, File_Watching_Handle **handle_address_out){ b32 was_already_in_table = false; File_Table_Entry *entry = osx_file_listener_lookup_and_return_pointer(hash, name); if (entry != 0){ ++entry->counter; was_already_in_table = true; } else{ b32 result = osx_file_listener_hash(hash, name, 1, handle_address_out); if (!result){ osx_file_listener_double_table(); osx_file_listener_hash(hash, name, 1, handle_address_out); } } return(was_already_in_table); } u64 osx_get_file_hash(void *name){ u32 count = *(u32*)(name); char *str = (char*)name + 4; u64 hash = 0; u64 state = count; u64 inc = 1 + 2*count; for (u32 i = 0; i <= count; ++i){ u64 old_state = state; state = state*6364136223846783005ULL + inc; u32 xorshifted = ((old_state >> 18u) ^ old_state) >> 27u; u32 rot = old_state >> 59u; hash = (hash << 3) + (hash & 1) + ((xorshifted >> rot) | (xorshifted << ((-rot) & 31))); if (i < count){ inc = 1 + 2*(((inc - 1) << 7) | (u8)str[i]); } } return(hash); } void osx_file_listener_init(void){ osx_file_listener_grow_table(4096); } void osx_add_file_listener(char *dir_name){ // TODO(allen): Decide what to do about these darn string mallocs. void *name_stored = osx_file_name_prefixed_length(dir_name); File_Watching_Handle *handle_address = 0; b32 was_already_in_table = osx_file_listener_insert_or_increment_always(osx_get_file_hash(name_stored), name_stored, &handle_address); if (was_already_in_table){ free(name_stored); } else{ *handle_address = schedule_file_watching(dir_name); } } void osx_remove_file_listener(char *dir_name){ void *name_stored = osx_file_name_prefixed_length(dir_name); osx_file_listener_lookup_and_decrement(osx_get_file_hash(name_stored), name_stored); free(name_stored); } i32 osx_get_file_change_event(char *buffer, i32 max, i32 *size){ file_queue_lock(); File_Change_Node *node = file_queue.first; sll_pop(file_queue.first, file_queue.last); file_queue_unlock(); i32 result = 0; if (node != 0){ if (node->len < max){ result = 1; memcpy(buffer, node->name, node->len); *size = node->len; } else{ result = -1; // TODO(allen): Somehow save the node? } file_change_node_free(node); } return(result); } void osx_show_cursor(i32 show, i32 cursor_type){ local_persist b32 cursor_is_shown = 1; if (show == 1){ if (!cursor_is_shown){ [NSCursor unhide]; cursor_is_shown = true; } } else if (show == -1){ if (cursor_is_shown){ [NSCursor hide]; cursor_is_shown = false; } } if (cursor_type > 0){ switch (cursor_type){ case APP_MOUSE_CURSOR_ARROW: { [[NSCursor arrowCursor] set]; }break; case APP_MOUSE_CURSOR_IBEAM: { [[NSCursor IBeamCursor] set]; }break; case APP_MOUSE_CURSOR_LEFTRIGHT: { [[NSCursor resizeLeftRightCursor] set]; }break; case APP_MOUSE_CURSOR_UPDOWN: { [[NSCursor resizeUpDownCursor] set]; }break; } } } My4coderView* view = 0; NSWindow* window = 0; void osx_begin_render(){ CGLLockContext([[view openGLContext] CGLContextObj]); [[view openGLContext] makeCurrentContext]; } void osx_end_render(){ [[view openGLContext] flushBuffer]; CGLUnlockContext([[view openGLContext] CGLContextObj]); } void osx_schedule_step(void){ [NSTimer scheduledTimerWithTimeInterval: 0.0 target: view selector: @selector(requestDisplay) userInfo: nil repeats:NO]; } void osx_toggle_fullscreen(void){ [window toggleFullScreen:nil]; } b32 osx_is_fullscreen(void){ b32 result = (([window styleMask] & NSFullScreenWindowMask) != 0); return(result); } void osx_close_app(void){ [NSApp terminate: nil]; } f32 osx_timer_seconds(void){ f32 result = CACurrentMediaTime(); return(result); } NSFontManager *font_manager = 0; NSString *get_font_path(NSFont *font){ CFStringRef name = (CFStringRef)[font fontName]; CGFloat size = [font pointSize]; CTFontDescriptorRef ref = CTFontDescriptorCreateWithNameAndSize(name, size); CFURLRef url = CTFontDescriptorCopyAttribute(ref, kCTFontURLAttribute); NSString *path = [(NSURL *)CFBridgingRelease(url) path]; return(path); } OSX_Font_Match osx_get_font_match(char *name, i32 pt_size, b32 italic, b32 bold){ if (font_manager == 0){ font_manager = [NSFontManager sharedFontManager]; } NSString *name_string = [NSString stringWithUTF8String:name]; NSFontTraitMask trait_mask = 0; if (italic){ trait_mask = (trait_mask | NSItalicFontMask); } NSInteger weight = 5; if (bold){ weight = 9; } b32 used_base_file = false; NSFont *font = [font_manager fontWithFamily: name_string traits: trait_mask weight: weight size:(float)pt_size]; if (font == nil){ font = [font_manager fontWithFamily: name_string traits: 0 weight: 5 size:(float)pt_size]; used_base_file = true; } OSX_Font_Match match = {}; if (font != nil){ NSString *path = get_font_path(font); char *path_c = 0; if (path != nil){ path_c = (char*)[path UTF8String]; } if (path_c != 0){ match.path = path_c; match.used_base_file = used_base_file; } } return(match); } OSX_Loadable_Fonts osx_list_loadable_fonts(void){ if (font_manager == 0){ font_manager = [NSFontManager sharedFontManager]; } NSArray<NSString*> *fonts = [font_manager availableFontFamilies]; OSX_Loadable_Fonts result = {}; NSUInteger count_u = [fonts count]; int count = (int)count_u; result.count = count; size_t memsize = count*2*sizeof(char*); void *mem = malloc(memsize); result.names = (char**)mem; result.paths = result.names + count; for (int i = 0; i < count; ++i){ NSString *font_n = fonts[i]; char *font_n_c = (char*)[font_n UTF8String]; NSFont *font = [font_manager fontWithFamily:font_n traits:NSUnboldFontMask|NSUnitalicFontMask weight:5 size:12]; NSString *path = get_font_path(font); char *path_c = 0; if (path != nil){ path_c = (char*)[path UTF8String]; } result.names[i] = font_n_c; result.paths[i] = path_c; } return(result); } void osx_change_title(char *str_c){ NSString *str = [NSString stringWithUTF8String:str_c]; [window setTitle:str]; } OSX_Keyboard_Modifiers osx_get_modifiers(void){ return(osx_mods_nsevent_to_struct([NSEvent modifierFlags])); } int main(int argc, char **argv){ memset(&osx_objc, 0, sizeof(osx_objc)); u32 clipboard_size = KB(16); osx_objc.clipboard_data = osx_allocate(clipboard_size); osx_objc.clipboard_max = clipboard_size; osx_objc.argc = argc; osx_objc.argv = argv; osx_file_listener_init(); @autoreleasepool{ NSApplication *app = [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [app setDelegate:[[AppDelegate alloc] init]]; NSRect screenRect = [[NSScreen mainScreen] frame]; float w = 800.f; float h = 600.f; NSRect frame = NSMakeRect((screenRect.size.width - w) * 0.5, (screenRect.size.height - h) * 0.5, w, h); u32 flags = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask; window = [[NSWindow alloc] initWithContentRect:frame styleMask:flags backing:NSBackingStoreBuffered defer:NO]; [window setAcceptsMouseMovedEvents:YES]; view = [[My4coderView alloc] init]; [view setFrame:[[window contentView] bounds]]; [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [[window contentView] addSubview:view]; [window setMinSize:NSMakeSize(100, 100)]; [window setTitle:@WINDOW_NAME]; [window makeKeyAndOrderFront:nil]; [NSTimer scheduledTimerWithTimeInterval: 0.5 target: view selector: @selector(checkClipboard) userInfo: nil repeats:YES]; osx_init(); osx_objc.running = true; [NSApp run]; } return(0); } // BOTTOM