From 7807d9574b79c5f841f5e2edcea7752405866c21 Mon Sep 17 00:00:00 2001 From: r0x0r Date: Wed, 11 Dec 2024 00:02:04 +0200 Subject: [PATCH] [Cocoa] fix menu --- webview/platforms/cocoa.py | 173 +++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 82 deletions(-) diff --git a/webview/platforms/cocoa.py b/webview/platforms/cocoa.py index 89c2e1f2..689aca75 100644 --- a/webview/platforms/cocoa.py +++ b/webview/platforms/cocoa.py @@ -53,6 +53,7 @@ class BrowserView: instances = {} app = AppKit.NSApplication.sharedApplication() app.setActivationPolicy_(0) + app_menu_list = None cascade_loc = Foundation.NSMakePoint(100.0, 0.0) @@ -619,6 +620,7 @@ def first_show(self): self._clear_main_menu() self._add_app_menu() self._add_view_menu() + self._add_user_menu() BrowserView.app.activateIgnoringOtherApps_(Foundation.YES) AppHelper.installMachInterrupt() @@ -864,6 +866,92 @@ def create_dialog(*args): return self._file_name + + def _add_user_menu(self): + """ + Create a custom menu for the app menu (MacOS bar menu) + + Args: + app_menu_list ([webview.menu.Menu]) + """ + + if BrowserView.app_menu_list is None: + return + + # From https://github.com/r0x0r/pywebview/issues/500 + class InternalMenu: + def __init__(self, title, parent): + self.m = AppKit.NSMenu.alloc().init() + self.item = AppKit.NSMenuItem.alloc().init() + self.item.setSubmenu_(self.m) + if not isinstance(parent, self.__class__): + self.m.setTitle_(title) + parent.addItem_(self.item) + else: + self.item.setTitle_(title) + parent.m.addItem_(self.item) + + def action(self, title: str, action: Callable, command: str | None = None): + InternalAction(self, title, action, command) + return self + + def separator(self): + self.m.addItem_(AppKit.NSMenuItem.separatorItem()) + return self + + def sub_menu(self, title: str): + return self.__class__(title, parent=self) + + class InternalAction: + def __init__(self, parent: InternalMenu, title: str, action: callable, command=None): + self.action = action + s = selector(self._call_action, signature=b'v@:') + if command: + item = parent.m.addItemWithTitle_action_keyEquivalent_(title, s, command) + else: + item = AppKit.NSMenuItem.alloc().init() + item.setAction_(s) + item.setTitle_(title) + parent.m.addItem_(item) + item.setTarget_(self) + + def _call_action(self): + # Don't run action function on main thread + Thread(target=self.action).start() + + def create_submenu(title, line_items, supermenu): + m = InternalMenu(title, parent=supermenu) + for menu_line_item in line_items: + if isinstance(menu_line_item, MenuSeparator): + m = m.separator() + elif isinstance(menu_line_item, MenuAction): + m = m.action(menu_line_item.title, menu_line_item.function) + elif isinstance(menu_line_item, Menu): + create_submenu(menu_line_item.title, menu_line_item.items, m) + + os_bar_menu = BrowserView.app.mainMenu() + if os_bar_menu is None: + os_bar_menu = AppKit.NSMenu.alloc().init() + BrowserView.app.setMainMenu_(os_bar_menu) + + for app_menu in BrowserView.app_menu_list: + create_submenu(app_menu.title, app_menu.items, os_bar_menu) + + + def get_active_window(): + active_window = BrowserView.app.keyWindow() + if active_window is None: + return None + + active_window_number = active_window.windowNumber() + + for uid, browser_view_instance in BrowserView.instances.items(): + if browser_view_instance.window.windowNumber() == active_window_number: + return browser_view_instance.pywebview_window + + return None + + def _clear_main_menu(self): """ Remove all items from the main menu. @@ -1092,6 +1180,9 @@ def setup_app(): # MUST be called before create_window and set_app_menu pass +def set_app_menu(app_menu_list): + BrowserView.app_menu_list = app_menu_list + def create_window(window): def create(): @@ -1157,88 +1248,6 @@ def load_html(content, base_uri, uid): i.load_html(content, base_uri) -def set_app_menu(app_menu_list): - """ - Create a custom menu for the app menu (MacOS bar menu) - - Args: - app_menu_list ([webview.menu.Menu]) - """ - - # From https://github.com/r0x0r/pywebview/issues/500 - class InternalMenu: - def __init__(self, title, parent): - self.m = AppKit.NSMenu.alloc().init() - self.item = AppKit.NSMenuItem.alloc().init() - self.item.setSubmenu_(self.m) - if not isinstance(parent, self.__class__): - self.m.setTitle_(title) - parent.addItem_(self.item) - else: - self.item.setTitle_(title) - parent.m.addItem_(self.item) - - def action(self, title: str, action: Callable, command: str | None = None): - InternalAction(self, title, action, command) - return self - - def separator(self): - self.m.addItem_(AppKit.NSMenuItem.separatorItem()) - return self - - def sub_menu(self, title: str): - return self.__class__(title, parent=self) - - class InternalAction: - def __init__(self, parent: InternalMenu, title: str, action: callable, command=None): - self.action = action - s = selector(self._call_action, signature=b'v@:') - if command: - item = parent.m.addItemWithTitle_action_keyEquivalent_(title, s, command) - else: - item = AppKit.NSMenuItem.alloc().init() - item.setAction_(s) - item.setTitle_(title) - parent.m.addItem_(item) - item.setTarget_(self) - - def _call_action(self): - # Don't run action function on main thread - Thread(target=self.action).start() - - def create_submenu(title, line_items, supermenu): - m = InternalMenu(title, parent=supermenu) - for menu_line_item in line_items: - if isinstance(menu_line_item, MenuSeparator): - m = m.separator() - elif isinstance(menu_line_item, MenuAction): - m = m.action(menu_line_item.title, menu_line_item.function) - elif isinstance(menu_line_item, Menu): - create_submenu(menu_line_item.title, menu_line_item.items, m) - - os_bar_menu = BrowserView.app.mainMenu() - if os_bar_menu is None: - os_bar_menu = AppKit.NSMenu.alloc().init() - BrowserView.app.setMainMenu_(os_bar_menu) - - for app_menu in app_menu_list: - create_submenu(app_menu.title, app_menu.items, os_bar_menu) - - -def get_active_window(): - active_window = BrowserView.app.keyWindow() - if active_window is None: - return None - - active_window_number = active_window.windowNumber() - - for uid, browser_view_instance in BrowserView.instances.items(): - if browser_view_instance.window.windowNumber() == active_window_number: - return browser_view_instance.pywebview_window - - return None - - def destroy_window(uid): i = BrowserView.instances.get(uid) if i: