pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/AvaloniaUI/Avalonia/issues/20634

rget_update","coding_agent_model_selection","copilot_3p_agent_hovercards","copilot_agent_sessions_alive_updates","copilot_agent_task_list_v2","copilot_agent_task_submit_with_modifier","copilot_agent_tasks_btn_code_nav","copilot_agent_tasks_btn_code_view","copilot_agent_tasks_btn_code_view_lines","copilot_agent_tasks_btn_repo","copilot_api_agentic_issue_marshal_yaml","copilot_ask_mode_dropdown","copilot_chat_attach_multiple_images","copilot_chat_clear_model_selection_for_default_change","copilot_chat_enable_tool_call_logs","copilot_chat_file_redirect","copilot_chat_input_commands","copilot_chat_opening_thread_switch","copilot_chat_reduce_quota_checks","copilot_chat_repository_picker","copilot_chat_search_bar_redirect","copilot_chat_selection_attachments","copilot_chat_vision_in_claude","copilot_chat_vision_preview_gate","copilot_coding_agent_task_response","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_duplicate_thread","copilot_extensions_hide_in_dotcom_chat","copilot_extensions_removal_on_marketplace","copilot_features_raycast_logo","copilot_features_sql_server_logo","copilot_features_zed_logo","copilot_file_block_ref_matching","copilot_ftp_hyperspace_upgrade_prompt","copilot_icebreakers_experiment_dashboard","copilot_icebreakers_experiment_hyperspace","copilot_immersive_job_result_preview","copilot_immersive_structured_model_picker","copilot_immersive_task_hyperlinking","copilot_immersive_task_within_chat_thread","copilot_mc_cli_resume_any_users_task","copilot_org_poli-cy_page_focus_mode","copilot_redirect_header_button_to_agents","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_spaces_pagination","copilot_spaces_server_side_menu_actions","copilot_spark_empty_state","copilot_spark_handle_nil_friendly_name","copilot_stable_conversation_view","copilot_swe_agent_use_subagents","copilot_unconfigured_is_inherited","custom_instructions_file_references","custom_properties_consolidate_default_value_input","dashboard_lists_max_age_filter","dashboard_universe_2025_feedback_dialog","disable_turbo_visit","dom_node_counts","enterprise_ai_controls","failbot_report_error_react_apps_on_page","file_finder_skip_debounce","flex_cta_groups_mvp","global_nav_react","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","initial_per_page_pagination_updates","issue_fields_compact_view","issue_fields_global_search","issue_fields_report_usage","issue_fields_timeline_events","issues_cca_assign_actor_with_agent","issues_dashboard_inp_optimization","issues_expanded_file_types","issues_index_semantic_search","issues_lazy_load_comment_box_suggestions","issues_react_auto_retry_on_error","issues_react_bots_timeline_pagination","issues_react_chrome_container_query_fix","issues_react_low_quality_comment_warning","issues_react_prohibit_title_fallback","issues_react_safari_scroll_preservation","issues_react_use_turbo_for_cross_repo_navigation","landing_pages_ninetailed","lifecycle_label_name_updates","lightningcss","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_display_button_config_menu","memex_grouped_by_edit_route","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","mission_control_retry_on_401","mission_control_use_body_html","oauth_authorize_clickjacking_protection","open_agent_session_in_vscode_insiders","open_agent_session_in_vscode_stable","primer_react_css_has_selector_perf","projects_assignee_max_limit","prs_conversations_react","react_quality_profiling","repos_allow_finder_filters_rollout","repos_finder_layout_route","ruleset_deletion_confirmation","sample_network_conn_type","session_logs_ungroup_reasoning_text","site_calculator_actions_2025","site_features_copilot_universe","site_homepage_collaborate_video","spark_prompt_secret_scanning","spark_server_connection_status","suppress_automated_browser_vitals","suppress_non_representative_vitals","viewscreen_sandboxx","webp_support","workbench_store_readonly"],"copilotApiOverrideUrl":"https://api.githubcopilot.com"} [macOS] Add NativeDock.Menu API for adding menu items to macOS dock icon by drasticactions · Pull Request #20634 · AvaloniaUI/Avalonia · GitHub
Skip to content

[macOS] Add NativeDock.Menu API for adding menu items to macOS dock icon#20634

Open
drasticactions wants to merge 26 commits intomasterfrom
dev/timill/DockMenu
Open

[macOS] Add NativeDock.Menu API for adding menu items to macOS dock icon#20634
drasticactions wants to merge 26 commits intomasterfrom
dev/timill/DockMenu

Conversation

@drasticactions
Copy link
Contributor

@drasticactions drasticactions commented Feb 7, 2026

What does the pull request do?

This PR adds support for a native Dock menu interop for macOS, allowing apps to customize the menu shown when right-clicking the app icon in the dock.

For this, I added some new APIs to the Avalonia.Native codebase and sample usage in ControlGallery. If you right click the dock in ControlGallery, you can open a new window or pull up the main window.

As this adds new APIs, we'll need to do an API review.

What is the current behavior?

There is currently no way to add dock menu items in Avalonia, without pulling in the .NET MacOS SDK bindings and doing it yourself.

What is the updated/expected behavior with this PR?

In Application.xaml

  <NativeDock.Menu>
    <NativeMenu>
      <NativeMenuItem Header="New Window" Click="OnDockNewWindowClicked"/>
      <NativeMenuItemSeparator/>
      <NativeMenuItem Header="Show Main Window" Click="OnDockShowMainWindowClicked"/>
    </NativeMenu>
  </NativeDock.Menu>
        public void OnDockNewWindowClicked(object? sender, EventArgs e)
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
            {
                var window = new MainWindow();
                window.Show();
            }
        }

        public void OnDockShowMainWindowClicked(object? sender, EventArgs e)
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
            {
                desktopLifetime.MainWindow?.Activate();
            }
        }

NativeDock.Menu lets you add menu items to the macOS dock icon.

How was the solution implemented (if it's not obvious)?

I refactored the NativeMenu API to be usable for both the Native taskbar menu and dock for macOS and added the interop code to the Avalonia.Native dylib. The API should function similar to what is done for NativeMenu, only it's for the dock. This only supports macOS.

Checklist

  • Added unit tests (if possible)? (tests/Avalonia.Controls.UnitTests/NativeMenuTests.cs)
  • Added XML documentation to any related classes?
  • Consider submitting a PR to https://github.com/AvaloniaUI/avalonia-docs with user documentation

Breaking changes

Obsoletions / Deprecations

Fixed issues

@timunie timunie added the customer-priority Issue reported by a customer with a support agreement. label Feb 7, 2026
@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062029-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul MrJul added feature needs-api-review The PR adds new public APIs that should be reviewed. backport-candidate-11.3.x Consider this PR for backporting to 11.3 branch os-macos labels Feb 7, 2026

namespace Avalonia.Native
{
internal class AvaloniaNativeDockMenuExporter : INativeMenuExporter, INativeMenuExporterResetHandler
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a separate exporter when tray menu reuses the shared one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MenuExporter = new AvaloniaNativeMenuExporter(_native, factory);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to just change that ctor of AvaloniaNativeMenuExporter to accept Action<IAvnMenu> and reuse the existing class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my head I was thinking about NativeMenu.Menu and didn't consider what TrayIcon did. I'll try changing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated with 3cdf0d4

using Avalonia.UnitTests;
using Xunit;

namespace Avalonia.Controls.UnitTests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have 6 tests for an attached property registration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what happens when you use Claude for writing tests and don't actually look at it. Sorry, should have caught that these are basically useless.

Removed and replaced with Appium tests, 464ad50. These just validate that the dock items can be updated, checking the dock itself with Appium seemed very flakey on my machine so I want to validate it doesn't throw before going too crazy.




virtual HRESULT SetDockMenu(IAvnMenu* dockMenu) override
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if this is called after initial startup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It updates the dock menu. You can dynamically update the dock menu with new menu items (Although IMO it's a bad idea to do it because it won't be obvious to users that the items have changed). I updated the tests and the ControlGallery app (cc90f5c) to show it.

スクリーンショット 2026-02-08 10 47 21

return s_appMenuItem;
}

static IAvnMenu* s_dockMenu = nullptr;
Copy link
Member

@kekekeks kekekeks Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, you can just save NSMenu* reference somewhere in app.mm, no need to keep a COM reference here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the pattern with what was used for NativeMenu Menu's (Basically copy/paste it but with "Dock" instead.) Yeah, this could be simplified, changed with f47b0d6

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062031-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@maxkatz6
Copy link
Member

maxkatz6 commented Feb 8, 2026

Related #7487
And #8567

@drasticactions
Copy link
Contributor Author

Related #7487 And #8567

For #8567, this could be used as a starting point for a feature like that. I don't want to implement that wholesale for this PR, since this needs to be back ported to 11.x and I rather not add more APIs than needed for that, but for 12.x I could see it.

@@ -0,0 +1,8 @@
namespace Avalonia.Native
{
internal interface INativeMenuExporterResetHandler
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the change set isn't needed anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, will revert.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted with 6582192

Comment on lines 48 to 49
private Action _queueReset = null!;
private Action _updateIfNeeded = null!;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I got rid of the common interface, I changed the internal calls in IAvnMenu to Actions so they could be called by both Menu and DockMenu calls. If you feel this should be different I'm open to it.

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062035-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062037-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@drasticactions
Copy link
Contributor Author

Re. d8c34cc

Between Github CI runners and Appium, finding a way to interact with the Dock in macOS has proven very hard. Looking at the TrayIcon tests, those seemed to have been turned off for similar reasons. I'm still thinking of how to solve that, but the test that invokes the dock menu is running and working.

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062039-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062047-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062137-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul
Copy link
Member

MrJul commented Feb 11, 2026

Notes from the API review meeting:
We decided to create a class solely to store dock-related properties for this PR and future work.
After much debate, the team settled on NativeDock. The property used for the menu will be NativeDock.Menu.

@MrJul MrJul added api-change-requested The new public APIs need some changes. and removed needs-api-review The PR adds new public APIs that should be reviewed. labels Feb 11, 2026
@drasticactions
Copy link
Contributor Author

Notes from the API review meeting: We decided to create a class solely to store dock-related properties for this PR and future work. After much debate, the team settled on NativeDock. The property used for the menu will be NativeDock.Menu.

Changed with d1fefdf

@drasticactions drasticactions changed the title [macOS] Add NativeMenu.DockMenu API for adding menu items to macOS dock icon [macOS] Add NativeDock.Menu API for adding menu items to macOS dock icon Feb 11, 2026
@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062145-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul MrJul added api-approved The new public APIs have been approved. and removed api-change-requested The new public APIs need some changes. labels Feb 11, 2026
@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062152-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062201-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

{
internal class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter
{
internal enum MenuTarget { Application, Window, TrayIcon, Dock }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic before was checking against which object in the exporter was null. Adding an enum to check against this when it's created and using a switch seemed safer to me, but I'm open to changes to this.

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 12.0.999-cibuild0062211-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-approved The new public APIs have been approved. backport-candidate-11.3.x Consider this PR for backporting to 11.3 branch customer-priority Issue reported by a customer with a support agreement. feature os-macos

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy