Skip to content

app: add close request events - #182

Open
qianniancn wants to merge 1 commit into
gioui:mainfrom
qianniancn:close-request-event
Open

app: add close request events#182
qianniancn wants to merge 1 commit into
gioui:mainfrom
qianniancn:close-request-event

Conversation

@qianniancn

@qianniancn qianniancn commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Applications may need a chance to stop a close before the window is destroyed.
For example, a terminal or a window with a running task can ask for confirmation
before the work is stopped.

On desktop platforms, ClosingEvent is sent when the user closes from the system
title bar, presses Alt+F4, or uses the close control in Gio decorations. The
window closes as usual unless the application calls Abort while handling the event.

Implement this for Windows, macOS, X11, and Wayland.

@eliasnaur

eliasnaur commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

I didn't look at the implementation because I'm not sure this is a good idea. We've had similar discussions before, but basically my question is: when would you ever block the user from closing a window? This is outright impossible on some platforms (mobile, web), but seems misguided in all cases. If there's unsaved data, why not checkpoint that to disk when you receive a ConfigEvent with Focused=false?

@qianniancn

qianniancn commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@eliasnaur That makes sense for unsaved data.

This event would only be for desktop applications. I was thinking of tray
apps: clicking the close button or pressing Alt+F4 hides the window, while a
Quit item in the tray menu exits the application.

Focused=false cannot tell that apart from switching to another application.
A custom title bar can handle its own close button, but not the native close
button or Alt+F4.

Would a desktop-only close event make sense for that case?

@eliasnaur

Copy link
Copy Markdown
Contributor

Would a desktop-only close event make sense for that case?

This is just the DestroyEvent, no? Your proposing a mechanism for blocking window close, but that doesn't seem necessary even for tray applications: when you receive a DestroyEvent, let the window close but don't exit.

@qianniancn

qianniancn commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@eliasnaur understand that DestroyEvent is enough to keep a tray app alive. My point is
not that tray apps are impossible without this event.

I think a close request would better support desktop applications, where
asking to close a window and destroying it are two different steps. With
DestroyEvent, the window is already gone. With a close request, an app can
hide the existing window to the tray or decide to close it with ActionClose.

Would that desktop-specific behavior still be too narrow for the API?

@eliasnaur

eliasnaur commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Why hide the window instead of letting it be destroyed, and then recreating it when the user clicks the tray icon? That seems superior in terms of resources as well.

@qianniancn

qianniancn commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@eliasnaur Destroying and recreating a window works for tray apps. I think this event is
useful beyond that.

On desktops, closing a window is a request that happens before the window is
destroyed. Gio currently sends DestroyEvent only after the window is gone.

For example, closing a terminal or an application with a running task may need
confirmation before stopping that task. Losing focus does not mean the user
wants to stop it. Recreating a window later cannot show that confirmation in
the window being closed.

This would only be emitted on desktop platforms, where native window systems
already distinguish a close request from window destruction. Would that be
useful as a Gio API?

@eliasnaur

Copy link
Copy Markdown
Contributor

Alright, a running task or terminal could theoretically be checkpointed but I suppose you're right that the user expects it to be killed. I'm ok with the concept.

As for the implementation, you seem to have redefined the window close button to never automatically close the window. This is not right for the Gio programs that don't need to ask their users for confirmation. It seems the functionality should be the other way around. I haven't thought long about this, but how about naming the event ClosingEvent and then add an Abort method to it? If the program doesn't call abort during the handling of the event, the Window is closed as usual. WDYT?

@qianniancn

Copy link
Copy Markdown
Contributor Author

@eliasnaur That makes sense. ClosingEvent with Abort keeps the current behavior unless an app wants to stop the close.

If an app aborts to show a confirmation dialog, should Window.Perform(system.ActionClose) bypass ClosingEvent? Otherwise the Confirm button would receive the same event again.

@eliasnaur

Copy link
Copy Markdown
Contributor

If an app aborts to show a confirmation dialog, should Window.Perform(system.ActionClose) bypass ClosingEvent? Otherwise the Confirm button would receive the same event again.

Good question. I prefer to keep ActionClose behaving the same way as a user's close, if for nothing else than ActionClose is also used for custom close buttons for decoration-less windows. That means a program must keep track of whether it wants to abort, but I don't have any better idea.

@qianniancn

Copy link
Copy Markdown
Contributor Author

I will make ActionClose use the same close path and let the application decide whether to abort each close attempt.

@qianniancn

Copy link
Copy Markdown
Contributor Author

@eliasnaur Implemented in 1a1ccdd.
ClosingEvent now closes the window by default. Applications can call Abort to keep it open.
ActionClose follows the same path as a native close request, including custom decorations.

Comment thread app/os_x11.go Outdated
arr[0] = C.long(w.atoms.evDelWindow)
arr[1] = C.CurrentTime
C.XSendEvent(w.x, w.xw, C.False, C.NoEventMask, &xev)
C.XFlush(w.x)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this a separate bug fix?

Comment thread app/system.go Outdated
}

type closingEventState struct {
aborted atomic.Bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why use an atomic value? There should be no concurrency: the event loop is blocking when the program is processing events, in particular ClosingEvent.

Comment thread app/system.go Outdated
// ClosingEvent is sent when the user requests to close a window.
// Call Abort while handling the event to keep the window open.
type ClosingEvent struct {
state *closingEventState

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why indirect state through a pointer, and not just embed the bool state?

Comment thread app/system.go Outdated
abort bool
}

func newClosingEvent() *ClosingEvent {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

useless constructor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I forgot to delete it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed in 9f84aff.

Desktop close actions now emit ClosingEvent before closing a window. Applications retain the existing behavior by doing nothing, or call Abort while handling the event when a confirmation is needed.

ActionClose follows the same path as system close buttons and keyboard shortcuts, including custom decorations.

Signed-off-by: qiannian <qianniancn@gmail.com>

@eliasnaur eliasnaur left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. What do you think, @whereswaldon ?

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

Successfully merging this pull request may close these issues.

2 participants