1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2003-2005 Imendio HB
4  * Copyright (C) 2002-2003 Richard Hult <richard@imendio.com>
5  * Copyright (C) 2002 CodeFactory AB
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public
18  * License along with this program; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #include <config.h>
24 #include <string.h>
25 #include <math.h>
26 #include <glib/gi18n.h>
27 #include <gdk/gdk.h>
28 #include <gdk/gdkx.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtk.h>
31 #include <gio/gio.h>
32 
33 #ifdef HAVE_APP_INDICATOR
34 #include <libappindicator/app-indicator.h>
35 #endif /* HAVE_APP_INDICATOR */
36 
37 #define MATE_DESKTOP_USE_UNSTABLE_API
38 #include <libmate-desktop/mate-desktop-utils.h>
39 
40 #include "drwright.h"
41 #include "drw-break-window.h"
42 #include "drw-monitor.h"
43 #include "drw-utils.h"
44 #include "drw-timer.h"
45 
46 #ifndef HAVE_APP_INDICATOR
47 #define BLINK_TIMEOUT        200
48 #define BLINK_TIMEOUT_MIN    120
49 #define BLINK_TIMEOUT_FACTOR 100
50 #endif /* HAVE_APP_INDICATOR */
51 
52 typedef enum {
53 	STATE_START,
54 	STATE_RUNNING,
55 	STATE_WARN,
56 	STATE_BREAK_SETUP,
57 	STATE_BREAK,
58 	STATE_BREAK_DONE_SETUP,
59 	STATE_BREAK_DONE
60 } DrwState;
61 
62 #ifdef HAVE_APP_INDICATOR
63 #define TYPING_MONITOR_ACTIVE_ICON "bar-green"
64 #define TYPING_MONITOR_ATTENTION_ICON "bar-red"
65 #endif /* HAVE_APP_INDICATOR */
66 
67 struct _DrWright {
68 	/* Widgets. */
69 	GtkWidget       *break_window;
70 	GList           *secondary_break_windows;
71 
72 	DrwMonitor      *monitor;
73 
74 	GtkUIManager    *ui_manager;
75 
76 	DrwState         state;
77 	DrwTimer        *timer;
78 	DrwTimer        *idle_timer;
79 
80 	gint             last_elapsed_time;
81 	gint             save_last_time;
82 
83 	/* Time settings. */
84 	gint             type_time;
85 	gint             break_time;
86 	gint             warn_time;
87 
88 	gboolean         enabled;
89 
90 	guint            clock_timeout_id;
91 #ifdef HAVE_APP_INDICATOR
92 	AppIndicator    *indicator;
93 #else
94 	guint            blink_timeout_id;
95 
96 	gboolean         blink_on;
97 
98 	GtkStatusIcon   *icon;
99 
100 	cairo_surface_t *neutral_bar;
101 	cairo_surface_t *red_bar;
102 	cairo_surface_t *green_bar;
103 	cairo_surface_t *disabled_bar;
104 	GdkPixbuf       *composite_bar;
105 #endif /* HAVE_APP_INDICATOR */
106 
107 	GtkWidget      *warn_dialog;
108 };
109 
110 static void     activity_detected_cb           (DrwMonitor     *monitor,
111 						DrWright       *drwright);
112 static gboolean maybe_change_state             (DrWright       *drwright);
113 static gint     get_time_left                  (DrWright       *drwright);
114 static gboolean update_status                  (DrWright       *drwright);
115 static void     break_window_done_cb           (GtkWidget      *window,
116 						DrWright       *dr);
117 static void     break_window_postpone_cb       (GtkWidget      *window,
118 						DrWright       *dr);
119 static void     break_window_destroy_cb        (GtkWidget      *window,
120 						DrWright       *dr);
121 static void     popup_break_cb                 (GtkAction      *action,
122 						DrWright       *dr);
123 static void     popup_preferences_cb           (GtkAction      *action,
124 						DrWright       *dr);
125 static void     popup_about_cb                 (GtkAction      *action,
126 						DrWright       *dr);
127 #ifdef HAVE_APP_INDICATOR
128 static void     init_app_indicator             (DrWright       *dr);
129 #else
130 static void     init_tray_icon                 (DrWright       *dr);
131 #endif /* HAVE_APP_INDICATOR */
132 static GList *  create_secondary_break_windows (void);
133 
134 static const GtkActionEntry actions[] = {
135   {"Preferences", "preferences-desktop", N_("_Preferences"), NULL, NULL, G_CALLBACK (popup_preferences_cb)},
136   {"About", "help-about", N_("_About"), NULL, NULL, G_CALLBACK (popup_about_cb)},
137   {"TakeABreak", NULL, N_("_Take a Break"), NULL, NULL, G_CALLBACK (popup_break_cb)}
138 };
139 
140 extern gboolean debug;
141 
142 static void
setup_debug_values(DrWright * dr)143 setup_debug_values (DrWright *dr)
144 {
145 	dr->type_time = 5;
146 	dr->warn_time = 4;
147 	dr->break_time = 10;
148 }
149 
150 #ifdef HAVE_APP_INDICATOR
151 static void
update_app_indicator(DrWright * dr)152 update_app_indicator (DrWright *dr)
153 {
154 	AppIndicatorStatus new_status;
155 
156 	if (!dr->enabled) {
157 		app_indicator_set_status (dr->indicator,
158 					  APP_INDICATOR_STATUS_PASSIVE);
159 		return;
160 	}
161 
162 	switch (dr->state) {
163 	case STATE_WARN:
164 	case STATE_BREAK_SETUP:
165 	case STATE_BREAK:
166 		new_status = APP_INDICATOR_STATUS_ATTENTION;
167 		break;
168 	default:
169 		new_status = APP_INDICATOR_STATUS_ACTIVE;
170 	}
171 
172 	app_indicator_set_status (dr->indicator, new_status);
173 }
174 #else
175 
176 static void
set_status_icon(GtkStatusIcon * icon,cairo_surface_t * surface)177 set_status_icon (GtkStatusIcon *icon, cairo_surface_t *surface)
178 {
179 	GdkPixbuf *pixbuf;
180 
181 	pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0,
182 					      cairo_image_surface_get_width (surface),
183 					      cairo_image_surface_get_height (surface));
184 
185 	gtk_status_icon_set_from_pixbuf (icon, pixbuf);
186 
187 	g_object_unref (pixbuf);
188 }
189 
190 static void
update_icon(DrWright * dr)191 update_icon (DrWright *dr)
192 {
193 	GdkPixbuf *pixbuf;
194 	GdkPixbuf *tmp_pixbuf;
195 	gint       width, height;
196 	gfloat     r;
197 	gint       offset;
198 	gboolean   set_pixbuf;
199 
200 	if (!dr->enabled) {
201 		set_status_icon (dr->icon, dr->disabled_bar);
202 		return;
203 	}
204 
205 	width = cairo_image_surface_get_width (dr->neutral_bar);
206 	height = cairo_image_surface_get_height (dr->neutral_bar);
207 
208 	tmp_pixbuf = gdk_pixbuf_get_from_surface (dr->neutral_bar,
209 						  0, 0,
210 						  width, height);
211 
212 	set_pixbuf = TRUE;
213 
214 	switch (dr->state) {
215 	case STATE_BREAK:
216 	case STATE_BREAK_SETUP:
217 		r = 1;
218 		break;
219 
220 	case STATE_BREAK_DONE:
221 	case STATE_BREAK_DONE_SETUP:
222 	case STATE_START:
223 		r = 0;
224 		break;
225 
226 	default:
227 		r = (float) (drw_timer_elapsed (dr->timer) + dr->save_last_time) /
228 		    (float) dr->type_time;
229 		break;
230 	}
231 
232 	offset = CLAMP ((height - 0) * (1.0 - r), 1, height - 0);
233 
234 	switch (dr->state) {
235 	case STATE_WARN:
236 		pixbuf = gdk_pixbuf_get_from_surface (dr->red_bar, 0, 0, width, height);
237 		set_pixbuf = FALSE;
238 		break;
239 
240 	case STATE_BREAK_SETUP:
241 	case STATE_BREAK:
242 		pixbuf = gdk_pixbuf_get_from_surface (dr->red_bar, 0, 0, width, height);
243 		break;
244 
245 	default:
246 		pixbuf = gdk_pixbuf_get_from_surface (dr->green_bar, 0, 0, width, height);
247 	}
248 
249 	gdk_pixbuf_composite (pixbuf,
250 			      tmp_pixbuf,
251 			      0,
252 			      offset,
253 			      width,
254 			      height - offset,
255 			      0,
256 			      0,
257 			      1.0,
258 			      1.0,
259 			      GDK_INTERP_BILINEAR,
260 			      255);
261 
262 	if (set_pixbuf) {
263 		gtk_status_icon_set_from_pixbuf (dr->icon,
264 						 tmp_pixbuf);
265 	}
266 
267 	if (dr->composite_bar) {
268 		g_object_unref (dr->composite_bar);
269 	}
270 
271 	dr->composite_bar = tmp_pixbuf;
272 
273 	g_object_unref (pixbuf);
274 }
275 
276 static gboolean
blink_timeout_cb(DrWright * dr)277 blink_timeout_cb (DrWright *dr)
278 {
279 	gfloat r;
280 	gint   timeout;
281 
282 	r = (dr->type_time - drw_timer_elapsed (dr->timer) - dr->save_last_time) / dr->warn_time;
283 	timeout = BLINK_TIMEOUT + BLINK_TIMEOUT_FACTOR * r;
284 
285 	if (timeout < BLINK_TIMEOUT_MIN) {
286 		timeout = BLINK_TIMEOUT_MIN;
287 	}
288 
289 	if (dr->blink_on || timeout == 0) {
290 		gtk_status_icon_set_from_pixbuf (dr->icon, dr->composite_bar);
291 	} else {
292 		set_status_icon (dr->icon, dr->neutral_bar);
293 	}
294 
295 	dr->blink_on = !dr->blink_on;
296 
297 	if (timeout) {
298 		dr->blink_timeout_id = g_timeout_add (timeout,
299 						      (GSourceFunc) blink_timeout_cb,
300 						      dr);
301 	} else {
302 		dr->blink_timeout_id = 0;
303 	}
304 
305 	return FALSE;
306 }
307 #endif /* HAVE_APP_INDICATOR */
308 
309 static void
start_blinking(DrWright * dr)310 start_blinking (DrWright *dr)
311 {
312 #ifndef HAVE_APP_INDICATOR
313 	if (!dr->blink_timeout_id) {
314 		dr->blink_on = TRUE;
315 		blink_timeout_cb (dr);
316 	}
317 
318 	/*gtk_widget_show (GTK_WIDGET (dr->icon));*/
319 #endif /* HAVE_APP_INDICATOR */
320 }
321 
322 static void
stop_blinking(DrWright * dr)323 stop_blinking (DrWright *dr)
324 {
325 #ifndef HAVE_APP_INDICATOR
326 	if (dr->blink_timeout_id) {
327 		g_source_remove (dr->blink_timeout_id);
328 		dr->blink_timeout_id = 0;
329 	}
330 
331 	/*gtk_widget_hide (GTK_WIDGET (dr->icon));*/
332 #endif /* HAVE_APP_INDICATOR */
333 }
334 
335 static gboolean
grab_keyboard_on_window(GdkWindow * window,guint32 activate_time)336 grab_keyboard_on_window (GdkWindow *window,
337 			 guint32    activate_time)
338 {
339 	GdkDisplay *display;
340 	GdkSeat *seat;
341 	GdkGrabStatus status;
342 
343 	display = gdk_window_get_display (window);
344 	seat = gdk_display_get_default_seat (display);
345 
346 	status = gdk_seat_grab (seat,
347 	                        window,
348 	                        GDK_SEAT_CAPABILITY_KEYBOARD,
349 	                        TRUE,
350 	                        NULL,
351 	                        NULL,
352 	                        NULL,
353 	                        NULL);
354 
355 	if (status == GDK_GRAB_SUCCESS) {
356 		return TRUE;
357 	}
358 
359 	return FALSE;
360 }
361 
362 static gboolean
break_window_map_event_cb(GtkWidget * widget,GdkEvent * event,DrWright * dr)363 break_window_map_event_cb (GtkWidget *widget,
364 			   GdkEvent  *event,
365 			   DrWright  *dr)
366 {
367 	grab_keyboard_on_window (gtk_widget_get_window (dr->break_window), gtk_get_current_event_time ());
368 
369         return FALSE;
370 }
371 
372 static gboolean
maybe_change_state(DrWright * dr)373 maybe_change_state (DrWright *dr)
374 {
375 	gint elapsed_time;
376 	gint elapsed_idle_time;
377 
378 	if (debug) {
379 		drw_timer_start (dr->idle_timer);
380 	}
381 
382 	elapsed_time = drw_timer_elapsed (dr->timer) + dr->save_last_time;
383 	elapsed_idle_time = drw_timer_elapsed (dr->idle_timer);
384 
385 	if (elapsed_time > dr->last_elapsed_time + dr->warn_time) {
386 		/* If the timeout is delayed by the amount of warning time, then
387 		 * we must have been suspended or stopped, so we just start
388 		 * over.
389 		 */
390 		dr->state = STATE_START;
391 	}
392 
393 	switch (dr->state) {
394 	case STATE_START:
395 		if (dr->break_window) {
396 			gtk_widget_destroy (dr->break_window);
397 			dr->break_window = NULL;
398 		}
399 
400 #ifndef HAVE_APP_INDICATOR
401 		set_status_icon (dr->icon, dr->neutral_bar);
402 #endif /* HAVE_APP_INDICATOR */
403 
404 		dr->save_last_time = 0;
405 
406 		drw_timer_start (dr->timer);
407 		drw_timer_start (dr->idle_timer);
408 
409 		if (dr->enabled) {
410 			dr->state = STATE_RUNNING;
411 		}
412 
413 		update_status (dr);
414 		stop_blinking (dr);
415 		break;
416 
417 	case STATE_RUNNING:
418 	case STATE_WARN:
419 		if (elapsed_idle_time >= dr->break_time) {
420 			dr->state = STATE_BREAK_DONE_SETUP;
421  		} else if (elapsed_time >= dr->type_time) {
422 			dr->state = STATE_BREAK_SETUP;
423 		} else if (dr->state != STATE_WARN
424 			   && elapsed_time >= dr->type_time - dr->warn_time) {
425 			dr->state = STATE_WARN;
426 			start_blinking (dr);
427 		}
428 		break;
429 
430 	case STATE_BREAK_SETUP:
431 		/* Don't allow more than one break window to coexist, can happen
432 		 * if a break is manually enforced.
433 		 */
434 		if (dr->break_window) {
435 			dr->state = STATE_BREAK;
436 			break;
437 		}
438 
439 		stop_blinking (dr);
440 #ifndef HAVE_APP_INDICATOR
441 		set_status_icon (dr->icon, dr->red_bar);
442 #endif /* HAVE_APP_INDICATOR */
443 
444 		drw_timer_start (dr->timer);
445 
446 		dr->break_window = drw_break_window_new ();
447 
448 		g_signal_connect (dr->break_window, "map_event",
449 				  G_CALLBACK (break_window_map_event_cb),
450 				  dr);
451 
452 		g_signal_connect (dr->break_window,
453 				  "done",
454 				  G_CALLBACK (break_window_done_cb),
455 				  dr);
456 
457 		g_signal_connect (dr->break_window,
458 				  "postpone",
459 				  G_CALLBACK (break_window_postpone_cb),
460 				  dr);
461 
462 		g_signal_connect (dr->break_window,
463 				  "destroy",
464 				  G_CALLBACK (break_window_destroy_cb),
465 				  dr);
466 
467 		dr->secondary_break_windows = create_secondary_break_windows ();
468 
469 		gtk_widget_show (dr->break_window);
470 
471 		dr->save_last_time = elapsed_time;
472 		dr->state = STATE_BREAK;
473 		break;
474 
475 	case STATE_BREAK:
476 		if (elapsed_time - dr->save_last_time >= dr->break_time) {
477 			dr->state = STATE_BREAK_DONE_SETUP;
478 		}
479 		break;
480 
481 	case STATE_BREAK_DONE_SETUP:
482 		stop_blinking (dr);
483 #ifndef HAVE_APP_INDICATOR
484 		set_status_icon (dr->icon, dr->green_bar);
485 #endif /* HAVE_APP_INDICATOR */
486 
487 		dr->state = STATE_BREAK_DONE;
488 		break;
489 
490 	case STATE_BREAK_DONE:
491 		dr->state = STATE_START;
492 		if (dr->break_window) {
493 			gtk_widget_destroy (dr->break_window);
494 			dr->break_window = NULL;
495 		}
496 		break;
497 	}
498 
499 	dr->last_elapsed_time = elapsed_time;
500 
501 #ifdef HAVE_APP_INDICATOR
502 	update_app_indicator (dr);
503 #else
504 	update_icon (dr);
505 #endif /* HAVE_APP_INDICATOR */
506 
507 	return TRUE;
508 }
509 
510 static gboolean
update_status(DrWright * dr)511 update_status (DrWright *dr)
512 {
513 	gint       min;
514 	gchar     *str;
515 #ifdef HAVE_APP_INDICATOR
516 	GtkWidget *item;
517 #endif /* HAVE_APP_INDICATOR */
518 
519 	if (!dr->enabled) {
520 #ifdef HAVE_APP_INDICATOR
521 		app_indicator_set_status (dr->indicator,
522 					  APP_INDICATOR_STATUS_PASSIVE);
523 #else
524 		gtk_status_icon_set_tooltip_text (dr->icon,
525 						  _("Disabled"));
526 #endif /* HAVE_APP_INDICATOR */
527 		return TRUE;
528 	}
529 
530 	min = get_time_left (dr);
531 
532 	if (min >= 1) {
533 #ifdef HAVE_APP_INDICATOR
534 		str = g_strdup_printf (_("Take a break now (next in %dm)"), min);
535 #else
536 		str = g_strdup_printf (ngettext("%d minute until the next break",
537 						"%d minutes until the next break",
538 						min), min);
539 #endif /* HAVE_APP_INDICATOR */
540 	} else {
541 #ifdef HAVE_APP_INDICATOR
542 		str = g_strdup_printf (_("Take a break now (next in less than one minute)"));
543 #else
544 		str = g_strdup_printf (_("Less than one minute until the next break"));
545 #endif /* HAVE_APP_INDICATOR */
546 	}
547 
548 #ifdef HAVE_APP_INDICATOR
549 	item = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop/TakeABreak");
550 	gtk_menu_item_set_label (GTK_MENU_ITEM (item), str);
551 #else
552 	gtk_status_icon_set_tooltip_text (dr->icon, str);
553 #endif /* HAVE_APP_INDICATOR */
554 
555 	g_free (str);
556 
557 	return TRUE;
558 }
559 
560 static gint
get_time_left(DrWright * dr)561 get_time_left (DrWright *dr)
562 {
563 	gint elapsed_time;
564 
565 	elapsed_time = drw_timer_elapsed (dr->timer);
566 
567 	return floor (0.5 + (dr->type_time - elapsed_time - dr->save_last_time) / 60.0);
568 }
569 
570 static void
activity_detected_cb(DrwMonitor * monitor,DrWright * dr)571 activity_detected_cb (DrwMonitor *monitor,
572 		      DrWright   *dr)
573 {
574 	drw_timer_start (dr->idle_timer);
575 }
576 
577 static void
gsettings_notify_cb(GSettings * settings,gchar * key,gpointer user_data)578 gsettings_notify_cb (GSettings *settings,
579 		 gchar       *key,
580 		 gpointer     user_data)
581 {
582 	DrWright  *dr = user_data;
583 	GtkWidget *item;
584 
585 	if (!strcmp (key, "type-time")) {
586 		dr->type_time = 60 * g_settings_get_int (settings, key);
587 		dr->warn_time = MIN (dr->type_time / 10, 5*60);
588 
589 		dr->state = STATE_START;
590 	}
591 	else if (!strcmp (key, "break-time")) {
592 		dr->break_time = 60 * g_settings_get_int (settings, key);
593 		dr->state = STATE_START;
594 	}
595 	else if (!strcmp (key, "enabled")) {
596 		dr->enabled = g_settings_get_boolean (settings, key);
597 		dr->state = STATE_START;
598 
599 		item = gtk_ui_manager_get_widget (dr->ui_manager,
600 						  "/Pop/TakeABreak");
601 		gtk_widget_set_sensitive (item, dr->enabled);
602 
603 		update_status (dr);
604 	}
605 
606 	maybe_change_state (dr);
607 }
608 
609 static void
popup_break_cb(GtkAction * action,DrWright * dr)610 popup_break_cb (GtkAction *action, DrWright *dr)
611 {
612 	if (dr->enabled) {
613 		dr->state = STATE_BREAK_SETUP;
614 		maybe_change_state (dr);
615 	}
616 }
617 
618 static void
popup_preferences_cb(GtkAction * action,DrWright * dr)619 popup_preferences_cb (GtkAction *action, DrWright *dr)
620 {
621 	GdkScreen *screen;
622 	GError    *error = NULL;
623 	GtkWidget *menu;
624 
625 	menu = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop");
626 	screen = gtk_widget_get_screen (menu);
627 
628 	if (!mate_gdk_spawn_command_line_on_screen (screen, "mate-keyboard-properties --typing-break", &error)) {
629 		GtkWidget *error_dialog;
630 
631 		error_dialog = gtk_message_dialog_new (NULL, 0,
632 						       GTK_MESSAGE_ERROR,
633 						       GTK_BUTTONS_CLOSE,
634 						       _("Unable to bring up the typing break properties dialog with the following error: %s"),
635 						       error->message);
636 		g_signal_connect (error_dialog,
637 				  "response",
638 				  G_CALLBACK (gtk_widget_destroy), NULL);
639 		gtk_window_set_resizable (GTK_WINDOW (error_dialog), FALSE);
640 		gtk_widget_show (error_dialog);
641 
642 		g_error_free (error);
643 	}
644 }
645 
646 static void
popup_about_cb(GtkAction * action,DrWright * dr)647 popup_about_cb (GtkAction *action, DrWright *dr)
648 {
649 	gint   i;
650 	gchar *authors[] = {
651 		N_("Written by Richard Hult <richard@imendio.com>"),
652 		N_("Eye candy added by Anders Carlsson"),
653 		NULL
654 	};
655 
656 	for (i = 0; authors [i]; i++)
657 		authors [i] = _(authors [i]);
658 
659 	gtk_show_about_dialog (NULL,
660 			       "authors", authors,
661 			       "comments",  _("A computer break reminder."),
662 			       "logo-icon-name", "mate-typing-monitor",
663 			       "translator-credits", _("translator-credits"),
664 			       "version", VERSION,
665 			       NULL);
666 }
667 
668 #ifndef HAVE_APP_INDICATOR
669 static void
popup_menu_cb(GtkWidget * widget,guint button,guint activate_time,DrWright * dr)670 popup_menu_cb (GtkWidget *widget,
671 	       guint      button,
672 	       guint      activate_time,
673 	       DrWright  *dr)
674 {
675 	GtkWidget *menu;
676 
677 	menu = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop");
678 
679 	gtk_menu_popup (GTK_MENU (menu),
680 			NULL,
681 			NULL,
682 			gtk_status_icon_position_menu,
683 			dr->icon,
684 			0,
685 			gtk_get_current_event_time ());
686 }
687 #endif /* HAVE_APP_INDICATOR */
688 
689 static void
break_window_done_cb(GtkWidget * window,DrWright * dr)690 break_window_done_cb (GtkWidget *window,
691 		      DrWright  *dr)
692 {
693 	gtk_widget_destroy (dr->break_window);
694 
695 	dr->state = STATE_BREAK_DONE_SETUP;
696 	dr->break_window = NULL;
697 
698 	update_status (dr);
699 	maybe_change_state (dr);
700 }
701 
702 static void
break_window_postpone_cb(GtkWidget * window,DrWright * dr)703 break_window_postpone_cb (GtkWidget *window,
704 			  DrWright  *dr)
705 {
706 	gint elapsed_time;
707 
708 	gtk_widget_destroy (dr->break_window);
709 
710 	dr->state = STATE_RUNNING;
711 	dr->break_window = NULL;
712 
713 	elapsed_time = drw_timer_elapsed (dr->timer);
714 
715 	if (elapsed_time + dr->save_last_time >= dr->type_time) {
716 		/* Typing time has expired, but break was postponed.
717 		 * We'll warn again in (elapsed * sqrt (typing_time))^2 */
718 		gfloat postpone_time = (((float) elapsed_time) / dr->break_time)
719 					* sqrt (dr->type_time);
720 		postpone_time *= postpone_time;
721 		dr->save_last_time = dr->type_time - MAX (dr->warn_time, (gint) postpone_time);
722 	}
723 
724 	drw_timer_start (dr->timer);
725 	maybe_change_state (dr);
726 	update_status (dr);
727 #ifdef HAVE_APP_INDICATOR
728 	update_app_indicator (dr);
729 #else
730 	update_icon (dr);
731 #endif /* HAVE_APP_INDICATOR */
732 }
733 
734 static void
break_window_destroy_cb(GtkWidget * window,DrWright * dr)735 break_window_destroy_cb (GtkWidget *window,
736 			 DrWright  *dr)
737 {
738 	GList *l;
739 
740 	for (l = dr->secondary_break_windows; l; l = l->next) {
741 		gtk_widget_destroy (l->data);
742 	}
743 
744 	g_list_free (dr->secondary_break_windows);
745 	dr->secondary_break_windows = NULL;
746 }
747 
748 #ifdef HAVE_APP_INDICATOR
749 static void
init_app_indicator(DrWright * dr)750 init_app_indicator (DrWright *dr)
751 {
752 	GtkWidget *indicator_menu;
753 
754 	dr->indicator =
755 		app_indicator_new_with_path ("typing-break-indicator",
756 					     TYPING_MONITOR_ACTIVE_ICON,
757 					     APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
758 					     IMAGEDIR);
759 	if (dr->enabled) {
760 		app_indicator_set_status (dr->indicator,
761 					  APP_INDICATOR_STATUS_ACTIVE);
762 	} else {
763 		app_indicator_set_status (dr->indicator,
764 					  APP_INDICATOR_STATUS_PASSIVE);
765 	}
766 
767 	indicator_menu = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop");
768 	app_indicator_set_menu (dr->indicator, GTK_MENU (indicator_menu));
769 	app_indicator_set_attention_icon (dr->indicator, TYPING_MONITOR_ATTENTION_ICON);
770 
771 	update_status (dr);
772 	update_app_indicator (dr);
773 }
774 #else
775 static void
init_tray_icon(DrWright * dr)776 init_tray_icon (DrWright *dr)
777 {
778 	GdkPixbuf *pixbuf;
779 
780 	pixbuf = gdk_pixbuf_get_from_surface (dr->neutral_bar, 0, 0,
781 					      cairo_image_surface_get_width (dr->neutral_bar),
782 					      cairo_image_surface_get_height (dr->neutral_bar));
783 
784 	dr->icon = gtk_status_icon_new_from_pixbuf (pixbuf);
785 	g_object_unref (pixbuf);
786 
787 	update_status (dr);
788 	update_icon (dr);
789 
790 	g_signal_connect (dr->icon,
791 			  "popup_menu",
792 			  G_CALLBACK (popup_menu_cb),
793 			  dr);
794 }
795 #endif /* HAVE_APP_INDICATOR */
796 
797 static GList *
create_secondary_break_windows(void)798 create_secondary_break_windows (void)
799 {
800 	GdkDisplay *display;
801 	GdkScreen  *screen;
802 	GtkWidget  *window;
803 	GList      *windows = NULL;
804 	gint        scale;
805 
806 	display = gdk_display_get_default ();
807 
808 	screen = gdk_display_get_default_screen (display);
809 
810 	if (screen != gdk_screen_get_default ()) {
811 		/* Handled by DrwBreakWindow. */
812 
813 		window = gtk_window_new (GTK_WINDOW_POPUP);
814 
815 		windows = g_list_prepend (windows, window);
816 		scale = gtk_widget_get_scale_factor (GTK_WIDGET (window));
817 
818 		gtk_window_set_screen (GTK_WINDOW (window), screen);
819 
820 		gtk_window_set_default_size (GTK_WINDOW (window),
821 					     WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale,
822 					     HeightOfScreen (gdk_x11_screen_get_xscreen (screen)) / scale);
823 
824 		gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE);
825 		drw_setup_background (GTK_WIDGET (window));
826 		gtk_window_stick (GTK_WINDOW (window));
827 		gtk_widget_show (window);
828 	}
829 
830 	return windows;
831 }
832 
833 DrWright *
drwright_new(void)834 drwright_new (void)
835 {
836 	DrWright  *dr;
837 	GtkWidget *item;
838 	GSettings *settings;
839 	GtkActionGroup *action_group;
840 
841 	static const char ui_description[] =
842 	  "<ui>"
843 	  "  <popup name='Pop'>"
844 	  "    <menuitem action='Preferences'/>"
845 	  "    <menuitem action='About'/>"
846 	  "    <separator/>"
847 	  "    <menuitem action='TakeABreak'/>"
848 	  "  </popup>"
849 	  "</ui>";
850 
851         dr = g_new0 (DrWright, 1);
852 
853 	settings = g_settings_new (TYPING_BREAK_SCHEMA);
854 
855 	g_signal_connect (settings, "changed", G_CALLBACK (gsettings_notify_cb), dr);
856 
857 	dr->type_time = 60 * g_settings_get_int (settings, "type-time");
858 
859 	dr->warn_time = MIN (dr->type_time / 12, 60*3);
860 
861 	dr->break_time = 60 * g_settings_get_int (settings, "break-time");
862 
863 	dr->enabled = g_settings_get_boolean (settings, "enabled");
864 
865 	if (debug) {
866 		setup_debug_values (dr);
867 	}
868 
869 	dr->ui_manager = gtk_ui_manager_new ();
870 
871 	action_group = gtk_action_group_new ("MenuActions");
872 #ifdef ENABLE_NLS
873 	gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
874 #endif /* ENABLE_NLS */
875 	gtk_action_group_add_actions (action_group, actions, G_N_ELEMENTS (actions), dr);
876 	gtk_ui_manager_insert_action_group (dr->ui_manager, action_group, 0);
877 	gtk_ui_manager_add_ui_from_string (dr->ui_manager, ui_description, -1, NULL);
878 
879 	item = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop/TakeABreak");
880 	gtk_widget_set_sensitive (item, dr->enabled);
881 
882 	dr->timer = drw_timer_new ();
883 	dr->idle_timer = drw_timer_new ();
884 
885 	dr->state = STATE_START;
886 
887 	dr->monitor = drw_monitor_new ();
888 
889 	g_signal_connect (dr->monitor,
890 			  "activity",
891 			  G_CALLBACK (activity_detected_cb),
892 			  dr);
893 
894 #ifdef HAVE_APP_INDICATOR
895 	init_app_indicator (dr);
896 #else
897 	dr->neutral_bar = cairo_image_surface_create_from_png (IMAGEDIR "/bar.png");
898 	dr->red_bar = cairo_image_surface_create_from_png (IMAGEDIR "/bar-red.png");
899 	dr->green_bar = cairo_image_surface_create_from_png (IMAGEDIR "/bar-green.png");
900 	dr->disabled_bar = cairo_image_surface_create_from_png (IMAGEDIR "/bar-disabled.png");
901 
902 	init_tray_icon (dr);
903 #endif /* HAVE_APP_INDICATOR */
904 
905 	g_timeout_add_seconds (12,
906 			       (GSourceFunc) update_status,
907 			       dr);
908 
909 	g_timeout_add_seconds (1,
910 			       (GSourceFunc) maybe_change_state,
911 			       dr);
912 
913 	return dr;
914 }
915