1 // AppletWindow.cc --- Applet info Window
2 //
3 // Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Rob Caelers & Raymond Penners
4 // All rights reserved.
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 
19 #include "preinclude.h"
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "nls.h"
26 #include "debug.hh"
27 
28 #include "W32AppletWindow.hh"
29 #include "TimerBoxControl.hh"
30 
31 #if defined(interface)
32 #undef interface
33 #endif
34 
35 #include "Applet.hh"
36 #include "GUI.hh"
37 #include "Menus.hh"
38 
W32AppletWindow()39 W32AppletWindow::W32AppletWindow()
40 {
41   TRACE_ENTER("W32AppletWindow::W32AppletWindow");
42 
43   memset(&local_heartbeat_data, 0, sizeof(AppletHeartbeatData));
44   memset(&local_menu_data, 0, sizeof(AppletMenuData));
45   memset(&heartbeat_data, 0, sizeof(AppletHeartbeatData));
46   memset(&menu_data, 0, sizeof(AppletMenuData));
47 
48   thread_id = 0;
49   thread_handle = NULL;
50   timer_box_view = this;
51   applet_window = NULL;
52   heartbeat_data.enabled = false;
53   local_applet_window = NULL;
54   init_menu(NULL);
55 
56   ::InitializeCriticalSection(&heartbeat_data_lock);
57   thread_abort_event = ::CreateEvent(NULL, FALSE, FALSE, NULL);
58   heartbeat_data_event = ::CreateEvent(NULL, FALSE, FALSE, NULL);
59 
60   // Intentionally last line, as this one calls W32AW::set_enabled(), e.g.
61   timer_box_control = new TimerBoxControl("applet", *this);
62 
63   TRACE_EXIT();
64 }
65 
~W32AppletWindow()66 W32AppletWindow::~W32AppletWindow()
67 {
68 	TRACE_ENTER("W32AppletWindow::~W32AppletWindow");
69 
70 	/* before this instance is destroyed we signal and wait for its worker thread to terminate. this
71 	isn't ideal because the gui will be blocked while we wait for termination if this destructor is
72 	called from the main thread. current conditions are acceptable, however. 2/12/2012
73 	*/
74 	heartbeat_data.enabled = false;
75 	SetEvent( thread_abort_event );
76 	if( thread_handle )
77 	{
78 		WaitForSingleObject( thread_handle, INFINITE );
79 		CloseHandle( thread_handle );
80 	}
81 
82 	if( thread_abort_event )
83 		CloseHandle( thread_abort_event );
84 
85 	if( heartbeat_data_event )
86 		CloseHandle( heartbeat_data_event );
87 
88 	DeleteCriticalSection(&heartbeat_data_lock);
89 
90 	delete timer_box_control;
91 
92 	TRACE_EXIT();
93 }
94 
95 
96 static HWND
RecursiveFindWindow(HWND hwnd,LPCTSTR lpClassName)97 RecursiveFindWindow(HWND hwnd, LPCTSTR lpClassName)
98 {
99   static char buf[80];
100   int num = GetClassName(hwnd, buf, sizeof(buf)-1);
101   buf[num] = 0;
102   HWND ret = NULL;
103 
104   if (! stricmp(lpClassName, buf))
105     {
106       ret =  hwnd;
107     }
108   else
109     {
110       HWND child = FindWindowEx(hwnd, 0, NULL, NULL);
111       while (child != NULL)
112         {
113           ret = RecursiveFindWindow(child, lpClassName);
114           if (ret)
115             {
116               break;
117             }
118           child = FindWindowEx(hwnd, child, NULL, NULL);
119         }
120     }
121   return ret;
122 }
123 
124 
125 
126 void
set_slot(BreakId id,int slot)127 W32AppletWindow::set_slot(BreakId id, int slot)
128 {
129   TRACE_ENTER_MSG("W32AppletWindow::set_slot", int(id) << ", " << slot);
130   heartbeat_data.slots[slot] = (short) id;
131   TRACE_EXIT();
132 }
133 
134 void
set_time_bar(BreakId id,std::string text,ITimeBar::ColorId primary_color,int primary_val,int primary_max,ITimeBar::ColorId secondary_color,int secondary_val,int secondary_max)135 W32AppletWindow::set_time_bar(BreakId id,
136                               std::string text,
137                               ITimeBar::ColorId primary_color,
138                               int primary_val, int primary_max,
139                               ITimeBar::ColorId secondary_color,
140                               int secondary_val, int secondary_max)
141 {
142   TRACE_ENTER_MSG("W32AppletWindow::set_time_bar", int(id) << "=" << text);
143   strncpy(heartbeat_data.bar_text[id], text.c_str(), APPLET_BAR_TEXT_MAX_LENGTH-1);
144   heartbeat_data.bar_text[id][APPLET_BAR_TEXT_MAX_LENGTH-1] = '\0';
145   heartbeat_data.bar_primary_color[id] = primary_color;
146   heartbeat_data.bar_primary_val[id] = primary_val;
147   heartbeat_data.bar_primary_max[id] = primary_max;
148   heartbeat_data.bar_secondary_color[id] = secondary_color;
149   heartbeat_data.bar_secondary_val[id] = secondary_val;
150   heartbeat_data.bar_secondary_max[id] = secondary_max;
151   TRACE_EXIT();
152 }
153 
154 void
update_view()155 W32AppletWindow::update_view()
156 {
157   TRACE_ENTER("W32AppletWindow::update_view");
158 
159   BOOL entered = ::TryEnterCriticalSection(&heartbeat_data_lock);
160   if (entered)
161   {
162     memcpy(&local_heartbeat_data, &heartbeat_data, sizeof(AppletHeartbeatData));
163 
164     update_applet_window();
165 
166     if (!menu_sent)
167       {
168         memcpy(&local_menu_data, &menu_data, sizeof(AppletMenuData));
169         local_applet_window = applet_window;
170         menu_sent = true;
171       }
172 
173     SetEvent(heartbeat_data_event);
174     ::LeaveCriticalSection(&heartbeat_data_lock);
175   }
176 
177   TRACE_EXIT();
178 }
179 
180 void
update_menu()181 W32AppletWindow::update_menu()
182 {
183   TRACE_ENTER("W32AppletWindow::update_menu");
184   if (local_applet_window != NULL)
185     {
186       TRACE_MSG("sending");
187 
188       COPYDATASTRUCT msg;
189       msg.dwData = APPLET_MESSAGE_MENU;
190       msg.cbData = sizeof(AppletMenuData);
191       msg.lpData = &local_menu_data;
192       SendMessage(local_applet_window, WM_COPYDATA, 0, (LPARAM) &msg);
193     }
194   TRACE_EXIT();
195 }
196 
197 void
update_time_bars()198 W32AppletWindow::update_time_bars()
199 {
200   TRACE_ENTER("W32AppletWindow::update_time_bars");
201   if (local_applet_window != NULL)
202     {
203       COPYDATASTRUCT msg;
204       msg.dwData = APPLET_MESSAGE_HEARTBEAT;
205       msg.cbData = sizeof(AppletHeartbeatData);
206       msg.lpData = &local_heartbeat_data;
207       TRACE_MSG("sending: enabled=" << local_heartbeat_data.enabled);
208       for (size_t i = 0; i < BREAK_ID_SIZEOF; i++)
209         {
210           TRACE_MSG("sending: slots[]=" << local_heartbeat_data.slots[i]);
211         }
212       SendMessage(local_applet_window, WM_COPYDATA, 0, (LPARAM) &msg);
213     }
214   TRACE_EXIT();
215 }
216 
217 void
update_applet_window()218 W32AppletWindow::update_applet_window()
219 {
220   TRACE_ENTER("W32AppletWindow::get_applet_window");
221   HWND previous_applet_window = applet_window;
222   if (applet_window == NULL || !IsWindow(applet_window))
223     {
224       HWND taskbar = FindWindow("Shell_TrayWnd",NULL);
225       applet_window = RecursiveFindWindow(taskbar, APPLET_WINDOW_CLASS_NAME);
226       menu_sent = false;
227     }
228 
229   if (previous_applet_window == NULL && applet_window != NULL)
230     {
231       state_changed_signal.emit(AppletWindow::APPLET_STATE_ACTIVE);
232     }
233   else if (previous_applet_window != NULL && applet_window == NULL)
234     {
235       state_changed_signal.emit(AppletWindow::APPLET_STATE_DISABLED);
236     }
237 
238   TRACE_EXIT();
239 }
240 
241 
242 void
set_enabled(bool enabled)243 W32AppletWindow::set_enabled( bool enabled )
244 {
245 	TRACE_ENTER_MSG( "W32AppletWindow::set_enabled", enabled );
246 	DWORD thread_exit_code = 0;
247 
248 	heartbeat_data.enabled = enabled;
249 
250 	if( !enabled )
251 		return;
252 
253 	if( thread_id
254 		&& thread_handle
255 		&& GetExitCodeThread( thread_handle, &thread_exit_code )
256 		&& ( thread_exit_code == STILL_ACTIVE )
257 	)
258 		return;
259 
260 	if( !thread_id )
261 	{
262 		// if there is no id but a handle then this instance's worker thread has exited or is exiting.
263 		if( thread_handle )
264 			CloseHandle( thread_handle );
265 
266 		thread_id = 0;
267 		SetLastError( 0 );
268 		thread_handle =
269 			(HANDLE)_beginthreadex( NULL, 0, run_event_pipe_static, this, 0, (unsigned int *)&thread_id );
270 
271 		if( !thread_handle || !thread_id )
272 		{
273 			TRACE_MSG( "Thread could not be created. GetLastError : " << GetLastError() );
274 		}
275 	}
276 
277 	TRACE_EXIT();
278 }
279 
280 
281 unsigned __stdcall
run_event_pipe_static(void * param)282 W32AppletWindow::run_event_pipe_static( void *param )
283 {
284   W32AppletWindow *pThis = (W32AppletWindow *) param;
285   pThis->run_event_pipe();
286   // invalidate the id to signal the thread is exiting
287   pThis->thread_id = 0;
288   return (DWORD) 0;
289 }
290 
291 
292 void
run_event_pipe()293 W32AppletWindow::run_event_pipe()
294 {
295 	const DWORD current_thread_id = GetCurrentThreadId();
296 
297 	TRACE_ENTER_MSG( "W32AppletWindow::run_event_pipe [ id: ", current_thread_id << " ]" );
298 
299 	while( thread_id == current_thread_id )
300 	{
301 		/* JS: thread_abort_event must be first in the array of events.
302 		the index returned by WaitForMultipleObjectsEx() corresponds to the first
303 		signaled event in the array if more than one is signaled
304 		*/
305 		HANDLE events[ 2 ] = { thread_abort_event, heartbeat_data_event };
306 		int const events_count = ( sizeof( events ) / sizeof( events[ 0 ] ) );
307 
308 		DWORD wait_result = WaitForMultipleObjectsEx( events_count, events, FALSE, INFINITE, FALSE );
309 
310 		if( ( wait_result == WAIT_FAILED ) || ( wait_result == ( WAIT_OBJECT_0 + 0 ) ) )
311 			break;
312 
313 		if( heartbeat_data.enabled && ( wait_result == ( WAIT_OBJECT_0 + 1 ) ) )
314 		{
315 			EnterCriticalSection( &heartbeat_data_lock );
316 
317 			update_time_bars();
318 			update_menu();
319 
320 			LeaveCriticalSection( &heartbeat_data_lock );
321 		}
322 	}
323 
324 	TRACE_EXIT();
325 }
326 
327 
328 void
init_menu(HWND hwnd)329 W32AppletWindow::init_menu(HWND hwnd)
330 {
331   menu_data.num_items = 0;
332   menu_sent = false;
333 
334   /*
335     As noted in frontend/win32/applet/include/applet.hh:
336     We pass the command_window HWND as a LONG for compatibility.
337   */
338   menu_data.command_window = HandleToLong( hwnd );
339 }
340 
341 
342 void
add_menu(const char * text,short cmd,int flags)343 W32AppletWindow::add_menu(const char *text, short cmd, int flags)
344 {
345   AppletMenuItemData *d = &menu_data.items[menu_data.num_items++];
346   d->command = cmd;
347   strcpy(d->text, text);
348   d->flags = flags;
349 }
350 
351 
352 AppletWindow::AppletState
activate_applet()353 W32AppletWindow::activate_applet()
354 {
355   update_applet_window();
356   return applet_window != NULL ? APPLET_STATE_ACTIVE : APPLET_STATE_DISABLED;
357 }
358 
359 
360 void
deactivate_applet()361 W32AppletWindow::deactivate_applet()
362 {
363 }
364 
365 
366 void
set_geometry(Orientation orientation,int size)367 W32AppletWindow::set_geometry(Orientation orientation, int size)
368 {
369   (void) orientation;
370   (void) size;
371 }
372 
373 
374 bool
on_applet_command(int command)375 W32AppletWindow::on_applet_command(int command)
376 {
377   TRACE_ENTER_MSG("W32AppletWindow::on_applet_command", command);
378   IGUI *gui = GUI::get_instance();
379   Menus *menus = gui->get_menus();
380   menus->applet_command(command);
381   TRACE_EXIT();
382   return false;
383 }
384 
385 
386 GdkFilterReturn
win32_filter_func(void * xevent,GdkEvent * event)387 W32AppletWindow::win32_filter_func (void     *xevent,
388                                     GdkEvent *event)
389 {
390   (void) event;
391   MSG *msg = (MSG *) xevent;
392   GdkFilterReturn ret = GDK_FILTER_CONTINUE;
393 
394   switch (msg->message)
395     {
396     case WM_USER:
397       {
398         sigc::slot<bool> my_slot = sigc::bind(sigc::mem_fun(*this, &W32AppletWindow::on_applet_command),
399                                               (int) msg->wParam);
400         Glib::signal_idle().connect(my_slot);
401 
402         ret = GDK_FILTER_REMOVE;
403       }
404       break;
405 
406     case WM_USER + 1:
407       {
408         timer_box_control->force_cycle();
409         ret = GDK_FILTER_REMOVE;
410       }
411       break;
412     }
413   return ret;
414 }
415