winemac: Handle a Cocoa quit request as a single-process session shutdown.

This lets programs ask the user to save documents, etc., and possibly cancel
the quit.
This commit is contained in:
Ken Thomases 2013-04-03 18:56:55 -05:00 committed by Alexandre Julliard
parent ca2d7140fb
commit fd1e187ac7
5 changed files with 240 additions and 1 deletions

View File

@ -1206,6 +1206,49 @@ - (void)applicationDidResignActive:(NSNotification *)notification
macdrv_release_event(event);
}
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
{
NSApplicationTerminateReply ret = NSTerminateNow;
NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
NSAppleEventDescriptor* desc = [m currentAppleEvent];
macdrv_event* event;
WineEventQueue* queue;
event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
event->deliver = 1;
switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
{
case kAELogOut:
case kAEReallyLogOut:
event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
break;
case kAEShowRestartDialog:
event->app_quit_requested.reason = QUIT_REASON_RESTART;
break;
case kAEShowShutdownDialog:
event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
break;
default:
event->app_quit_requested.reason = QUIT_REASON_NONE;
break;
}
[eventQueuesLock lock];
if ([eventQueues count])
{
for (queue in eventQueues)
[queue postEvent:event];
ret = NSTerminateLater;
}
[eventQueuesLock unlock];
macdrv_release_event(event);
return ret;
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
@ -1541,3 +1584,13 @@ void macdrv_set_application_icon(CFArrayRef images)
[NSApp setApplicationIconFromCGImageArray:imageArray];
});
}
/***********************************************************************
* macdrv_quit_reply
*/
void macdrv_quit_reply(int reply)
{
OnMainThread(^{
[NSApp replyToApplicationShouldTerminate:reply];
});
}

View File

@ -33,6 +33,7 @@ static const char *dbgstr_event(int type)
{
static const char * const event_names[] = {
"APP_DEACTIVATED",
"APP_QUIT_REQUESTED",
"DISPLAYS_CHANGED",
"KEY_PRESS",
"KEY_RELEASE",
@ -87,6 +88,7 @@ static macdrv_event_mask get_event_mask(DWORD mask)
if (mask & QS_POSTMESSAGE)
{
event_mask |= event_mask_for_type(APP_DEACTIVATED);
event_mask |= event_mask_for_type(APP_QUIT_REQUESTED);
event_mask |= event_mask_for_type(DISPLAYS_CHANGED);
event_mask |= event_mask_for_type(STATUS_ITEM_CLICKED);
event_mask |= event_mask_for_type(WINDOW_CLOSE_REQUESTED);
@ -165,6 +167,9 @@ void macdrv_handle_event(const macdrv_event *event)
case APP_DEACTIVATED:
macdrv_app_deactivated();
break;
case APP_QUIT_REQUESTED:
macdrv_app_quit_requested(event);
break;
case DISPLAYS_CHANGED:
macdrv_displays_changed(event);
break;
@ -257,7 +262,8 @@ DWORD CDECL macdrv_MsgWaitForMultipleObjectsEx(DWORD count, const HANDLE *handle
timeout, flags & MWMO_ALERTABLE);
}
if (data->current_event && data->current_event->type != QUERY_EVENT)
if (data->current_event && data->current_event->type != QUERY_EVENT &&
data->current_event->type != APP_QUIT_REQUESTED)
event_mask = 0; /* don't process nested events */
if (process_events(data->queue, event_mask)) ret = count - 1;

View File

@ -149,6 +149,7 @@ static inline RECT rect_from_cgrect(CGRect cgrect)
extern void macdrv_window_got_focus(HWND hwnd, const macdrv_event *event) DECLSPEC_HIDDEN;
extern void macdrv_window_lost_focus(HWND hwnd, const macdrv_event *event) DECLSPEC_HIDDEN;
extern void macdrv_app_deactivated(void) DECLSPEC_HIDDEN;
extern void macdrv_app_quit_requested(const macdrv_event *event) DECLSPEC_HIDDEN;
extern void macdrv_window_did_minimize(HWND hwnd) DECLSPEC_HIDDEN;
extern void macdrv_window_did_unminimize(HWND hwnd) DECLSPEC_HIDDEN;

View File

@ -134,6 +134,7 @@
extern void macdrv_window_rejected_focus(const struct macdrv_event *event) DECLSPEC_HIDDEN;
extern void macdrv_beep(void) DECLSPEC_HIDDEN;
extern void macdrv_set_application_icon(CFArrayRef images) DECLSPEC_HIDDEN;
extern void macdrv_quit_reply(int reply) DECLSPEC_HIDDEN;
/* cursor */
@ -153,6 +154,7 @@ extern int macdrv_set_display_mode(const struct macdrv_display* display,
/* event */
enum {
APP_DEACTIVATED,
APP_QUIT_REQUESTED,
DISPLAYS_CHANGED,
KEY_PRESS,
KEY_RELEASE,
@ -172,6 +174,13 @@ extern int macdrv_set_display_mode(const struct macdrv_display* display,
NUM_EVENT_TYPES
};
enum {
QUIT_REASON_NONE,
QUIT_REASON_LOGOUT,
QUIT_REASON_RESTART,
QUIT_REASON_SHUTDOWN,
};
typedef uint32_t macdrv_event_mask;
typedef struct macdrv_event {
@ -180,6 +189,9 @@ extern int macdrv_set_display_mode(const struct macdrv_display* display,
int type;
macdrv_window window;
union {
struct {
int reason;
} app_quit_requested;
struct {
int activating;
} displays_changed;

View File

@ -1637,3 +1637,170 @@ void macdrv_window_did_unminimize(HWND hwnd)
done:
release_win_data(data);
}
struct quit_info {
HWND *wins;
UINT capacity;
UINT count;
UINT done;
DWORD flags;
BOOL result;
BOOL replied;
};
static BOOL CALLBACK get_process_windows(HWND hwnd, LPARAM lp)
{
struct quit_info *qi = (struct quit_info*)lp;
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (pid == GetCurrentProcessId())
{
if (qi->count >= qi->capacity)
{
UINT new_cap = qi->capacity * 2;
HWND *new_wins = HeapReAlloc(GetProcessHeap(), 0, qi->wins,
new_cap * sizeof(*qi->wins));
if (!new_wins) return FALSE;
qi->wins = new_wins;
qi->capacity = new_cap;
}
qi->wins[qi->count++] = hwnd;
}
return TRUE;
}
static void CALLBACK quit_callback(HWND hwnd, UINT msg, ULONG_PTR data, LRESULT result)
{
struct quit_info *qi = (struct quit_info*)data;
qi->done++;
if (msg == WM_QUERYENDSESSION)
{
TRACE("got WM_QUERYENDSESSION result %ld from win %p (%u of %u done)\n", result,
hwnd, qi->done, qi->count);
if (!result && qi->result)
{
qi->result = FALSE;
/* On the first FALSE from WM_QUERYENDSESSION, we already know the
ultimate reply. Might as well tell Cocoa now. */
if (!qi->replied)
{
qi->replied = TRUE;
TRACE("giving quit reply %d\n", qi->result);
macdrv_quit_reply(qi->result);
}
}
if (qi->done >= qi->count)
{
UINT i;
qi->done = 0;
for (i = 0; i < qi->count; i++)
{
TRACE("sending WM_ENDSESSION to win %p result %d flags 0x%08x\n", qi->wins[i],
qi->result, qi->flags);
if (!SendMessageCallbackW(qi->wins[i], WM_ENDSESSION, qi->result, qi->flags,
quit_callback, (ULONG_PTR)qi))
{
WARN("failed to send WM_ENDSESSION to win %p; error 0x%08x\n",
qi->wins[i], GetLastError());
quit_callback(qi->wins[i], WM_ENDSESSION, (ULONG_PTR)qi, 0);
}
}
}
}
else /* WM_ENDSESSION */
{
TRACE("finished WM_ENDSESSION for win %p (%u of %u done)\n", hwnd, qi->done, qi->count);
if (qi->done >= qi->count)
{
if (!qi->replied)
{
TRACE("giving quit reply %d\n", qi->result);
macdrv_quit_reply(qi->result);
}
TRACE("%sterminating process\n", qi->result ? "" : "not ");
if (qi->result)
TerminateProcess(GetCurrentProcess(), 0);
HeapFree(GetProcessHeap(), 0, qi->wins);
HeapFree(GetProcessHeap(), 0, qi);
}
}
}
/***********************************************************************
* macdrv_app_quit_requested
*
* Handler for APP_QUIT_REQUESTED events.
*/
void macdrv_app_quit_requested(const macdrv_event *event)
{
struct quit_info *qi;
UINT i;
TRACE("reason %d\n", event->app_quit_requested.reason);
qi = HeapAlloc(GetProcessHeap(), 0, sizeof(*qi));
if (!qi)
goto fail;
qi->capacity = 32;
qi->wins = HeapAlloc(GetProcessHeap(), 0, qi->capacity * sizeof(*qi->wins));
qi->count = qi->done = 0;
if (!qi->wins || !EnumWindows(get_process_windows, (LPARAM)qi))
goto fail;
switch (event->app_quit_requested.reason)
{
case QUIT_REASON_LOGOUT:
default:
qi->flags = ENDSESSION_LOGOFF;
break;
case QUIT_REASON_RESTART:
case QUIT_REASON_SHUTDOWN:
qi->flags = 0;
break;
}
qi->result = TRUE;
qi->replied = FALSE;
for (i = 0; i < qi->count; i++)
{
TRACE("sending WM_QUERYENDSESSION to win %p\n", qi->wins[i]);
if (!SendMessageCallbackW(qi->wins[i], WM_QUERYENDSESSION, 0, qi->flags,
quit_callback, (ULONG_PTR)qi))
{
WARN("failed to send WM_QUERYENDSESSION to win %p; error 0x%08x; assuming refusal\n",
qi->wins[i], GetLastError());
quit_callback(qi->wins[i], WM_QUERYENDSESSION, (ULONG_PTR)qi, FALSE);
}
}
/* quit_callback() will clean up qi */
return;
fail:
WARN("failed to allocate window list\n");
if (qi)
{
HeapFree(GetProcessHeap(), 0, qi->wins);
HeapFree(GetProcessHeap(), 0, qi);
}
macdrv_quit_reply(FALSE);
}