Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wx events end_session and query_end_session are not propagated on macos #4

Open
dominicletz opened this issue Aug 20, 2021 · 4 comments

Comments

@dominicletz
Copy link
Collaborator

Problem
When a user wants to shut down his mac, the running erlang application will ignore the shutdown signal and stay open, and this way it will block the macOS shutdown.

Root Cause
The root problem is that :wxFrame.connect(frame, :end_session) in window.ex does not have any effect. And the :end_session event is never delivered to the application. Only :wxFrame.connect(frame, :close_window) is delivered but does not make it possible to differentiate between the user closing a window or the system shutting down.

Current Workaround
Currently, we're checking on receiving the :close_window event whether any window is shown -- if no window is shown, we assume this is a system shutdown and trigger a OS.shutdown() - the problem with this approach is that when a window is open, the window will be closed, but a system shutdown is still being prevented until the user tries again.

Steps to reproduce
Open the sample app and open the main window. Now open the macOS activity monitor and use the "Stop" option to stop the application. The application should be stopped -- but actually, only the window is closed, only a second "Stop" call will make the application stop if now window is shown (the workaround is in effect)

Background
When using the macOS "Activity Monitor" or when doing a system shutdown to stop an app macOS is not sending a Unix signal but instead an apple event to the app to close it.

wxWidgets is internally creating two events for this. From the docs:

    EVT_QUERY_END_SESSION(func):
    Process a wxEVT_QUERY_END_SESSION session event, supplying the member function. This event can be handled in wxApp-derived class only.
    EVT_END_SESSION(func):
    Process a wxEVT_END_SESSION session event, supplying the member function. This event can be handled in wxApp-derived class only.

Reference:
https://github.com/wxWidgets/wxWidgets/blob/master/src/osx/carbon/app.cpp#L209
https://docs.wxwidgets.org/3.0/classwx_close_event.html

Solution Proposal
A) One option to make these events available is to add Connect() calls in wxe_impl.cpp and pipe them through from there (https://github.com/erlang/otp/blob/master/lib/wx/c_src/wxe_impl.cpp#L151)

B) or alternatively make it possible to issue wxEventHandler:connect(:wxApp, :end_session) calls from erlang and trigger the corresponding app Connect() calls. Currently there is no way to reference in :wxApp instance from erlang space so that would be needed to be added.

there might be other options as well.

@mazz-seven
Copy link

I notice a related issue on macOS when trying to close the app with Command+Q or from the dock Left Click -> Quit or from the leftmost menu -> Quit.
The app is not closing and in the case of, leftmost menu -> quit, it freezes.

P.S
Thank you for the work you did, this project is exactly what I was looking for, for months.

@wojtekmach
Copy link

wojtekmach commented Nov 2, 2021

I have found the following:

Create a default MenuBar so that we can intercept the quit command

// fprintf(stderr, "Dummy Close invoked\r\n");
// wxMac really wants a top level window which command-q quits if there are no
// windows open, and this will kill the erlang, override default handling

https://github.com/erlang/otp/blob/OTP-24.1.4/lib/wx/c_src/wxe_impl.cpp#L161:L168
https://github.com/erlang/otp/blob/OTP-24.1.4/lib/wx/c_src/wxe_impl.cpp#L210:L214

What worked for me was to subscribe to menu bar events and add an explicit handler for the "quit" menu item (event with id wxID_EXIT aka 5006)

Here is my full test rig for this:

defmodule Example.App do
  @moduledoc false

  @behaviour :wx_object

  # https://github.com/erlang/otp/blob/OTP-24.1.2/lib/wx/include/wx.hrl#L1314
  @wx_id_exit 5006

  def start_link() do
    :wx_object.start_link(__MODULE__, [], [])
  end

  @impl true
  def init(_) do
    title = "Example"

    size = {400, 400}
    wx = :wx.new()
    frame = :wxFrame.new(wx, -1, title, size: size)
    :wxFrame.show(frame)

    # Menubar

    menubar = :wxMenuBar.new()
    :wxFrame.setMenuBar(frame, menubar)

    # App Menu
    menu = :wxMenuBar.oSXGetAppleMenu(menubar)
    :wxMenu.setTitle(menu, title)

    # Remove all items except for Quit since we don't yet handle the standard items
    # like "Hide <app>", "Hide Others", "Show All", etc
    for item <- :wxMenu.getMenuItems(menu) do
      if :wxMenuItem.getId(item) == @wx_id_exit do
        :wxMenuItem.setText(item, "Quit #{title}\tCtrl+Q")
      else
        :wxMenu.delete(menu, item)
      end
    end

    :wxFrame.connect(frame, :command_menu_selected)
    state = %{frame: frame}
    {frame, state}
  end

  @impl true
  def handle_event({:wx, @wx_id_exit, _, _, _}, state) do
    {:stop, :normal, state}
  end
end

defmodule Example.Main do
  @moduledoc false

  def main(_args) do
    {:wx_ref, _, _, pid} = Example.App.start_link()
    ref = Process.monitor(pid)

    receive do
      {:DOWN, ^ref, _, _, _} ->
        :ok
    end
  end
end

Example.Main.main(System.argv())

@dominicletz
Copy link
Collaborator Author

That's awesome @wojtekmach I'll have a look at that

@dominicletz
Copy link
Collaborator Author

@wojtekmach and @mazz-seven I've fixed the MacOS quit button issue in 1.3.2!

But the original issue here is still open :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants