From 792b74503c5ec55769c3c15701c3d5b4d2a139bf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Oct 2023 09:42:06 +0530 Subject: [PATCH] Implement mouse shape support for macOS Code for loading hidden system cursors not available via NCursor comes from the SDL library, with thanks. --- gen/cursors.py | 11 ++++++ glfw/cocoa_window.m | 85 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/gen/cursors.py b/gen/cursors.py index 21753aeb0..5dba2e70a 100755 --- a/gen/cursors.py +++ b/gen/cursors.py @@ -58,6 +58,7 @@ def main(args: List[str]=sys.argv) -> None: glfw_xfont_map = [] kitty_to_enum_map = {} enum_to_glfw_map = {} + glfw_cocoa_map = {} for line in cursors.splitlines(): line = line.strip() if line: @@ -78,6 +79,15 @@ def main(args: List[str]=sys.argv) -> None: else: items = tuple('"' + x.replace('!', '') + '"' for x in xc) glfw_xfont_map.append(f'case {glfw_name}: return try_cursor_names(cursor, {len(items)}, {", ".join(items)});') + parts = cocoa.split(':', 1) + if len(parts) == 1: + if parts[0].startswith('_'): + glfw_cocoa_map[glfw_name] = f'U({glfw_name}, {parts[0]});' + else: + glfw_cocoa_map[glfw_name] = f'C({glfw_name}, {parts[0]});' + else: + glfw_cocoa_map[glfw_name] = f'S({glfw_name}, {parts[0]}, {parts[1]});' + glfw_enum.append('GLFW_INVALID_CURSOR') patch_file('glfw/glfw3.h', 'mouse cursor shapes', '\n'.join(f' {x},' for x in glfw_enum)) @@ -94,6 +104,7 @@ def main(args: List[str]=sys.argv) -> None: f' case {k}: set_glfw_mouse_cursor(w, {v}); break;' for k, v in enum_to_glfw_map.items())) patch_file('kitty/glfw.c', 'name to glfw', '\n'.join( f' if (strcmp(name, "{k}") == 0) return {enum_to_glfw_map[v]};' for k, v in kitty_to_enum_map.items())) + patch_file('glfw/cocoa_window.m', 'glfw to cocoa', '\n'.join(f' {x}' for x in glfw_cocoa_map.values())) subprocess.check_call(['glfw/glfw.py']) diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index cfc3377c6..a4d542346 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -2505,27 +2505,86 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor, return true; } -int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) -{ +static NSCursor* +load_hidden_system_cursor(NSString *name, SEL fallback) { + // this implementation comes from SDL_cocoamouse.m + NSString *cursorPath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:name]; + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]]; + /* we can't do animation atm. :/ */ + const int frames = (int)[[info valueForKey:@"frames"] integerValue]; + NSCursor *cursor; + NSImage *image = [[NSImage alloc] initWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]]; + if ((image == nil) || (image.isValid == NO)) { + return [NSCursor performSelector:fallback]; + } + + if (frames > 1) { +#ifdef MAC_OS_VERSION_12_0 /* same value as deprecated symbol. */ + const NSCompositingOperation operation = NSCompositingOperationCopy; +#else + const NSCompositingOperation operation = NSCompositeCopy; +#endif + const NSSize cropped_size = NSMakeSize(image.size.width, (int)(image.size.height / frames)); + NSImage *cropped = [[NSImage alloc] initWithSize:cropped_size]; + if (cropped == nil) { + return [NSCursor performSelector:fallback]; + } + + [cropped lockFocus]; + { + const NSRect cropped_rect = NSMakeRect(0, 0, cropped_size.width, cropped_size.height); + [image drawInRect:cropped_rect fromRect:cropped_rect operation:operation fraction:1]; + } + [cropped unlockFocus]; + image = cropped; + } + + cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue], [[info valueForKey:@"hoty"] doubleValue])]; + return cursor; +} + +int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) { #define C(name, val) case name: cursor->ns.object = [NSCursor val]; break; -#define U(name, val) case name: cursor->ns.object = [[NSCursor class] performSelector:@selector(val)]; break; +#define U(name, val) case name: cursor->ns.object = [NSCursor performSelector:@selector(val)]; break; +#define S(name, val, fallback) case name: cursor->ns.object = load_hidden_system_cursor(@#val, @selector(val)); break; switch(shape) { - C(GLFW_ARROW_CURSOR, arrowCursor); - C(GLFW_IBEAM_CURSOR, IBeamCursor); + /* start glfw to cocoa (auto generated by gen-key-constants.py do not edit) */ + C(GLFW_DEFAULT_CURSOR, arrowCursor); + C(GLFW_TEXT_CURSOR, IBeamCursor); + C(GLFW_POINTER_CURSOR, pointingHandCursor); + S(GLFW_HELP_CURSOR, help, arrowCursor); + S(GLFW_WAIT_CURSOR, busybutclickable, arrowCursor); + S(GLFW_PROGRESS_CURSOR, busybutclickable, arrowCursor); C(GLFW_CROSSHAIR_CURSOR, crosshairCursor); - C(GLFW_HAND_CURSOR, pointingHandCursor); - C(GLFW_HRESIZE_CURSOR, resizeLeftRightCursor); - C(GLFW_VRESIZE_CURSOR, resizeUpDownCursor); - U(GLFW_NW_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor); - U(GLFW_NE_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor); - U(GLFW_SW_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor); - U(GLFW_SE_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor); + C(GLFW_VERTICAL_TEXT_CURSOR, IBeamCursorForVerticalLayout); + S(GLFW_MOVE_CURSOR, move, openHandCursor); + C(GLFW_E_RESIZE_CURSOR, resizeRightCursor); + S(GLFW_NE_RESIZE_CURSOR, resizenortheast, _windowResizeNorthEastSouthWestCursor); + S(GLFW_NW_RESIZE_CURSOR, resizenorthwest, _windowResizeNorthWestSouthEastCursor); + C(GLFW_N_RESIZE_CURSOR, resizeUpCursor); + S(GLFW_SE_RESIZE_CURSOR, resizesoutheast, _windowResizeNorthWestSouthEastCursor); + S(GLFW_SW_RESIZE_CURSOR, resizesouthwest, _windowResizeNorthEastSouthWestCursor); + C(GLFW_S_RESIZE_CURSOR, resizeDownCursor); + C(GLFW_W_RESIZE_CURSOR, resizeLeftCursor); + C(GLFW_EW_RESIZE_CURSOR, resizeLeftRightCursor); + C(GLFW_NS_RESIZE_CURSOR, resizeUpDownCursor); + U(GLFW_NESW_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor); + U(GLFW_NWSE_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor); + S(GLFW_ZOOM_IN_CURSOR, zoomin, arrowCursor); + S(GLFW_ZOOM_OUT_CURSOR, zoomout, arrowCursor); + C(GLFW_ALIAS_CURSOR, dragLinkCursor); + C(GLFW_COPY_CURSOR, dragCopyCursor); + C(GLFW_NOT_ALLOWED_CURSOR, operationNotAllowedCursor); + C(GLFW_NO_DROP_CURSOR, operationNotAllowedCursor); + C(GLFW_GRAB_CURSOR, openHandCursor); + C(GLFW_GRABBING_CURSOR, closedHandCursor); +/* end glfw to cocoa */ case GLFW_INVALID_CURSOR: return false; } #undef C #undef U - +#undef S if (!cursor->ns.object) { _glfwInputError(GLFW_PLATFORM_ERROR,