From 0d44b505205a56f683cdaa12cce868e615dd2a04 Mon Sep 17 00:00:00 2001
From: bruvzg <7645683+bruvzg@users.noreply.github.com>
Date: Sun, 15 Oct 2023 20:52:56 +0300
Subject: [PATCH] [macOS] Add default Window and Help menus, allow special menu
 customization.

---
 doc/classes/DisplayServer.xml          |  37 ++
 doc/classes/PopupMenu.xml              |   9 +
 editor/editor_node.cpp                 |  23 +-
 editor/editor_node.h                   |   1 +
 platform/macos/display_server_macos.h  |   7 +-
 platform/macos/display_server_macos.mm | 513 +++++++++++++++----------
 scene/gui/menu_bar.cpp                 |  48 ++-
 scene/gui/popup_menu.cpp               |  52 ++-
 scene/gui/popup_menu.h                 |   6 +
 servers/display_server.cpp             |   7 +
 servers/display_server.h               |   2 +
 11 files changed, 477 insertions(+), 228 deletions(-)

diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 7f7dc1d288c..b8f82accfc0 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -235,6 +235,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -258,6 +261,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -281,6 +287,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -305,6 +314,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -327,6 +339,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -353,6 +368,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -376,6 +394,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -391,6 +412,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -408,6 +432,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -421,6 +448,9 @@
 				[codeblock]
 				"_main" - Main menu (macOS).
 				"_dock" - Dock popup menu (macOS).
+				"_apple" - Apple menu (macOS, custom items added before "Services").
+				"_window" - Window menu (macOS, custom items added after "Bring All to Front").
+				"_help" - Help menu (macOS).
 				[/codeblock]
 			</description>
 		</method>
@@ -549,6 +579,13 @@
 				[b]Note:[/b] This method is implemented only on macOS.
 			</description>
 		</method>
+		<method name="global_menu_get_system_menu_roots" qualifiers="const">
+			<return type="Dictionary" />
+			<description>
+				Returns Dictionary of supported system menu IDs and names.
+				[b]Note:[/b] This method is implemented only on macOS.
+			</description>
+		</method>
 		<method name="global_menu_is_item_checkable" qualifiers="const">
 			<return type="bool" />
 			<param index="0" name="menu_root" type="String" />
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index 3f4ec1b677a..40d5a9f4a24 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -346,6 +346,12 @@
 				Returns [code]true[/code] if the specified item's shortcut is disabled.
 			</description>
 		</method>
+		<method name="is_system_menu" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if the menu is bound to the special system menu.
+			</description>
+		</method>
 		<method name="remove_item">
 			<return type="void" />
 			<param index="0" name="index" type="int" />
@@ -566,6 +572,9 @@
 		<member name="submenu_popup_delay" type="float" setter="set_submenu_popup_delay" getter="get_submenu_popup_delay" default="0.3">
 			Sets the delay time in seconds for the submenu item to popup on mouse hovering. If the popup menu is added as a child of another (acting as a submenu), it will inherit the delay time of the parent menu item.
 		</member>
+		<member name="system_menu_root" type="String" setter="set_system_menu_root" getter="get_system_menu_root" default="&quot;&quot;">
+			If set to one of the values returned by [method DisplayServer.global_menu_get_system_menu_roots], this [PopupMenu] is bound to the special system menu. Only one [PopupMenu] can be bound to each special menu at a time.
+		</member>
 	</members>
 	<signals>
 		<signal name="id_focused">
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 6e30980852c..a2f8976f079 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -7339,6 +7339,20 @@ EditorNode::EditorNode() {
 		file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/file_quit", TTR("Quit"), KeyModifierMask::CMD_OR_CTRL + Key::Q), FILE_QUIT, true);
 	}
 
+	ED_SHORTCUT_AND_COMMAND("editor/editor_settings", TTR("Editor Settings..."));
+	ED_SHORTCUT_OVERRIDE("editor/editor_settings", "macos", KeyModifierMask::META + Key::COMMA);
+#ifdef MACOS_ENABLED
+	if (global_menu) {
+		apple_menu = memnew(PopupMenu);
+		apple_menu->set_system_menu_root("_apple");
+		main_menu->add_child(apple_menu);
+
+		apple_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES);
+		apple_menu->add_separator();
+		apple_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
+	}
+#endif
+
 	project_menu = memnew(PopupMenu);
 	project_menu->set_name(TTR("Project"));
 	main_menu->add_child(project_menu);
@@ -7404,9 +7418,13 @@ EditorNode::EditorNode() {
 	settings_menu->set_name(TTR("Editor"));
 	main_menu->add_child(settings_menu);
 
-	ED_SHORTCUT_AND_COMMAND("editor/editor_settings", TTR("Editor Settings..."));
-	ED_SHORTCUT_OVERRIDE("editor/editor_settings", "macos", KeyModifierMask::META + Key::COMMA);
+#ifdef MACOS_ENABLED
+	if (!global_menu) {
+		settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES);
+	}
+#else
 	settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES);
+#endif
 	settings_menu->add_shortcut(ED_SHORTCUT("editor/command_palette", TTR("Command Palette..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::P), HELP_COMMAND_PALETTE);
 	settings_menu->add_separator();
 
@@ -7453,6 +7471,7 @@ EditorNode::EditorNode() {
 
 	help_menu = memnew(PopupMenu);
 	help_menu->set_name(TTR("Help"));
+	help_menu->set_system_menu_root("_help");
 	main_menu->add_child(help_menu);
 
 	help_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 2b6e89ac5ad..7420810dcea 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -346,6 +346,7 @@ private:
 	EditorRunBar *project_run_bar = nullptr;
 	VBoxContainer *main_screen_vbox = nullptr;
 	MenuBar *main_menu = nullptr;
+	PopupMenu *apple_menu = nullptr;
 	PopupMenu *file_menu = nullptr;
 	PopupMenu *project_menu = nullptr;
 	PopupMenu *debug_menu = nullptr;
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index f012292f84d..a19e02eb21e 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -140,6 +140,8 @@ private:
 	String rendering_driver;
 
 	NSMenu *apple_menu = nullptr;
+	NSMenu *window_menu = nullptr;
+	NSMenu *help_menu = nullptr;
 	NSMenu *dock_menu = nullptr;
 	struct MenuData {
 		Callable open;
@@ -226,7 +228,8 @@ private:
 
 	static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);
 
-	bool _has_help_menu() const;
+	int _get_system_menu_start(const NSMenu *p_menu) const;
+	int _get_system_menu_count(const NSMenu *p_menu) const;
 	NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out);
 
 public:
@@ -321,6 +324,8 @@ public:
 	virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override;
 	virtual void global_menu_clear(const String &p_menu_root) override;
 
+	virtual Dictionary global_menu_get_system_menu_roots() const override;
+
 	virtual bool tts_is_speaking() const override;
 	virtual bool tts_is_paused() const override;
 	virtual TypedArray<Dictionary> tts_get_voices() const override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 407a3158277..742b552e131 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -64,6 +64,9 @@
 #import <IOKit/hid/IOHIDKeys.h>
 #import <IOKit/hid/IOHIDLib.h>
 
+#define MENU_TAG_START 0x00004447
+#define MENU_TAG_END 0xFFFF4447
+
 const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) const {
 	const NSMenu *menu = nullptr;
 	if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
@@ -72,16 +75,21 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons
 	} else if (p_menu_root.to_lower() == "_dock") {
 		// macOS dock menu.
 		menu = dock_menu;
+	} else if (p_menu_root.to_lower() == "_apple") {
+		// macOS Apple menu.
+		menu = apple_menu;
+	} else if (p_menu_root.to_lower() == "_window") {
+		// macOS Window menu.
+		menu = window_menu;
+	} else if (p_menu_root.to_lower() == "_help") {
+		// macOS Help menu.
+		menu = help_menu;
 	} else {
 		// Submenu.
 		if (submenu.has(p_menu_root)) {
 			menu = submenu[p_menu_root].menu;
 		}
 	}
-	if (menu == apple_menu) {
-		// Do not allow to change Apple menu.
-		return nullptr;
-	}
 	return menu;
 }
 
@@ -93,6 +101,15 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
 	} else if (p_menu_root.to_lower() == "_dock") {
 		// macOS dock menu.
 		menu = dock_menu;
+	} else if (p_menu_root.to_lower() == "_apple") {
+		// macOS Apple menu.
+		menu = apple_menu;
+	} else if (p_menu_root.to_lower() == "_window") {
+		// macOS Window menu.
+		menu = window_menu;
+	} else if (p_menu_root.to_lower() == "_help") {
+		// macOS Help menu.
+		menu = help_menu;
 	} else {
 		// Submenu.
 		if (!submenu.has(p_menu_root)) {
@@ -104,10 +121,6 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
 		}
 		menu = submenu[p_menu_root].menu;
 	}
-	if (menu == apple_menu) {
-		// Do not allow to change Apple menu.
-		return nullptr;
-	}
 	return menu;
 }
 
@@ -820,22 +833,6 @@ String DisplayServerMacOS::get_name() const {
 	return "macOS";
 }
 
-bool DisplayServerMacOS::_has_help_menu() const {
-	if ([NSApp helpMenu]) {
-		return true;
-	} else {
-		NSMenu *menu = [NSApp mainMenu];
-		const NSMenuItem *menu_item = [menu itemAtIndex:[menu numberOfItems] - 1];
-		if (menu_item) {
-			String menu_name = String::utf8([[menu_item title] UTF8String]);
-			if (menu_name == "Help" || menu_name == RTR("Help")) {
-				return true;
-			}
-		}
-		return false;
-	}
-}
-
 bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const {
 	if (submenu_inv.has(p_menu)) {
 		const MenuData &md = submenu[submenu_inv[p_menu]];
@@ -854,24 +851,57 @@ bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const {
 	return false;
 }
 
+int DisplayServerMacOS::_get_system_menu_start(const NSMenu *p_menu) const {
+	if (p_menu == [NSApp mainMenu]) { // Skip Apple menu.
+		return 1;
+	}
+	if (p_menu == apple_menu || p_menu == window_menu || p_menu == help_menu) {
+		int count = [p_menu numberOfItems];
+		for (int i = 0; i < count; i++) {
+			NSMenuItem *menu_item = [p_menu itemAtIndex:i];
+			if (menu_item.tag == MENU_TAG_START) {
+				return i + 1;
+			}
+		}
+	}
+	return 0;
+}
+
+int DisplayServerMacOS::_get_system_menu_count(const NSMenu *p_menu) const {
+	if (p_menu == [NSApp mainMenu]) { // Skip Apple, Window and Help menu.
+		return [p_menu numberOfItems] - 3;
+	}
+	if (p_menu == apple_menu || p_menu == window_menu || p_menu == help_menu) {
+		int start = 0;
+		int count = [p_menu numberOfItems];
+		for (int i = 0; i < count; i++) {
+			NSMenuItem *menu_item = [p_menu itemAtIndex:i];
+			if (menu_item.tag == MENU_TAG_START) {
+				start = i + 1;
+			}
+			if (menu_item.tag == MENU_TAG_END) {
+				return i - start;
+			}
+		}
+	}
+	return [p_menu numberOfItems];
+}
+
 NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) {
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
 		NSMenuItem *menu_item;
-		int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
-		if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
-			p_index = [menu numberOfItems];
-		} else if (p_index < 0) {
-			p_index = item_count;
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		if (p_index < 0) {
+			p_index = item_start + item_count;
 		} else {
-			if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-				p_index++;
-			}
-			p_index = CLAMP(p_index, 0, item_count);
+			p_index += item_start;
+			p_index = CLAMP(p_index, item_start, item_start + item_count);
 		}
 		menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
-		*r_out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
+		*r_out = p_index - item_start;
 		return menu_item;
 	}
 	return nullptr;
@@ -1070,19 +1100,16 @@ int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root,
 			return -1;
 		}
 		NSMenuItem *menu_item;
-		int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
-		if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
-			p_index = [menu numberOfItems];
-		} else if (p_index < 0) {
-			p_index = item_count;
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		if (p_index < 0) {
+			p_index = item_start + item_count;
 		} else {
-			if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-				p_index++;
-			}
-			p_index = CLAMP(p_index, 0, item_count);
+			p_index += item_start;
+			p_index = CLAMP(p_index, item_start, item_start + item_count);
 		}
 		menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
-		out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
+		out = p_index - item_start;
 
 		GodotMenuItem *obj = [[GodotMenuItem alloc] init];
 		obj->callback = Callable();
@@ -1105,13 +1132,16 @@ int DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int
 		if (menu == [NSApp mainMenu]) { // Do not add separators into main menu.
 			return -1;
 		}
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
 		if (p_index < 0) {
-			p_index = [menu numberOfItems];
+			p_index = item_start + item_count;
 		} else {
-			p_index = CLAMP(p_index, 0, [menu numberOfItems]);
+			p_index += item_start;
+			p_index = CLAMP(p_index, item_start, item_start + item_count);
 		}
 		[menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
-		return p_index;
+		return p_index - item_start;
 	}
 	return -1;
 }
@@ -1121,11 +1151,8 @@ int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_men
 
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - 1;
-		} else {
-			return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
-		}
+		int item_start = _get_system_menu_start(menu);
+		return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - item_start;
 	}
 
 	return -1;
@@ -1136,16 +1163,14 @@ int DisplayServerMacOS::global_menu_get_item_index_from_tag(const String &p_menu
 
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
-		for (NSInteger i = (menu == [NSApp mainMenu]) ? 1 : 0; i < [menu numberOfItems]; i++) {
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		for (NSInteger i = item_start; i < item_start + item_count; i++) {
 			const NSMenuItem *menu_item = [menu itemAtIndex:i];
 			if (menu_item) {
 				const GodotMenuItem *obj = [menu_item representedObject];
 				if (obj && obj->meta == p_tag) {
-					if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-						return i - 1;
-					} else {
-						return i;
-					}
+					return i - item_start;
 				}
 			}
 		}
@@ -1160,10 +1185,10 @@ bool DisplayServerMacOS::global_menu_is_item_checked(const String &p_menu_root,
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, false);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			return ([menu_item state] == NSControlStateValueOn);
@@ -1178,10 +1203,10 @@ bool DisplayServerMacOS::global_menu_is_item_checkable(const String &p_menu_root
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, false);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1199,10 +1224,10 @@ bool DisplayServerMacOS::global_menu_is_item_radio_checkable(const String &p_men
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, false);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1220,10 +1245,10 @@ Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, Callable());
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable());
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1241,10 +1266,10 @@ Callable DisplayServerMacOS::global_menu_get_item_key_callback(const String &p_m
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, Callable());
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable());
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1262,10 +1287,10 @@ Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root,
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, Variant());
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Variant());
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, Variant());
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1283,10 +1308,10 @@ String DisplayServerMacOS::global_menu_get_item_text(const String &p_menu_root,
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, String());
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			return String::utf8([[menu_item title] UTF8String]);
@@ -1301,10 +1326,10 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, String());
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			NSMenu *sub_menu = [menu_item submenu];
@@ -1322,10 +1347,10 @@ Key DisplayServerMacOS::global_menu_get_item_accelerator(const String &p_menu_ro
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, Key::NONE);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Key::NONE);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, Key::NONE);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
@@ -1358,10 +1383,10 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root,
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, false);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			return ![menu_item isEnabled];
@@ -1376,10 +1401,10 @@ bool DisplayServerMacOS::global_menu_is_item_hidden(const String &p_menu_root, i
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, false);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			return [menu_item isHidden];
@@ -1394,10 +1419,10 @@ String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_roo
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, String());
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			return String::utf8([[menu_item toolTip] UTF8String]);
@@ -1412,10 +1437,10 @@ int DisplayServerMacOS::global_menu_get_item_state(const String &p_menu_root, in
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1433,10 +1458,10 @@ int DisplayServerMacOS::global_menu_get_item_max_states(const String &p_menu_roo
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1454,10 +1479,10 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Ref<Texture2D>());
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, Ref<Texture2D>());
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1477,10 +1502,10 @@ int DisplayServerMacOS::global_menu_get_item_indentation_level(const String &p_m
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND_V(p_idx < 0, 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
 		const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			return [menu_item indentationLevel];
@@ -1495,10 +1520,10 @@ void DisplayServerMacOS::global_menu_set_item_checked(const String &p_menu_root,
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			if (p_checked) {
@@ -1516,10 +1541,10 @@ void DisplayServerMacOS::global_menu_set_item_checkable(const String &p_menu_roo
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1535,10 +1560,10 @@ void DisplayServerMacOS::global_menu_set_item_radio_checkable(const String &p_me
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1554,10 +1579,10 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1573,10 +1598,10 @@ void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_me
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1592,10 +1617,10 @@ void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1611,10 +1636,10 @@ void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1630,10 +1655,10 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			[menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
@@ -1647,10 +1672,10 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root,
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu && p_submenu.is_empty()) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
@@ -1673,10 +1698,10 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root,
 			return;
 		}
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			[menu setSubmenu:sub_menu forItem:menu_item];
@@ -1690,10 +1715,10 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			if (p_keycode == Key::NONE) {
@@ -1713,10 +1738,10 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			[menu_item setEnabled:(!p_disabled)];
@@ -1730,10 +1755,10 @@ void DisplayServerMacOS::global_menu_set_item_hidden(const String &p_menu_root,
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			[menu_item setHidden:p_hidden];
@@ -1747,10 +1772,10 @@ void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root,
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			[menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
@@ -1764,10 +1789,10 @@ void DisplayServerMacOS::global_menu_set_item_state(const String &p_menu_root, i
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1783,10 +1808,10 @@ void DisplayServerMacOS::global_menu_set_item_max_states(const String &p_menu_ro
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1802,10 +1827,10 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			GodotMenuItem *obj = [menu_item representedObject];
@@ -1832,10 +1857,10 @@ void DisplayServerMacOS::global_menu_set_item_indentation_level(const String &p_
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if (menu_item) {
 			[menu_item setIndentationLevel:p_level];
@@ -1848,11 +1873,7 @@ int DisplayServerMacOS::global_menu_get_item_count(const String &p_menu_root) co
 
 	const NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			return [menu numberOfItems] - 1;
-		} else {
-			return [menu numberOfItems];
-		}
+		return _get_system_menu_count(menu);
 	} else {
 		return 0;
 	}
@@ -1864,10 +1885,10 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int
 	NSMenu *menu = _get_menu_root(p_menu_root);
 	if (menu) {
 		ERR_FAIL_COND(p_idx < 0);
-		if (menu == [NSApp mainMenu]) { // Skip Apple menu.
-			p_idx++;
-		}
-		ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+		int item_start = _get_system_menu_start(menu);
+		int item_count = _get_system_menu_count(menu);
+		p_idx += item_start;
+		ERR_FAIL_COND(p_idx >= item_start + item_count);
 		NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
 		if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
 			ERR_PRINT("Can't remove open menu!");
@@ -1886,12 +1907,41 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
 			ERR_PRINT("Can't remove open menu!");
 			return;
 		}
-		[menu removeAllItems];
-		// Restore Apple menu.
+
+		if (menu == apple_menu) {
+			int start = _get_system_menu_start(apple_menu);
+			int count = _get_system_menu_count(apple_menu);
+			for (int i = start + count - 1; i >= start; i--) {
+				[apple_menu removeItemAtIndex:i];
+			}
+		} else if (menu == window_menu) {
+			int start = _get_system_menu_start(window_menu);
+			int count = _get_system_menu_count(window_menu);
+			for (int i = start + count - 1; i >= start; i--) {
+				[window_menu removeItemAtIndex:i];
+			}
+		} else if (menu == help_menu) {
+			int start = _get_system_menu_start(help_menu);
+			int count = _get_system_menu_count(help_menu);
+			for (int i = start + count - 1; i >= start; i--) {
+				[help_menu removeItemAtIndex:i];
+			}
+		} else {
+			[menu removeAllItems];
+		}
+
+		// Restore Apple, Window and Help menu.
 		if (menu == [NSApp mainMenu]) {
 			NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
 			[menu setSubmenu:apple_menu forItem:menu_item];
+
+			menu_item = [menu addItemWithTitle:@"Window" action:nil keyEquivalent:@""];
+			[menu setSubmenu:window_menu forItem:menu_item];
+
+			menu_item = [menu addItemWithTitle:@"Help" action:nil keyEquivalent:@""];
+			[menu setSubmenu:help_menu forItem:menu_item];
 		}
+
 		if (submenu.has(p_menu_root)) {
 			submenu_inv.erase(submenu[p_menu_root].menu);
 			submenu.erase(p_menu_root);
@@ -1899,6 +1949,15 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
 	}
 }
 
+Dictionary DisplayServerMacOS::global_menu_get_system_menu_roots() const {
+	Dictionary out;
+	out["_dock"] = "@Dock";
+	out["_apple"] = "@Apple";
+	out["_window"] = "Window";
+	out["_help"] = "Help";
+	return out;
+}
+
 bool DisplayServerMacOS::tts_is_speaking() const {
 	ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
 	return [tts isSpeaking];
@@ -3564,6 +3623,9 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win
 		} break;
 		case WINDOW_FLAG_NO_FOCUS: {
 			wd.no_focus = p_enabled;
+
+			NSWindow *w = wd.window_object;
+			w.excludedFromWindowsMenu = wd.is_popup || wd.no_focus;
 		} break;
 		case WINDOW_FLAG_MOUSE_PASSTHROUGH: {
 			wd.mpass = p_enabled;
@@ -3572,6 +3634,9 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win
 			ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
 			ERR_FAIL_COND_MSG([wd.window_object isVisible] && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");
 			wd.is_popup = p_enabled;
+
+			NSWindow *w = wd.window_object;
+			w.excludedFromWindowsMenu = wd.is_popup || wd.no_focus;
 		} break;
 		default: {
 		}
@@ -4488,6 +4553,13 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
 
 	[apple_menu addItem:[NSMenuItem separatorItem]];
 
+	menu_item = [apple_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""];
+	menu_item.hidden = YES;
+	menu_item.tag = MENU_TAG_START;
+	menu_item = [apple_menu addItemWithTitle:@"_end_" action:nil keyEquivalent:@""];
+	menu_item.hidden = YES;
+	menu_item.tag = MENU_TAG_END;
+
 	NSMenu *services = [[NSMenu alloc] initWithTitle:@""];
 	menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""];
 	[apple_menu setSubmenu:services forItem:menu_item];
@@ -4508,10 +4580,41 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
 	title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
 	[apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
 
+	window_menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Window", nil)];
+	[window_menu addItemWithTitle:NSLocalizedString(@"Minimize", nil) action:@selector(performMiniaturize:) keyEquivalent:@"m"];
+	[window_menu addItemWithTitle:NSLocalizedString(@"Zoom", nil) action:@selector(performZoom:) keyEquivalent:@""];
+	[window_menu addItem:[NSMenuItem separatorItem]];
+	[window_menu addItemWithTitle:NSLocalizedString(@"Bring All to Front", nil) action:@selector(bringAllToFront:) keyEquivalent:@""];
+	[window_menu addItem:[NSMenuItem separatorItem]];
+	menu_item = [window_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""];
+	menu_item.hidden = YES;
+	menu_item.tag = MENU_TAG_START;
+	menu_item = [window_menu addItemWithTitle:@"_end_" action:nil keyEquivalent:@""];
+	menu_item.hidden = YES;
+	menu_item.tag = MENU_TAG_END;
+
+	help_menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Help", nil)];
+	menu_item = [help_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""];
+	menu_item.hidden = YES;
+	menu_item.tag = MENU_TAG_START;
+	menu_item = [help_menu addItemWithTitle:@"_end_" action:nil keyEquivalent:@""];
+	menu_item.hidden = YES;
+	menu_item.tag = MENU_TAG_END;
+
+	[NSApp setWindowsMenu:window_menu];
+	[NSApp setHelpMenu:help_menu];
+
 	// Add items to the menu bar.
 	NSMenu *main_menu = [NSApp mainMenu];
 	menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
 	[main_menu setSubmenu:apple_menu forItem:menu_item];
+
+	menu_item = [main_menu addItemWithTitle:NSLocalizedString(@"Window", nil) action:nil keyEquivalent:@""];
+	[main_menu setSubmenu:window_menu forItem:menu_item];
+
+	menu_item = [main_menu addItemWithTitle:NSLocalizedString(@"Help", nil) action:nil keyEquivalent:@""];
+	[main_menu setSubmenu:help_menu forItem:menu_item];
+
 	[main_menu setAutoenablesItems:NO];
 
 	//!!!!!!!!!!!!!!!!!!!!!!!!!!
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 7418ba73334..7fa2653ed94 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -249,11 +249,13 @@ String MenuBar::bind_global_menu() {
 	Vector<PopupMenu *> popups = _get_popups();
 	for (int i = 0; i < menu_cache.size(); i++) {
 		String submenu_name = popups[i]->bind_global_menu();
-		int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
-		ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
-		ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
-		ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
-		ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+		if (!popups[i]->is_system_menu()) {
+			int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
+			ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
+			ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
+			ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
+			ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+		}
 	}
 
 	return global_menu_name;
@@ -268,8 +270,10 @@ void MenuBar::unbind_global_menu() {
 	int global_start = _find_global_start_index();
 	Vector<PopupMenu *> popups = _get_popups();
 	for (int i = menu_cache.size() - 1; i >= 0; i--) {
-		popups[i]->unbind_global_menu();
-		ds->global_menu_remove_item("_main", global_start + i);
+		if (!popups[i]->is_system_menu()) {
+			popups[i]->unbind_global_menu();
+			ds->global_menu_remove_item("_main", global_start + i);
+		}
 	}
 
 	global_menu_name = String();
@@ -558,8 +562,10 @@ void MenuBar::add_child_notify(Node *p_child) {
 
 	if (!global_menu_name.is_empty()) {
 		String submenu_name = pm->bind_global_menu();
-		int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
-		DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+		if (!pm->is_system_menu()) {
+			int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
+			DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+		}
 	}
 	update_minimum_size();
 }
@@ -587,14 +593,16 @@ void MenuBar::move_child_notify(Node *p_child) {
 	menu_cache.insert(new_idx, menu);
 
 	if (!global_menu_name.is_empty()) {
-		int global_start = _find_global_start_index();
-		if (old_idx != -1) {
-			DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
-		}
-		if (new_idx != -1) {
-			String submenu_name = pm->bind_global_menu();
-			int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
-			DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+		if (!pm->is_system_menu()) {
+			int global_start = _find_global_start_index();
+			if (old_idx != -1) {
+				DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
+			}
+			if (new_idx != -1) {
+				String submenu_name = pm->bind_global_menu();
+				int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
+				DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+			}
 		}
 	}
 }
@@ -612,8 +620,10 @@ void MenuBar::remove_child_notify(Node *p_child) {
 	menu_cache.remove_at(idx);
 
 	if (!global_menu_name.is_empty()) {
-		pm->unbind_global_menu();
-		DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+		if (!pm->is_system_menu()) {
+			pm->unbind_global_menu();
+			DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+		}
 	}
 
 	p_child->remove_meta("_menu_name");
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index d6b8dd02025..605a3fe3974 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -40,6 +40,8 @@
 #include "scene/gui/menu_bar.h"
 #include "scene/theme/theme_db.h"
 
+HashMap<String, PopupMenu *> PopupMenu::system_menus;
+
 String PopupMenu::bind_global_menu() {
 #ifdef TOOLS_ENABLED
 	if (is_part_of_edited_scene()) {
@@ -54,8 +56,20 @@ String PopupMenu::bind_global_menu() {
 		return global_menu_name; // Already bound;
 	}
 
-	DisplayServer *ds = DisplayServer::get_singleton();
 	global_menu_name = "__PopupMenu#" + itos(get_instance_id());
+	if (system_menu_name.length() > 0) {
+		if (system_menus.has(system_menu_name)) {
+			WARN_PRINT(vformat("Attempting to bind PopupMenu to the special menu %s, but another menu is already bound to it. This menu: %s, current menu: %s", system_menu_name, this->get_description(), system_menus[system_menu_name]->get_description()));
+		} else {
+			const Dictionary &supported_special_names = DisplayServer::get_singleton()->global_menu_get_system_menu_roots();
+			if (supported_special_names.has(system_menu_name)) {
+				system_menus[system_menu_name] = this;
+				global_menu_name = system_menu_name;
+			}
+		}
+	}
+
+	DisplayServer *ds = DisplayServer::get_singleton();
 	ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close));
 	for (int i = 0; i < items.size(); i++) {
 		Item &item = items.write[i];
@@ -105,6 +119,10 @@ void PopupMenu::unbind_global_menu() {
 		return;
 	}
 
+	if (global_menu_name == system_menu_name && system_menus[system_menu_name] == this) {
+		system_menus.erase(system_menu_name);
+	}
+
 	for (int i = 0; i < items.size(); i++) {
 		Item &item = items.write[i];
 		if (!item.submenu.is_empty()) {
@@ -120,6 +138,24 @@ void PopupMenu::unbind_global_menu() {
 	global_menu_name = String();
 }
 
+bool PopupMenu::is_system_menu() const {
+	return (global_menu_name == system_menu_name) && (system_menu_name.length() > 0);
+}
+
+void PopupMenu::set_system_menu_root(const String &p_special) {
+	if (is_inside_tree() && system_menu_name.length() > 0) {
+		unbind_global_menu();
+	}
+	system_menu_name = p_special;
+	if (is_inside_tree() && system_menu_name.length() > 0) {
+		bind_global_menu();
+	}
+}
+
+String PopupMenu::get_system_menu_root() const {
+	return system_menu_name;
+}
+
 String PopupMenu::_get_accel_text(const Item &p_item) const {
 	if (p_item.shortcut.is_valid()) {
 		return p_item.shortcut->get_as_text();
@@ -947,6 +983,15 @@ void PopupMenu::_notification(int p_what) {
 			if (!is_embedded()) {
 				set_flag(FLAG_NO_FOCUS, true);
 			}
+			if (system_menu_name.length() > 0) {
+				bind_global_menu();
+			}
+		} break;
+
+		case NOTIFICATION_EXIT_TREE: {
+			if (system_menu_name.length() > 0) {
+				unbind_global_menu();
+			}
 		} break;
 
 		case NOTIFICATION_THEME_CHANGED:
@@ -2716,11 +2761,16 @@ void PopupMenu::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search);
 	ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search);
 
+	ClassDB::bind_method(D_METHOD("is_system_menu"), &PopupMenu::is_system_menu);
+	ClassDB::bind_method(D_METHOD("set_system_menu_root", "special"), &PopupMenu::set_system_menu_root);
+	ClassDB::bind_method(D_METHOD("get_system_menu_root"), &PopupMenu::get_system_menu_root);
+
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
+	ADD_PROPERTY(PropertyInfo(Variant::STRING, "system_menu_root", PROPERTY_HINT_ENUM, "Dock (macOS):_dock,Apple Menu(macOS):_apple,Window Menu(macOS):_window,Help Menu(macOS):_help"), "set_system_menu_root", "get_system_menu_root");
 
 	ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
 
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 5d5f4a8322a..4703686c518 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -40,6 +40,8 @@
 class PopupMenu : public Popup {
 	GDCLASS(PopupMenu, Popup);
 
+	static HashMap<String, PopupMenu *> system_menus;
+
 	struct Item {
 		Ref<Texture2D> icon;
 		int icon_max_width = 0;
@@ -90,6 +92,7 @@ class PopupMenu : public Popup {
 	};
 
 	String global_menu_name;
+	String system_menu_name;
 
 	bool close_allowed = false;
 	bool activated_by_keyboard = false;
@@ -218,6 +221,9 @@ public:
 
 	String bind_global_menu();
 	void unbind_global_menu();
+	bool is_system_menu() const;
+	void set_system_menu_root(const String &p_special);
+	String get_system_menu_root() const;
 
 	void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
 	void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 6459cc74622..bb28bc0eb8b 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -267,6 +267,11 @@ void DisplayServer::global_menu_clear(const String &p_menu_root) {
 	WARN_PRINT("Global menus not supported by this display server.");
 }
 
+Dictionary DisplayServer::global_menu_get_system_menu_roots() const {
+	WARN_PRINT("Global menus not supported by this display server.");
+	return Dictionary();
+}
+
 bool DisplayServer::tts_is_speaking() const {
 	WARN_PRINT("TTS is not supported by this display server.");
 	return false;
@@ -652,6 +657,8 @@ void DisplayServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu_root", "idx"), &DisplayServer::global_menu_remove_item);
 	ClassDB::bind_method(D_METHOD("global_menu_clear", "menu_root"), &DisplayServer::global_menu_clear);
 
+	ClassDB::bind_method(D_METHOD("global_menu_get_system_menu_roots"), &DisplayServer::global_menu_get_system_menu_roots);
+
 	ClassDB::bind_method(D_METHOD("tts_is_speaking"), &DisplayServer::tts_is_speaking);
 	ClassDB::bind_method(D_METHOD("tts_is_paused"), &DisplayServer::tts_is_paused);
 	ClassDB::bind_method(D_METHOD("tts_get_voices"), &DisplayServer::tts_get_voices);
diff --git a/servers/display_server.h b/servers/display_server.h
index d2e112d2241..4450677f718 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -185,6 +185,8 @@ public:
 	virtual void global_menu_remove_item(const String &p_menu_root, int p_idx);
 	virtual void global_menu_clear(const String &p_menu_root);
 
+	virtual Dictionary global_menu_get_system_menu_roots() const;
+
 	struct TTSUtterance {
 		String text;
 		String voice;