1 /*
2  * Copyright (C) 2007 Novell, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 
20 /* EggSMClientWin32
21  *
22  * For details on the Windows XP logout process, see:
23  * http://msdn.microsoft.com/en-us/library/aa376876.aspx.
24  *
25  * Vista adds some new APIs which EggSMClient does not make use of; see
26  * http://msdn.microsoft.com/en-us/library/ms700677(VS.85).aspx
27  *
28  * When shutting down, Windows sends every top-level window a
29  * WM_QUERYENDSESSION event, which the application must respond to
30  * synchronously, saying whether or not it will quit. To avoid main
31  * loop re-entrancy problems (and to avoid having to muck about too
32  * much with the guts of the gdk-win32 main loop), we watch for this
33  * event in a separate thread, which then signals the main thread and
34  * waits for the main thread to handle the event. Since we don't want
35  * to require g_thread_init() to be called, we do this all using
36  * Windows-specific thread methods.
37  *
38  * After the application handles the WM_QUERYENDSESSION event,
39  * Windows then sends it a WM_ENDSESSION event with a TRUE or FALSE
40  * parameter indicating whether the session is or is not actually
41  * going to end now. We handle this from the other thread as well.
42  *
43  * As mentioned above, Vista introduces several additional new APIs
44  * that don't fit into the (current) EggSMClient API. Windows also has
45  * an entirely separate shutdown-notification scheme for non-GUI apps,
46  * which we also don't handle here.
47  */
48 
49 #include "config.h"
50 
51 #include "eggsmclient-private.h"
52 #include <gdk/gdk.h>
53 
54 #define WIN32_LEAN_AND_MEAN
55 #define UNICODE
56 #include <windows.h>
57 #include <process.h>
58 
59 #define EGG_TYPE_SM_CLIENT_WIN32            (egg_sm_client_win32_get_type ())
60 #define EGG_SM_CLIENT_WIN32(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32))
61 #define EGG_SM_CLIENT_WIN32_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class))
62 #define EGG_IS_SM_CLIENT_WIN32(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_WIN32))
63 #define EGG_IS_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_WIN32))
64 #define EGG_SM_CLIENT_WIN32_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class))
65 
66 typedef struct _EggSMClientWin32        EggSMClientWin32;
67 typedef struct _EggSMClientWin32Class   EggSMClientWin32Class;
68 
69 struct _EggSMClientWin32 {
70   EggSMClient parent;
71 
72   HANDLE message_event, response_event;
73 
74   volatile GSourceFunc event;
75   volatile gboolean will_quit;
76 };
77 
78 struct _EggSMClientWin32Class
79 {
80   EggSMClientClass parent_class;
81 
82 };
83 
84 static void     sm_client_win32_startup (EggSMClient *client,
85 					 const char  *client_id);
86 static void     sm_client_win32_will_quit (EggSMClient *client,
87 					   gboolean     will_quit);
88 static gboolean sm_client_win32_end_session (EggSMClient         *client,
89 					     EggSMClientEndStyle  style,
90 					     gboolean  request_confirmation);
91 
92 static GSource *g_win32_handle_source_add (HANDLE handle, GSourceFunc callback,
93 					gpointer user_data);
94 static gboolean got_message (gpointer user_data);
95 static void sm_client_thread (gpointer data);
96 
G_DEFINE_TYPE(EggSMClientWin32,egg_sm_client_win32,EGG_TYPE_SM_CLIENT)97 G_DEFINE_TYPE (EggSMClientWin32, egg_sm_client_win32, EGG_TYPE_SM_CLIENT)
98 
99 static void
100 egg_sm_client_win32_init (EggSMClientWin32 *win32)
101 {
102   ;
103 }
104 
105 static void
egg_sm_client_win32_class_init(EggSMClientWin32Class * klass)106 egg_sm_client_win32_class_init (EggSMClientWin32Class *klass)
107 {
108   EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
109 
110   sm_client_class->startup             = sm_client_win32_startup;
111   sm_client_class->will_quit           = sm_client_win32_will_quit;
112   sm_client_class->end_session         = sm_client_win32_end_session;
113 }
114 
115 EggSMClient *
egg_sm_client_win32_new(void)116 egg_sm_client_win32_new (void)
117 {
118   return g_object_new (EGG_TYPE_SM_CLIENT_WIN32, NULL);
119 }
120 
121 static void
sm_client_win32_startup(EggSMClient * client,const char * client_id)122 sm_client_win32_startup (EggSMClient *client,
123 			 const char  *client_id)
124 {
125   EggSMClientWin32 *win32 = (EggSMClientWin32 *)client;
126 
127   win32->message_event = CreateEvent (NULL, FALSE, FALSE, NULL);
128   win32->response_event = CreateEvent (NULL, FALSE, FALSE, NULL);
129   g_win32_handle_source_add (win32->message_event, got_message, win32);
130   _beginthread (sm_client_thread, 0, client);
131 }
132 
133 static void
sm_client_win32_will_quit(EggSMClient * client,gboolean will_quit)134 sm_client_win32_will_quit (EggSMClient *client,
135 			   gboolean     will_quit)
136 {
137   EggSMClientWin32 *win32 = (EggSMClientWin32 *)client;
138 
139   win32->will_quit = will_quit;
140   SetEvent (win32->response_event);
141 }
142 
143 static gboolean
sm_client_win32_end_session(EggSMClient * client,EggSMClientEndStyle style,gboolean request_confirmation)144 sm_client_win32_end_session (EggSMClient         *client,
145 			     EggSMClientEndStyle  style,
146 			     gboolean             request_confirmation)
147 {
148   UINT uFlags = EWX_LOGOFF;
149 
150   switch (style)
151     {
152     case EGG_SM_CLIENT_END_SESSION_DEFAULT:
153     case EGG_SM_CLIENT_LOGOUT:
154       uFlags = EWX_LOGOFF;
155       break;
156     case EGG_SM_CLIENT_REBOOT:
157       uFlags = EWX_REBOOT;
158       break;
159     case EGG_SM_CLIENT_SHUTDOWN:
160       uFlags = EWX_POWEROFF;
161       break;
162     }
163 
164   /* There's no way to make ExitWindowsEx() show a logout dialog, so
165    * we ignore @request_confirmation.
166    */
167 
168 #ifdef SHTDN_REASON_FLAG_PLANNED
169   ExitWindowsEx (uFlags, SHTDN_REASON_FLAG_PLANNED);
170 #else
171   ExitWindowsEx (uFlags, 0);
172 #endif
173 
174   return TRUE;
175 }
176 
177 
178 /* callbacks from logout-listener thread */
179 
180 static gboolean
emit_quit_requested(gpointer smclient)181 emit_quit_requested (gpointer smclient)
182 {
183   gdk_threads_enter ();
184   egg_sm_client_quit_requested (smclient);
185   gdk_threads_leave ();
186 
187   return FALSE;
188 }
189 
190 static gboolean
emit_quit(gpointer smclient)191 emit_quit (gpointer smclient)
192 {
193   EggSMClientWin32 *win32 = smclient;
194 
195   gdk_threads_enter ();
196   egg_sm_client_quit (smclient);
197   gdk_threads_leave ();
198 
199   SetEvent (win32->response_event);
200   return FALSE;
201 }
202 
203 static gboolean
emit_quit_cancelled(gpointer smclient)204 emit_quit_cancelled (gpointer smclient)
205 {
206   EggSMClientWin32 *win32 = smclient;
207 
208   gdk_threads_enter ();
209   egg_sm_client_quit_cancelled (smclient);
210   gdk_threads_leave ();
211 
212   SetEvent (win32->response_event);
213   return FALSE;
214 }
215 
216 static gboolean
got_message(gpointer smclient)217 got_message (gpointer smclient)
218 {
219   EggSMClientWin32 *win32 = smclient;
220 
221   win32->event (win32);
222   return TRUE;
223 }
224 
225 /* Windows HANDLE GSource */
226 
227 typedef struct {
228   GSource source;
229   GPollFD pollfd;
230 } GWin32HandleSource;
231 
232 static gboolean
g_win32_handle_source_prepare(GSource * source,gint * timeout)233 g_win32_handle_source_prepare (GSource *source, gint *timeout)
234 {
235   *timeout = -1;
236   return FALSE;
237 }
238 
239 static gboolean
g_win32_handle_source_check(GSource * source)240 g_win32_handle_source_check (GSource *source)
241 {
242   GWin32HandleSource *hsource = (GWin32HandleSource *)source;
243 
244   return hsource->pollfd.revents;
245 }
246 
247 static gboolean
g_win32_handle_source_dispatch(GSource * source,GSourceFunc callback,gpointer user_data)248 g_win32_handle_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
249 {
250   return (*callback) (user_data);
251 }
252 
253 static void
g_win32_handle_source_finalize(GSource * source)254 g_win32_handle_source_finalize (GSource *source)
255 {
256   ;
257 }
258 
259 GSourceFuncs g_win32_handle_source_funcs = {
260   g_win32_handle_source_prepare,
261   g_win32_handle_source_check,
262   g_win32_handle_source_dispatch,
263   g_win32_handle_source_finalize
264 };
265 
266 static GSource *
g_win32_handle_source_add(HANDLE handle,GSourceFunc callback,gpointer user_data)267 g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, gpointer user_data)
268 {
269   GWin32HandleSource *hsource;
270   GSource *source;
271 
272   source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource));
273   hsource = (GWin32HandleSource *)source;
274   hsource->pollfd.fd = (int)handle;
275   hsource->pollfd.events = G_IO_IN;
276   hsource->pollfd.revents = 0;
277   g_source_add_poll (source, &hsource->pollfd);
278 
279   g_source_set_callback (source, callback, user_data, NULL);
280   g_source_attach (source, NULL);
281   return source;
282 }
283 
284 /* logout-listener thread */
285 
286 LRESULT CALLBACK
sm_client_win32_window_procedure(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)287 sm_client_win32_window_procedure (HWND   hwnd,
288 				  UINT   message,
289 				  WPARAM wParam,
290 				  LPARAM lParam)
291 {
292   EggSMClientWin32 *win32 =
293     (EggSMClientWin32 *)GetWindowLongPtr (hwnd, GWLP_USERDATA);
294 
295   switch (message)
296     {
297     case WM_QUERYENDSESSION:
298       win32->event = emit_quit_requested;
299       SetEvent (win32->message_event);
300 
301       WaitForSingleObject (win32->response_event, INFINITE);
302       return win32->will_quit;
303 
304     case WM_ENDSESSION:
305       if (wParam)
306 	{
307 	  /* The session is ending */
308 	  win32->event = emit_quit;
309 	}
310       else
311 	{
312 	  /* Nope, the session *isn't* ending */
313 	  win32->event = emit_quit_cancelled;
314 	}
315 
316       SetEvent (win32->message_event);
317       WaitForSingleObject (win32->response_event, INFINITE);
318 
319       return 0;
320 
321     default:
322       return DefWindowProc (hwnd, message, wParam, lParam);
323     }
324 }
325 
326 static void
sm_client_thread(gpointer smclient)327 sm_client_thread (gpointer smclient)
328 {
329   HINSTANCE instance;
330   WNDCLASSEXW wcl;
331   ATOM klass;
332   HWND window;
333   MSG msg;
334 
335   instance = GetModuleHandle (NULL);
336 
337   memset (&wcl, 0, sizeof (WNDCLASSEX));
338   wcl.cbSize = sizeof (WNDCLASSEX);
339   wcl.lpfnWndProc = sm_client_win32_window_procedure;
340   wcl.hInstance = instance;
341   wcl.lpszClassName = L"EggSmClientWindow";
342   klass = RegisterClassEx (&wcl);
343 
344   window = CreateWindowEx (0, MAKEINTRESOURCE (klass),
345 			   L"EggSmClientWindow", 0,
346 			   10, 10, 50, 50, GetDesktopWindow (),
347 			   NULL, instance, NULL);
348   SetWindowLongPtr (window, GWLP_USERDATA, (LONG_PTR)smclient);
349 
350   /* main loop */
351   while (GetMessage (&msg, NULL, 0, 0))
352     DispatchMessage (&msg);
353 }
354