From 2b0596c7f91a395ab612d821b97199b0d6bfaffa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 22 Aug 2017 20:59:56 +0530 Subject: [PATCH] Add a basic global menubar on macOS Fixes #111 --- kitty/cocoa_window.m | 113 +++++++++++++++++++++++++++++++++++++++++++ kitty/glfw.h | 6 ++- kitty/main.py | 3 +- 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index 362790e0c..7b0ae897d 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -10,11 +10,123 @@ #include #include +// Needed for _NSGetProgname +#include #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200) #define NSWindowStyleMaskResizable NSResizableWindowMask #endif +@interface MenuDispatcher : NSObject +@end + +@implementation MenuDispatcher +@end + +static NSObject* global_menu = NULL; + +static NSString* +find_app_name(void) { + size_t i; + NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; + + // Keys to search for as potential application names + NSString* name_keys[] = + { + @"CFBundleDisplayName", + @"CFBundleName", + @"CFBundleExecutable", + }; + + for (i = 0; i < sizeof(name_keys) / sizeof(name_keys[0]); i++) + { + id name = [infoDictionary objectForKey:name_keys[i]]; + if (name && + [name isKindOfClass:[NSString class]] && + ![name isEqualToString:@""]) + { + return name; + } + } + + char** progname = _NSGetProgname(); + if (progname && *progname) + return [NSString stringWithUTF8String:*progname]; + + // Really shouldn't get here + return @"kitty"; +} + + +PyObject* +cocoa_create_global_menu(PyObject UNUSED *_self) { + if (global_menu != NULL) { Py_RETURN_NONE; } + NSString* app_name = find_app_name(); + global_menu = [[MenuDispatcher alloc] init]; + NSMenu* bar = [[NSMenu alloc] init]; + [NSApp setMainMenu:bar]; + + NSMenuItem* appMenuItem = + [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; + NSMenu* appMenu = [[NSMenu alloc] init]; + [appMenuItem setSubmenu:appMenu]; + + [appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", app_name] + action:@selector(orderFrontStandardAboutPanel:) + keyEquivalent:@""]; + [appMenu addItem:[NSMenuItem separatorItem]]; + [appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name] + action:@selector(hide:) + keyEquivalent:@"h"]; + [[appMenu addItemWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"] + setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask]; + [appMenu addItemWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + [appMenu addItem:[NSMenuItem separatorItem]]; + NSMenu* servicesMenu = [[NSMenu alloc] init]; + [NSApp setServicesMenu:servicesMenu]; + [[appMenu addItemWithTitle:@"Services" + action:NULL + keyEquivalent:@""] setSubmenu:servicesMenu]; + [servicesMenu release]; + [appMenu addItem:[NSMenuItem separatorItem]]; + + [appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name] + action:@selector(terminate:) + keyEquivalent:@"q"]; + + NSMenuItem* windowMenuItem = + [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; + NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + [NSApp setWindowsMenu:windowMenu]; + [windowMenuItem setSubmenu:windowMenu]; + + [windowMenu addItemWithTitle:@"Minimize" + action:@selector(performMiniaturize:) + keyEquivalent:@"m"]; + [windowMenu addItemWithTitle:@"Zoom" + action:@selector(performZoom:) + keyEquivalent:@""]; + [windowMenu addItem:[NSMenuItem separatorItem]]; + [windowMenu addItemWithTitle:@"Bring All to Front" + action:@selector(arrangeInFront:) + keyEquivalent:@""]; + + [windowMenu addItem:[NSMenuItem separatorItem]]; + [[windowMenu addItemWithTitle:@"Enter Full Screen" + action:@selector(toggleFullScreen:) + keyEquivalent:@"f"] + setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask]; + + + [bar release]; + Py_RETURN_NONE; +} + + PyObject* cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id) { NSWindow *window = (NSWindow*)PyLong_AsVoidPtr(window_id); @@ -28,6 +140,7 @@ Py_RETURN_NONE; } + PyObject* cocoa_get_lang(PyObject UNUSED *self) { NSString* locale = nil; diff --git a/kitty/glfw.h b/kitty/glfw.h index bfda0ba8f..39b98093a 100644 --- a/kitty/glfw.h +++ b/kitty/glfw.h @@ -22,11 +22,14 @@ PyObject* glfw_init_hint_string(PyObject UNUSED *self, PyObject *args); #ifdef __APPLE__ PyObject* cocoa_get_lang(PyObject UNUSED *self); PyObject* cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id); +PyObject* cocoa_create_global_menu(PyObject UNUSED *self); #define COCOA_GET_LANG {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, #define COCOA_MWR {"cocoa_make_window_resizable", (PyCFunction)cocoa_make_window_resizable, METH_O, ""}, +#define COCOA_CGM {"cocoa_create_global_menu", (PyCFunction)cocoa_create_global_menu, METH_NOARGS, ""}, #else #define COCOA_GET_LANG #define COCOA_MWR +#define COCOA_CGM #endif #define GLFW_FUNC_WRAPPERS \ @@ -41,4 +44,5 @@ PyObject* cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, \ {"glfw_init_hint_string", (PyCFunction)glfw_init_hint_string, METH_VARARGS, ""}, \ COCOA_GET_LANG \ - COCOA_MWR + COCOA_MWR \ + COCOA_CGM diff --git a/kitty/main.py b/kitty/main.py index c2df87ef5..5c710ae62 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -211,9 +211,10 @@ def run_app(opts, args): window.set_title(appname) window.make_context_current() if isosx: + from .fast_data_types import cocoa_make_window_resizable, cocoa_create_global_menu check_for_extensions() + cocoa_create_global_menu() if opts.macos_hide_titlebar: - from .fast_data_types import cocoa_make_window_resizable cocoa_make_window_resizable(window.cocoa_window_id()) else: with open(logo_data_file, 'rb') as f: