This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch fix-modern-macos
in repository efl.

View the commit online.

commit 95db05515ea8ffd2072f48deb903b7a77ca5c2b9
Author: Cedric BAIL <[email protected]>
AuthorDate: Wed Apr 29 17:59:25 2026 -0600

    feat(elementary): auto-populate default macOS menu bar
    
    When elm_win_main_menu_get is called on a Cocoa build, EFL now
    pre-populates two default submenus:
    
      <AppName>
        Quit <AppName>    Cmd+Q
    
      File
        New Window        Cmd+N
    
    The File / New Window entry is only present when an app has registered
    a new-window callback via elm_app_new_window_cb_set. Registering or
    clearing the callback after the menu is built adds or removes the File
    menu live (reactive update via _elm_app_new_window_cb_changed).
    
    Apps that want no default menu can opt out with
    elm_app_default_menu_disable() before creating their first window.
    
    Also adds _elm_cocoa_app_init() — a one-shot setup function that
    installs the Cocoa Dock-reopen handler. Wiring lands in efl_ui_win.c
    in the next commit.
---
 po/POTFILES.in                         |   1 +
 src/lib/ecore_cocoa/Ecore_Cocoa.h      |  21 ++++
 src/lib/ecore_cocoa/ecore_cocoa_menu.m |  21 ++++
 src/lib/elementary/elm_cocoa_menu.c    | 197 ++++++++++++++++++++++++++++++++-
 src/lib/elementary/elm_main.c          |   9 +-
 src/lib/elementary/elm_priv.h          |   3 +
 6 files changed, 246 insertions(+), 6 deletions(-)

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 00f11e6b6e..14975d426c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1854,6 +1854,7 @@ src/lib/elementary/elm_calendar_item_eo.c
 src/lib/elementary/elm_calendar_item_eo.h
 src/lib/elementary/elm_calendar_item_eo.legacy.h
 src/lib/elementary/elm_calendar_legacy.h
+src/lib/elementary/elm_cocoa_menu.c
 src/lib/elementary/elm_check.h
 src/lib/elementary/elm_check_legacy.h
 src/lib/elementary/elm_clock.c
diff --git a/src/lib/ecore_cocoa/Ecore_Cocoa.h b/src/lib/ecore_cocoa/Ecore_Cocoa.h
index c51d0b37e1..782262e71e 100644
--- a/src/lib/ecore_cocoa/Ecore_Cocoa.h
+++ b/src/lib/ecore_cocoa/Ecore_Cocoa.h
@@ -739,12 +739,33 @@ EAPI void ecore_cocoa_menu_item_label_set(Ecore_Cocoa_Menu *menu,
  * @param key   Single-character UTF-8 string, or NULL/"" to clear.
  *
  * @since 1.28
+ * @see ecore_cocoa_menu_item_key_equivalent_set_by_tag
  */
 EAPI void ecore_cocoa_menu_item_key_equivalent_set(Ecore_Cocoa_Menu *menu,
                                                    int               idx,
                                                    const char       *key)
    EINA_ARG_NONNULL(1);
 
+/**
+ * Sets the keyboard equivalent for an existing menu item, identified by
+ * its NSMenuItem tag (the value set via ecore_cocoa_menu_item_tag_set()).
+ *
+ * Tag-based variant of ecore_cocoa_menu_item_key_equivalent_set; preferred
+ * over the positional form because tags survive item deletion while
+ * positional indices shift.
+ *
+ * @param menu  The menu.
+ * @param tag   Tag value previously assigned to the item via
+ *              ecore_cocoa_menu_item_tag_set().
+ * @param key   Single-character UTF-8 string, or NULL/"" to clear.
+ *
+ * @since 1.28
+ */
+EAPI void ecore_cocoa_menu_item_key_equivalent_set_by_tag(Ecore_Cocoa_Menu *menu,
+                                                          int               tag,
+                                                          const char       *key)
+   EINA_ARG_NONNULL(1);
+
 /**
  * Attaches a sub-menu to an existing menu item.
  * @param menu    The parent menu.
diff --git a/src/lib/ecore_cocoa/ecore_cocoa_menu.m b/src/lib/ecore_cocoa/ecore_cocoa_menu.m
index 004e7c6aa9..ba7c21d024 100644
--- a/src/lib/ecore_cocoa/ecore_cocoa_menu.m
+++ b/src/lib/ecore_cocoa/ecore_cocoa_menu.m
@@ -301,6 +301,27 @@ ecore_cocoa_menu_item_key_equivalent_set(Ecore_Cocoa_Menu *menu,
    [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand];
 }
 
+EAPI void
+ecore_cocoa_menu_item_key_equivalent_set_by_tag(Ecore_Cocoa_Menu *menu,
+                                                int               tag,
+                                                const char       *key)
+{
+   NSMenuItem *item;
+
+   EINA_SAFETY_ON_NULL_RETURN(menu);
+
+   item = [menu->ns_menu itemWithTag:(NSInteger)tag];
+   if (!item) return;
+
+   if (!key || !key[0])
+     {
+        [item setKeyEquivalent:@""];
+        return;
+     }
+   [item setKeyEquivalent:[NSString stringWithUTF8String:key]];
+   [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand];
+}
+
 EAPI void
 ecore_cocoa_menu_submenu_set(Ecore_Cocoa_Menu *menu, int idx,
                              Ecore_Cocoa_Menu *submenu)
diff --git a/src/lib/elementary/elm_cocoa_menu.c b/src/lib/elementary/elm_cocoa_menu.c
index 92de78f029..8556633b40 100644
--- a/src/lib/elementary/elm_cocoa_menu.c
+++ b/src/lib/elementary/elm_cocoa_menu.c
@@ -22,6 +22,18 @@ struct _Elm_Cocoa_Menu
    unsigned          next_id;    /* monotonic counter, initial value 0, first id is 1 */
 };
 
+/* References to the auto-populated default menu submenus. NULL if the
+ * default menu wasn't populated (because elm_app_default_menu_disable()
+ * was called) or if the corresponding menu item hasn't been added yet. */
+static Elm_Object_Item *_default_app_item        = NULL;
+static Elm_Object_Item *_default_quit_item       = NULL;
+static Elm_Object_Item *_default_file_item       = NULL;
+static Elm_Object_Item *_default_new_window_item = NULL;
+static Eina_Bool        _default_menu_active     = EINA_FALSE;
+
+/* Tracks whether _elm_cocoa_app_init has installed the reopen handler. */
+static Eina_Bool        _cocoa_app_initialized   = EINA_FALSE;
+
 static void
 _submenu_hash_free_cb(void *data)
 {
@@ -128,6 +140,108 @@ _item_add_recursive(Elm_Cocoa_Menu *cm, Ecore_Cocoa_Menu *ns_parent,
    return id;
 }
 
+/* ------------------------------------------------------------------ */
+/* Default-menu callbacks                                               */
+/* ------------------------------------------------------------------ */
+
+static void
+_cb_default_quit(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED,
+                 void *event_info EINA_UNUSED)
+{
+   elm_exit();
+}
+
+static void
+_cb_default_new_window(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED,
+                       void *event_info EINA_UNUSED)
+{
+   elm_app_new_window_request();
+}
+
+static const char *
+_resolve_app_name(void)
+{
+   const char *n = elm_app_name_get();
+   if (n && n[0]) return n;
+   /* Fallback: a generic but stable string. Cocoa-side CFBundleName
+    * lookup is left for a future refinement; CFBundleName is normally
+    * already wired into elm_app_name via the launcher. */
+   return E_("Application");
+}
+
+static void
+_set_key_equiv(Elm_Object_Item *eo_item, const char *key)
+{
+   Ecore_Cocoa_Menu *ns_menu;
+   Ecore_Cocoa_Menu *sub;
+
+   /* Reach into the menu bridge to set the NSMenuItem key equivalent.
+    * eo_item -> item->cocoa_menu / item->cocoa_idx is set when the item
+    * was added by _item_add_recursive. */
+   ELM_MENU_ITEM_DATA_GET(eo_item, item);
+   if (!item || !item->cocoa_menu) return;
+
+   /* Find the NSMenu that directly contains this item */
+   ns_menu = item->cocoa_menu->root;
+   if (item->parent && item->parent->cocoa_idx > 0)
+     {
+        sub = eina_hash_find(item->cocoa_menu->submenus,
+                             &item->parent->cocoa_idx);
+        if (sub) ns_menu = sub;
+     }
+   ecore_cocoa_menu_item_key_equivalent_set_by_tag(ns_menu, item->cocoa_idx, key);
+}
+
+static void
+_elm_cocoa_menu_populate_defaults(Eo *menu)
+{
+   const char *app_name;
+   char        buf[256];
+   Elm_App_New_Window_Cb cb;
+
+   if (_elm_app_default_menu_disabled_get()) return;
+
+   app_name = _resolve_app_name();
+
+   /* <AppName> menu — always present */
+   _default_app_item = elm_menu_item_add(menu, NULL, NULL, app_name,
+                                         NULL, NULL);
+   if (!_default_app_item) return;
+
+   /* Mark active only after first successful item add. This guards the
+    * multi-window idempotency check: a second register on a different
+    * window finds _default_menu_active=TRUE and skips populating defaults
+    * a second time. */
+   _default_menu_active = EINA_TRUE;
+
+   /* Concatenate translated verb + app_name separately to avoid
+    * format-string injection via a malicious PO file
+    * (e.g., "Quit %s" → "Beenden %n"). */
+   snprintf(buf, sizeof(buf), "%s %s", E_("Quit"), app_name);
+   _default_quit_item = elm_menu_item_add(menu, _default_app_item, NULL, buf,
+                                          _cb_default_quit, NULL);
+   if (_default_quit_item)
+     _set_key_equiv(_default_quit_item, "q");
+
+   /* File menu — only if a new-window cb is registered */
+   cb = NULL;
+   elm_app_new_window_cb_get(&cb, NULL);
+   if (cb)
+     {
+        _default_file_item = elm_menu_item_add(menu, NULL, NULL,
+                                               E_("File"), NULL, NULL);
+        if (_default_file_item)
+          {
+             _default_new_window_item =
+                elm_menu_item_add(menu, _default_file_item, NULL,
+                                  E_("New Window"),
+                                  _cb_default_new_window, NULL);
+             if (_default_new_window_item)
+               _set_key_equiv(_default_new_window_item, "n");
+          }
+     }
+}
+
 /* ------------------------------------------------------------------ */
 /* Public internal API                                                  */
 /* ------------------------------------------------------------------ */
@@ -138,6 +252,7 @@ _elm_cocoa_menu_register(Eo *obj)
    Elm_Cocoa_Menu *cm;
    Eina_List *l;
    Elm_Object_Item *eo_item;
+   Eina_Bool was_first;
 
    ELM_MENU_DATA_GET_OR_RETURN_VAL(obj, sd, NULL);
 
@@ -180,7 +295,20 @@ _elm_cocoa_menu_register(Eo *obj)
               item->label ? item->label : "(null)");
      }
 
-   ecore_cocoa_menu_main_set(cm->root);
+   /* Track whether this is the first registration (before populate, so
+    * we can decide whether to install as main menu). */
+   was_first = !_default_menu_active;
+
+   /* Populate the platform-default menu (App + File submenus) */
+   _elm_cocoa_menu_populate_defaults(obj);
+
+   /* Only install as main menu on first registration. On second+
+    * window, the app-level menu is already set — replacing it with
+    * this window's empty root would blank the macOS menu bar.
+    * The disabled-defaults branch installs unconditionally because the
+    * app fully controls its menu and legitimately needs install. */
+   if (was_first || _elm_app_default_menu_disabled_get())
+     ecore_cocoa_menu_main_set(cm->root);
 
    return cm;
 }
@@ -200,6 +328,15 @@ _elm_cocoa_menu_unregister(Eo *obj)
    eina_hash_free(sd->cocoa_menu->elements);
    free(sd->cocoa_menu);
    sd->cocoa_menu = NULL;
+
+   /* Clear default-menu state so a future register starts fresh.
+    * Items are owned by the elm_menu widget; its destructor frees them.
+    * Do NOT call elm_object_item_del here — the widget is being torn down. */
+   _default_app_item        = NULL;
+   _default_quit_item       = NULL;
+   _default_file_item       = NULL;
+   _default_new_window_item = NULL;
+   _default_menu_active     = EINA_FALSE;
 }
 
 /* Promote a leaf NSMenuItem into a submenu-bearing item.
@@ -335,4 +472,62 @@ _elm_cocoa_menu_update(Elm_Cocoa_Menu *cm)
    eina_iterator_free(it);
 }
 
+/* Cocoa-side definition of the cb-changed hook.
+ * Adds or removes the File / New Window menu item live when the app
+ * registers or clears its new-window callback after the menu has
+ * already been built. */
+void
+_elm_app_new_window_cb_changed(Eina_Bool now_set)
+{
+   Eo *menu;
+
+   /* No effect if defaults were disabled or the menu hasn't been built
+    * yet (in which case _default_app_item is NULL). */
+   if (!_default_menu_active) return;
+   if (!_default_app_item) return;
+   /* No need to re-check elm_app_default_menu_disable() — _default_menu_active
+    * is set only after that check passed in _elm_cocoa_menu_populate_defaults,
+    * and the disable flag is write-once. */
+
+   /* The menu is the parent widget of _default_app_item */
+   menu = elm_object_item_widget_get(_default_app_item);
+   if (!menu) return;
+
+   if (now_set && !_default_file_item)
+     {
+        _default_file_item = elm_menu_item_add(menu, NULL, NULL,
+                                               E_("File"), NULL, NULL);
+        if (_default_file_item)
+          {
+             _default_new_window_item =
+                elm_menu_item_add(menu, _default_file_item, NULL,
+                                  E_("New Window"),
+                                  _cb_default_new_window, NULL);
+             if (_default_new_window_item)
+               _set_key_equiv(_default_new_window_item, "n");
+          }
+     }
+   else if (!now_set && _default_file_item)
+     {
+        /* Removing the parent removes its children too */
+        elm_object_item_del(_default_file_item);
+        _default_file_item       = NULL;
+        _default_new_window_item = NULL;
+     }
+}
+
+static Eina_Bool
+_elm_cocoa_reopen_handler(Eina_Bool has_visible EINA_UNUSED)
+{
+   return elm_app_new_window_request();
+}
+
+void
+_elm_cocoa_app_init(void)
+{
+   if (_cocoa_app_initialized) return;
+   _cocoa_app_initialized = EINA_TRUE;
+   ecore_cocoa_reopen_cb_set(_elm_cocoa_reopen_handler);
+}
+
 #endif /* HAVE_ELEMENTARY_COCOA */
diff --git a/src/lib/elementary/elm_main.c b/src/lib/elementary/elm_main.c
index 27823d3fef..ff5d32dd40 100644
--- a/src/lib/elementary/elm_main.c
+++ b/src/lib/elementary/elm_main.c
@@ -675,16 +675,15 @@ _elm_app_default_menu_disabled_get(void)
    return _app_default_menu_disabled;
 }
 
-/* Default no-op definition. The Cocoa menu bridge (elm_cocoa_menu.c)
- * provides a real implementation in Cocoa builds; until that lands in
- * a follow-up commit, this stub is the only definition and prevents
- * a link error. When the Cocoa implementation arrives this stub will
- * be removed (or guarded) so there is exactly one definition. */
+#ifndef HAVE_ELEMENTARY_COCOA
+/* Default no-op for non-Cocoa platforms. The Cocoa menu bridge in
+ * elm_cocoa_menu.c provides the real implementation. */
 void
 _elm_app_new_window_cb_changed(Eina_Bool now_set EINA_UNUSED)
 {
    /* No-op: only the Cocoa menu bridge cares about live cb changes. */
 }
+#endif
 
 static Eina_Bool _elm_need_e_dbus = EINA_FALSE;
 static void *e_dbus_handle = NULL;
diff --git a/src/lib/elementary/elm_priv.h b/src/lib/elementary/elm_priv.h
index 6f85d60113..14c495257c 100644
--- a/src/lib/elementary/elm_priv.h
+++ b/src/lib/elementary/elm_priv.h
@@ -847,6 +847,9 @@ int             _elm_cocoa_menu_item_add(Elm_Cocoa_Menu *cocoa_menu,
 void            _elm_cocoa_menu_item_delete(Elm_Cocoa_Menu *cocoa_menu,
                                              int id);
 void            _elm_cocoa_menu_update(Elm_Cocoa_Menu *cocoa_menu);
+/* Initialises Cocoa-app-level wiring. Idempotent — safe to call
+ * multiple times. Currently sets up the macOS Dock-reopen handler. */
+void _elm_cocoa_app_init(void);
 #endif /* HAVE_ELEMENTARY_COCOA */
 
 void                 _elm_menu_menu_bar_set(Eo *obj, Eina_Bool menu_bar);

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to