summaryrefslogtreecommitdiff
path: root/src/eggsmclient-win32.c
blob: c037a15a269c1521219768d72ec8d4cc81c48877 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
 * Copyright (C) 2007 Novell, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/* EggSMClientWin32
 *
 * For details on the Windows XP logout process, see:
 * http://msdn.microsoft.com/en-us/library/aa376876.aspx.
 *
 * Vista adds some new APIs which EggSMClient does not make use of; see
 * http://msdn.microsoft.com/en-us/library/ms700677(VS.85).aspx
 *
 * When shutting down, Windows sends every top-level window a
 * WM_QUERYENDSESSION event, which the application must respond to
 * synchronously, saying whether or not it will quit. To avoid main
 * loop re-entrancy problems (and to avoid having to muck about too
 * much with the guts of the gdk-win32 main loop), we watch for this
 * event in a separate thread, which then signals the main thread and
 * waits for the main thread to handle the event. Since we don't want
 * to require g_thread_init() to be called, we do this all using
 * Windows-specific thread methods.
 *
 * After the application handles the WM_QUERYENDSESSION event,
 * Windows then sends it a WM_ENDSESSION event with a TRUE or FALSE
 * parameter indicating whether the session is or is not actually
 * going to end now. We handle this from the other thread as well.
 *
 * As mentioned above, Vista introduces several additional new APIs
 * that don't fit into the (current) EggSMClient API. Windows also has
 * an entirely separate shutdown-notification scheme for non-GUI apps,
 * which we also don't handle here.
 */

#include "config.h"

#include "eggsmclient-private.h"
#include <gdk/gdk.h>

#define WIN32_LEAN_AND_MEAN
#define UNICODE
#include <windows.h>
#include <process.h>

#define EGG_TYPE_SM_CLIENT_WIN32            (egg_sm_client_win32_get_type ())
#define EGG_SM_CLIENT_WIN32(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32))
#define EGG_SM_CLIENT_WIN32_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class))
#define EGG_IS_SM_CLIENT_WIN32(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_WIN32))
#define EGG_IS_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_WIN32))
#define EGG_SM_CLIENT_WIN32_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class))

typedef struct _EggSMClientWin32        EggSMClientWin32;
typedef struct _EggSMClientWin32Class   EggSMClientWin32Class;

struct _EggSMClientWin32
{
	EggSMClient parent;

	HANDLE message_event, response_event;

	volatile GSourceFunc event;
	volatile gboolean will_quit;
};

struct _EggSMClientWin32Class
{
	EggSMClientClass parent_class;

};

static void     sm_client_win32_startup (EggSMClient *client,
        const char  *client_id);
static void     sm_client_win32_will_quit (EggSMClient *client,
        gboolean     will_quit);
static gboolean sm_client_win32_end_session (EggSMClient         *client,
        EggSMClientEndStyle  style,
        gboolean  request_confirmation);

static GSource *g_win32_handle_source_add (HANDLE handle, GSourceFunc callback,
        gpointer user_data);
static gboolean got_message (gpointer user_data);
static void sm_client_thread (gpointer data);

G_DEFINE_TYPE (EggSMClientWin32, egg_sm_client_win32, EGG_TYPE_SM_CLIENT)

static void
egg_sm_client_win32_init (EggSMClientWin32 *win32)
{
	;
}

static void
egg_sm_client_win32_class_init (EggSMClientWin32Class *klass)
{
	EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);

	sm_client_class->startup             = sm_client_win32_startup;
	sm_client_class->will_quit           = sm_client_win32_will_quit;
	sm_client_class->end_session         = sm_client_win32_end_session;
}

EggSMClient *
egg_sm_client_win32_new (void)
{
	return g_object_new (EGG_TYPE_SM_CLIENT_WIN32, NULL);
}

static void
sm_client_win32_startup (EggSMClient *client,
                         const char  *client_id)
{
	EggSMClientWin32 *win32 = (EggSMClientWin32 *)client;

	win32->message_event = CreateEvent (NULL, FALSE, FALSE, NULL);
	win32->response_event = CreateEvent (NULL, FALSE, FALSE, NULL);
	g_win32_handle_source_add (win32->message_event, got_message, win32);
	_beginthread (sm_client_thread, 0, client);
}

static void
sm_client_win32_will_quit (EggSMClient *client,
                           gboolean     will_quit)
{
	EggSMClientWin32 *win32 = (EggSMClientWin32 *)client;

	win32->will_quit = will_quit;
	SetEvent (win32->response_event);
}

static gboolean
sm_client_win32_end_session (EggSMClient         *client,
                             EggSMClientEndStyle  style,
                             gboolean             request_confirmation)
{
	UINT uFlags = EWX_LOGOFF;

	switch (style)
	{
	case EGG_SM_CLIENT_END_SESSION_DEFAULT:
	case EGG_SM_CLIENT_LOGOUT:
		uFlags = EWX_LOGOFF;
		break;
	case EGG_SM_CLIENT_REBOOT:
		uFlags = EWX_REBOOT;
		break;
	case EGG_SM_CLIENT_SHUTDOWN:
		uFlags = EWX_POWEROFF;
		break;
	}

	/* There's no way to make ExitWindowsEx() show a logout dialog, so
	 * we ignore @request_confirmation.
	 */

#ifdef SHTDN_REASON_FLAG_PLANNED
	ExitWindowsEx (uFlags, SHTDN_REASON_FLAG_PLANNED);
#else
	ExitWindowsEx (uFlags, 0);
#endif

	return TRUE;
}


/* callbacks from logout-listener thread */

static gboolean
emit_quit_requested (gpointer smclient)
{
	gdk_threads_enter ();
	egg_sm_client_quit_requested (smclient);
	gdk_threads_leave ();

	return FALSE;
}

static gboolean
emit_quit (gpointer smclient)
{
	EggSMClientWin32 *win32 = smclient;

	gdk_threads_enter ();
	egg_sm_client_quit (smclient);
	gdk_threads_leave ();

	SetEvent (win32->response_event);
	return FALSE;
}

static gboolean
emit_quit_cancelled (gpointer smclient)
{
	EggSMClientWin32 *win32 = smclient;

	gdk_threads_enter ();
	egg_sm_client_quit_cancelled (smclient);
	gdk_threads_leave ();

	SetEvent (win32->response_event);
	return FALSE;
}

static gboolean
got_message (gpointer smclient)
{
	EggSMClientWin32 *win32 = smclient;

	win32->event (win32);
	return TRUE;
}

/* Windows HANDLE GSource */

typedef struct
{
	GSource source;
	GPollFD pollfd;
} GWin32HandleSource;

static gboolean
g_win32_handle_source_prepare (GSource *source, gint *timeout)
{
	*timeout = -1;
	return FALSE;
}

static gboolean
g_win32_handle_source_check (GSource *source)
{
	GWin32HandleSource *hsource = (GWin32HandleSource *)source;

	return hsource->pollfd.revents;
}

static gboolean
g_win32_handle_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
{
	return (*callback) (user_data);
}

static void
g_win32_handle_source_finalize (GSource *source)
{
	;
}

GSourceFuncs g_win32_handle_source_funcs =
{
	g_win32_handle_source_prepare,
	g_win32_handle_source_check,
	g_win32_handle_source_dispatch,
	g_win32_handle_source_finalize
};

static GSource *
g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, gpointer user_data)
{
	GWin32HandleSource *hsource;
	GSource *source;

	source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource));
	hsource = (GWin32HandleSource *)source;
	hsource->pollfd.fd = (int)handle;
	hsource->pollfd.events = G_IO_IN;
	hsource->pollfd.revents = 0;
	g_source_add_poll (source, &hsource->pollfd);

	g_source_set_callback (source, callback, user_data, NULL);
	g_source_attach (source, NULL);
	return source;
}

/* logout-listener thread */

LRESULT CALLBACK
sm_client_win32_window_procedure (HWND   hwnd,
                                  UINT   message,
                                  WPARAM wParam,
                                  LPARAM lParam)
{
	EggSMClientWin32 *win32 =
	    (EggSMClientWin32 *)GetWindowLongPtr (hwnd, GWLP_USERDATA);

	switch (message)
	{
	case WM_QUERYENDSESSION:
		win32->event = emit_quit_requested;
		SetEvent (win32->message_event);

		WaitForSingleObject (win32->response_event, INFINITE);
		return win32->will_quit;

	case WM_ENDSESSION:
		if (wParam)
		{
			/* The session is ending */
			win32->event = emit_quit;
		}
		else
		{
			/* Nope, the session *isn't* ending */
			win32->event = emit_quit_cancelled;
		}

		SetEvent (win32->message_event);
		WaitForSingleObject (win32->response_event, INFINITE);

		return 0;

	default:
		return DefWindowProc (hwnd, message, wParam, lParam);
	}
}

static void
sm_client_thread (gpointer smclient)
{
	HINSTANCE instance;
	WNDCLASSEXW wcl;
	ATOM klass;
	HWND window;
	MSG msg;

	instance = GetModuleHandle (NULL);

	memset (&wcl, 0, sizeof (WNDCLASSEX));
	wcl.cbSize = sizeof (WNDCLASSEX);
	wcl.lpfnWndProc = sm_client_win32_window_procedure;
	wcl.hInstance = instance;
	wcl.lpszClassName = L"EggSmClientWindow";
	klass = RegisterClassEx (&wcl);

	window = CreateWindowEx (0, MAKEINTRESOURCE (klass),
	                         L"EggSmClientWindow", 0,
	                         10, 10, 50, 50, GetDesktopWindow (),
	                         NULL, instance, NULL);
	SetWindowLongPtr (window, GWLP_USERDATA, (LONG_PTR)smclient);

	/* main loop */
	while (GetMessage (&msg, NULL, 0, 0))
		DispatchMessage (&msg);
}