1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3
* Copyright (C) 2003-2005 Imendio HB
4
* Copyright (C) 2002-2003 Richard Hult <richard@imendio.com>
5
* Copyright (C) 2002 CodeFactory AB
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.
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.
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., 59 Temple Place - Suite 330,
20
* Boston, MA 02111-1307, USA.
26
#include <glib/gi18n.h>
28
#include <gdk/gdkkeysyms.h>
30
#include <gconf/gconf-client.h>
32
#ifdef HAVE_APP_INDICATOR
33
#include <libappindicator/app-indicator.h>
34
#endif /* HAVE_APP_INDICATOR */
37
#include "drw-break-window.h"
38
#include "drw-monitor.h"
39
#include "drw-utils.h"
40
#include "drw-timer.h"
42
#ifndef HAVE_APP_INDICATOR
43
#define BLINK_TIMEOUT 200
44
#define BLINK_TIMEOUT_MIN 120
45
#define BLINK_TIMEOUT_FACTOR 100
46
#endif /* HAVE_APP_INDICATOR */
48
#define POPUP_ITEM_ENABLED 1
49
#define POPUP_ITEM_BREAK 2
57
STATE_BREAK_DONE_SETUP,
61
#ifdef HAVE_APP_INDICATOR
62
#define TYPING_MONITOR_ACTIVE_ICON "bar-green"
63
#define TYPING_MONITOR_ATTENTION_ICON "bar-red"
64
#endif /* HAVE_APP_INDICATOR */
68
GtkWidget *break_window;
69
GList *secondary_break_windows;
73
GtkUIManager *ui_manager;
79
gint last_elapsed_time;
89
guint clock_timeout_id;
90
#ifdef HAVE_APP_INDICATOR
91
AppIndicator *indicator;
93
guint blink_timeout_id;
99
GdkPixbuf *neutral_bar;
101
GdkPixbuf *green_bar;
102
GdkPixbuf *disabled_bar;
103
GdkPixbuf *composite_bar;
104
#endif /* HAVE_APP_INDICATOR */
106
GtkWidget *warn_dialog;
109
static void activity_detected_cb (DrwMonitor *monitor,
111
static gboolean maybe_change_state (DrWright *drwright);
112
static gint get_time_left (DrWright *drwright);
113
static gboolean update_status (DrWright *drwright);
114
static void break_window_done_cb (GtkWidget *window,
116
static void break_window_postpone_cb (GtkWidget *window,
118
static void break_window_destroy_cb (GtkWidget *window,
120
static void popup_break_cb (GtkAction *action,
122
static void popup_preferences_cb (GtkAction *action,
124
static void popup_about_cb (GtkAction *action,
126
#ifdef HAVE_APP_INDICATOR
127
static void init_app_indicator (DrWright *dr);
129
static void init_tray_icon (DrWright *dr);
130
#endif /* HAVE_APP_INDICATOR */
131
static GList * create_secondary_break_windows (void);
133
static const GtkActionEntry actions[] = {
134
{"Preferences", GTK_STOCK_PREFERENCES, NULL, NULL, NULL, G_CALLBACK (popup_preferences_cb)},
135
{"About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK (popup_about_cb)},
136
{"TakeABreak", NULL, N_("_Take a Break"), NULL, NULL, G_CALLBACK (popup_break_cb)}
139
extern gboolean debug;
142
setup_debug_values (DrWright *dr)
149
#ifdef HAVE_APP_INDICATOR
151
update_app_indicator (DrWright *dr)
153
AppIndicatorStatus new_status;
156
app_indicator_set_status (dr->indicator,
157
APP_INDICATOR_STATUS_PASSIVE);
163
case STATE_BREAK_SETUP:
165
new_status = APP_INDICATOR_STATUS_ATTENTION;
168
new_status = APP_INDICATOR_STATUS_ACTIVE;
171
app_indicator_set_status (dr->indicator, new_status);
175
update_icon (DrWright *dr)
178
GdkPixbuf *tmp_pixbuf;
185
gtk_status_icon_set_from_pixbuf (dr->icon,
190
tmp_pixbuf = gdk_pixbuf_copy (dr->neutral_bar);
192
width = gdk_pixbuf_get_width (tmp_pixbuf);
193
height = gdk_pixbuf_get_height (tmp_pixbuf);
199
case STATE_BREAK_SETUP:
203
case STATE_BREAK_DONE:
204
case STATE_BREAK_DONE_SETUP:
210
r = (float) (drw_timer_elapsed (dr->timer) + dr->save_last_time) /
211
(float) dr->type_time;
215
offset = CLAMP ((height - 0) * (1.0 - r), 1, height - 0);
219
pixbuf = dr->red_bar;
223
case STATE_BREAK_SETUP:
225
pixbuf = dr->red_bar;
229
pixbuf = dr->green_bar;
232
gdk_pixbuf_composite (pixbuf,
246
gtk_status_icon_set_from_pixbuf (dr->icon,
250
if (dr->composite_bar) {
251
g_object_unref (dr->composite_bar);
254
dr->composite_bar = tmp_pixbuf;
258
blink_timeout_cb (DrWright *dr)
263
r = (dr->type_time - drw_timer_elapsed (dr->timer) - dr->save_last_time) / dr->warn_time;
264
timeout = BLINK_TIMEOUT + BLINK_TIMEOUT_FACTOR * r;
266
if (timeout < BLINK_TIMEOUT_MIN) {
267
timeout = BLINK_TIMEOUT_MIN;
270
if (dr->blink_on || timeout == 0) {
271
gtk_status_icon_set_from_pixbuf (dr->icon,
274
gtk_status_icon_set_from_pixbuf (dr->icon,
278
dr->blink_on = !dr->blink_on;
281
dr->blink_timeout_id = g_timeout_add (timeout,
282
(GSourceFunc) blink_timeout_cb,
285
dr->blink_timeout_id = 0;
290
#endif /* HAVE_APP_INDICATOR */
293
start_blinking (DrWright *dr)
295
#ifndef HAVE_APP_INDICATOR
296
if (!dr->blink_timeout_id) {
298
blink_timeout_cb (dr);
301
/*gtk_widget_show (GTK_WIDGET (dr->icon));*/
302
#endif /* HAVE_APP_INDICATOR */
306
stop_blinking (DrWright *dr)
308
#ifndef HAVE_APP_INDICATOR
309
if (dr->blink_timeout_id) {
310
g_source_remove (dr->blink_timeout_id);
311
dr->blink_timeout_id = 0;
314
/*gtk_widget_hide (GTK_WIDGET (dr->icon));*/
315
#endif /* HAVE_APP_INDICATOR */
319
grab_keyboard_on_window (GdkWindow *window,
320
guint32 activate_time)
322
GdkGrabStatus status;
324
status = gdk_keyboard_grab (window, TRUE, activate_time);
325
if (status == GDK_GRAB_SUCCESS) {
333
break_window_map_event_cb (GtkWidget *widget,
337
grab_keyboard_on_window (gtk_widget_get_window (dr->break_window), gtk_get_current_event_time ());
343
maybe_change_state (DrWright *dr)
346
gint elapsed_idle_time;
349
drw_timer_start (dr->idle_timer);
352
elapsed_time = drw_timer_elapsed (dr->timer) + dr->save_last_time;
353
elapsed_idle_time = drw_timer_elapsed (dr->idle_timer);
355
if (elapsed_time > dr->last_elapsed_time + dr->warn_time) {
356
/* If the timeout is delayed by the amount of warning time, then
357
* we must have been suspended or stopped, so we just start
360
dr->state = STATE_START;
365
if (dr->break_window) {
366
gtk_widget_destroy (dr->break_window);
367
dr->break_window = NULL;
370
#ifndef HAVE_APP_INDICATOR
371
gtk_status_icon_set_from_pixbuf (dr->icon,
373
#endif /* HAVE_APP_INDICATOR */
375
dr->save_last_time = 0;
377
drw_timer_start (dr->timer);
378
drw_timer_start (dr->idle_timer);
381
dr->state = STATE_RUNNING;
390
if (elapsed_idle_time >= dr->break_time) {
391
dr->state = STATE_BREAK_DONE_SETUP;
392
} else if (elapsed_time >= dr->type_time) {
393
dr->state = STATE_BREAK_SETUP;
394
} else if (dr->state != STATE_WARN
395
&& elapsed_time >= dr->type_time - dr->warn_time) {
396
dr->state = STATE_WARN;
401
case STATE_BREAK_SETUP:
402
/* Don't allow more than one break window to coexist, can happen
403
* if a break is manually enforced.
405
if (dr->break_window) {
406
dr->state = STATE_BREAK;
411
#ifndef HAVE_APP_INDICATOR
412
gtk_status_icon_set_from_pixbuf (dr->icon,
414
#endif /* HAVE_APP_INDICATOR */
416
drw_timer_start (dr->timer);
418
dr->break_window = drw_break_window_new ();
420
g_signal_connect (dr->break_window, "map_event",
421
G_CALLBACK (break_window_map_event_cb),
424
g_signal_connect (dr->break_window,
426
G_CALLBACK (break_window_done_cb),
429
g_signal_connect (dr->break_window,
431
G_CALLBACK (break_window_postpone_cb),
434
g_signal_connect (dr->break_window,
436
G_CALLBACK (break_window_destroy_cb),
439
dr->secondary_break_windows = create_secondary_break_windows ();
441
gtk_widget_show (dr->break_window);
443
dr->save_last_time = elapsed_time;
444
dr->state = STATE_BREAK;
448
if (elapsed_time - dr->save_last_time >= dr->break_time) {
449
dr->state = STATE_BREAK_DONE_SETUP;
453
case STATE_BREAK_DONE_SETUP:
455
#ifndef HAVE_APP_INDICATOR
456
gtk_status_icon_set_from_pixbuf (dr->icon,
458
#endif /* HAVE_APP_INDICATOR */
460
dr->state = STATE_BREAK_DONE;
463
case STATE_BREAK_DONE:
464
dr->state = STATE_START;
465
if (dr->break_window) {
466
gtk_widget_destroy (dr->break_window);
467
dr->break_window = NULL;
472
dr->last_elapsed_time = elapsed_time;
474
#ifdef HAVE_APP_INDICATOR
475
update_app_indicator (dr);
478
#endif /* HAVE_APP_INDICATOR */
484
update_status (DrWright *dr)
488
#ifdef HAVE_APP_INDICATOR
490
#endif /* HAVE_APP_INDICATOR */
493
#ifdef HAVE_APP_INDICATOR
494
app_indicator_set_status (dr->indicator,
495
APP_INDICATOR_STATUS_PASSIVE);
497
gtk_status_icon_set_tooltip_text (dr->icon,
499
#endif /* HAVE_APP_INDICATOR */
503
min = get_time_left (dr);
506
#ifdef HAVE_APP_INDICATOR
507
str = g_strdup_printf (_("Take a break now (next in %dm)"), min);
509
str = g_strdup_printf (ngettext("%d minute until the next break",
510
"%d minutes until the next break",
512
#endif /* HAVE_APP_INDICATOR */
514
#ifdef HAVE_APP_INDICATOR
515
str = g_strdup_printf (_("Take a break now (next in less than one minute)"));
517
str = g_strdup_printf (_("Less than one minute until the next break"));
518
#endif /* HAVE_APP_INDICATOR */
521
#ifdef HAVE_APP_INDICATOR
522
item = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop/TakeABreak");
523
gtk_menu_item_set_label (GTK_MENU_ITEM (item), str);
525
gtk_status_icon_set_tooltip_text (dr->icon, str);
526
#endif /* HAVE_APP_INDICATOR */
534
get_time_left (DrWright *dr)
538
elapsed_time = drw_timer_elapsed (dr->timer);
540
return floor (0.5 + (dr->type_time - elapsed_time - dr->save_last_time) / 60.0);
544
activity_detected_cb (DrwMonitor *monitor,
547
drw_timer_start (dr->idle_timer);
551
gconf_notify_cb (GConfClient *client,
556
DrWright *dr = user_data;
559
if (!strcmp (entry->key, GCONF_PATH "/type_time")) {
560
if (entry->value->type == GCONF_VALUE_INT) {
561
dr->type_time = 60 * gconf_value_get_int (entry->value);
562
dr->warn_time = MIN (dr->type_time / 10, 5*60);
564
dr->state = STATE_START;
567
else if (!strcmp (entry->key, GCONF_PATH "/break_time")) {
568
if (entry->value->type == GCONF_VALUE_INT) {
569
dr->break_time = 60 * gconf_value_get_int (entry->value);
570
dr->state = STATE_START;
573
else if (!strcmp (entry->key, GCONF_PATH "/enabled")) {
574
if (entry->value->type == GCONF_VALUE_BOOL) {
575
dr->enabled = gconf_value_get_bool (entry->value);
576
dr->state = STATE_START;
578
item = gtk_ui_manager_get_widget (dr->ui_manager,
580
gtk_widget_set_sensitive (item, dr->enabled);
586
maybe_change_state (dr);
590
popup_break_cb (GtkAction *action, DrWright *dr)
593
dr->state = STATE_BREAK_SETUP;
594
maybe_change_state (dr);
599
popup_preferences_cb (GtkAction *action, DrWright *dr)
602
GError *error = NULL;
605
menu = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop");
606
screen = gtk_widget_get_screen (menu);
608
if (!gdk_spawn_command_line_on_screen (screen, "gnome-keyboard-properties --typing-break", &error)) {
609
GtkWidget *error_dialog;
611
error_dialog = gtk_message_dialog_new (NULL, 0,
614
_("Unable to bring up the typing break properties dialog with the following error: %s"),
616
g_signal_connect (error_dialog,
618
G_CALLBACK (gtk_widget_destroy), NULL);
619
gtk_window_set_resizable (GTK_WINDOW (error_dialog), FALSE);
620
gtk_widget_show (error_dialog);
622
g_error_free (error);
627
popup_about_cb (GtkAction *action, DrWright *dr)
631
N_("Written by Richard Hult <richard@imendio.com>"),
632
N_("Eye candy added by Anders Carlsson"),
636
for (i = 0; authors [i]; i++)
637
authors [i] = _(authors [i]);
639
gtk_show_about_dialog (NULL,
641
"comments", _("A computer break reminder."),
642
"logo-icon-name", "typing-monitor",
643
"translator-credits", _("translator-credits"),
648
#ifndef HAVE_APP_INDICATOR
650
popup_menu_cb (GtkWidget *widget,
657
menu = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop");
659
gtk_menu_popup (GTK_MENU (menu),
662
gtk_status_icon_position_menu,
665
gtk_get_current_event_time ());
667
#endif /* HAVE_APP_INDICATOR */
670
break_window_done_cb (GtkWidget *window,
673
gtk_widget_destroy (dr->break_window);
675
dr->state = STATE_BREAK_DONE_SETUP;
676
dr->break_window = NULL;
679
maybe_change_state (dr);
683
break_window_postpone_cb (GtkWidget *window,
688
gtk_widget_destroy (dr->break_window);
690
dr->state = STATE_RUNNING;
691
dr->break_window = NULL;
693
elapsed_time = drw_timer_elapsed (dr->timer);
695
if (elapsed_time + dr->save_last_time >= dr->type_time) {
696
/* Typing time has expired, but break was postponed.
697
* We'll warn again in (elapsed * sqrt (typing_time))^2 */
698
gfloat postpone_time = (((float) elapsed_time) / dr->break_time)
699
* sqrt (dr->type_time);
700
postpone_time *= postpone_time;
701
dr->save_last_time = dr->type_time - MAX (dr->warn_time, (gint) postpone_time);
704
drw_timer_start (dr->timer);
705
maybe_change_state (dr);
707
#ifdef HAVE_APP_INDICATOR
708
update_app_indicator (dr);
711
#endif /* HAVE_APP_INDICATOR */
715
break_window_destroy_cb (GtkWidget *window,
720
for (l = dr->secondary_break_windows; l; l = l->next) {
721
gtk_widget_destroy (l->data);
724
g_list_free (dr->secondary_break_windows);
725
dr->secondary_break_windows = NULL;
728
#ifdef HAVE_APP_INDICATOR
730
init_app_indicator (DrWright *dr)
732
GtkWidget *indicator_menu;
735
app_indicator_new_with_path ("typing-break-indicator",
736
TYPING_MONITOR_ACTIVE_ICON,
737
APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
740
app_indicator_set_status (dr->indicator,
741
APP_INDICATOR_STATUS_ACTIVE);
743
app_indicator_set_status (dr->indicator,
744
APP_INDICATOR_STATUS_PASSIVE);
747
indicator_menu = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop");
748
app_indicator_set_menu (dr->indicator, GTK_MENU (indicator_menu));
749
app_indicator_set_attention_icon (dr->indicator, TYPING_MONITOR_ATTENTION_ICON);
752
update_app_indicator (dr);
756
init_tray_icon (DrWright *dr)
758
dr->icon = gtk_status_icon_new_from_pixbuf (dr->neutral_bar);
763
g_signal_connect (dr->icon,
765
G_CALLBACK (popup_menu_cb),
768
#endif /* HAVE_APP_INDICATOR */
771
create_secondary_break_windows (void)
777
GList *windows = NULL;
779
display = gdk_display_get_default ();
781
for (i = 0; i < gdk_display_get_n_screens (display); i++) {
782
screen = gdk_display_get_screen (display, i);
784
if (screen == gdk_screen_get_default ()) {
785
/* Handled by DrwBreakWindow. */
789
window = gtk_window_new (GTK_WINDOW_POPUP);
791
windows = g_list_prepend (windows, window);
793
gtk_window_set_screen (GTK_WINDOW (window), screen);
795
gtk_window_set_default_size (GTK_WINDOW (window),
796
gdk_screen_get_width (screen),
797
gdk_screen_get_height (screen));
799
gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE);
800
drw_setup_background (GTK_WIDGET (window));
801
gtk_window_stick (GTK_WINDOW (window));
802
gtk_widget_show (window);
814
GtkActionGroup *action_group;
816
static const char ui_description[] =
818
" <popup name='Pop'>"
819
" <menuitem action='Preferences'/>"
820
" <menuitem action='About'/>"
822
" <menuitem action='TakeABreak'/>"
826
dr = g_new0 (DrWright, 1);
828
client = gconf_client_get_default ();
830
gconf_client_add_dir (client,
832
GCONF_CLIENT_PRELOAD_NONE,
835
gconf_client_notify_add (client, GCONF_PATH,
841
dr->type_time = 60 * gconf_client_get_int (
842
client, GCONF_PATH "/type_time", NULL);
844
dr->warn_time = MIN (dr->type_time / 12, 60*3);
846
dr->break_time = 60 * gconf_client_get_int (
847
client, GCONF_PATH "/break_time", NULL);
849
dr->enabled = gconf_client_get_bool (
851
GCONF_PATH "/enabled",
854
g_object_unref (client);
857
setup_debug_values (dr);
860
dr->ui_manager = gtk_ui_manager_new ();
862
action_group = gtk_action_group_new ("MenuActions");
863
gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
864
gtk_action_group_add_actions (action_group, actions, G_N_ELEMENTS (actions), dr);
865
gtk_ui_manager_insert_action_group (dr->ui_manager, action_group, 0);
866
gtk_ui_manager_add_ui_from_string (dr->ui_manager, ui_description, -1, NULL);
868
item = gtk_ui_manager_get_widget (dr->ui_manager, "/Pop/TakeABreak");
869
gtk_widget_set_sensitive (item, dr->enabled);
871
dr->timer = drw_timer_new ();
872
dr->idle_timer = drw_timer_new ();
874
dr->state = STATE_START;
876
dr->monitor = drw_monitor_new ();
878
g_signal_connect (dr->monitor,
880
G_CALLBACK (activity_detected_cb),
883
#ifdef HAVE_APP_INDICATOR
884
init_app_indicator (dr);
886
dr->neutral_bar = gdk_pixbuf_new_from_file (IMAGEDIR "/bar.png", NULL);
887
dr->red_bar = gdk_pixbuf_new_from_file (IMAGEDIR "/bar-red.png", NULL);
888
dr->green_bar = gdk_pixbuf_new_from_file (IMAGEDIR "/bar-green.png", NULL);
889
dr->disabled_bar = gdk_pixbuf_new_from_file (IMAGEDIR "/bar-disabled.png", NULL);
892
#endif /* HAVE_APP_INDICATOR */
894
g_timeout_add_seconds (12,
895
(GSourceFunc) update_status,
898
g_timeout_add_seconds (1,
899
(GSourceFunc) maybe_change_state,