diff --git a/ChangeLog b/ChangeLog index fe4111e37..20be1d963 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Thu Jun 26 07:38:45 2003 Jonathan Blandford + + * typing-break/drwright.c: added the typing break to CVS from + drwright. + 2003-06-24 Jody Goldberg * configure.in : post release version bump diff --git a/capplets/common/gconf-property-editor.c b/capplets/common/gconf-property-editor.c index 95b368689..a46f37105 100644 --- a/capplets/common/gconf-property-editor.c +++ b/capplets/common/gconf-property-editor.c @@ -1070,9 +1070,16 @@ peditor_numeric_range_value_changed (GConfClient *client, if (value != NULL) { value_wid = peditor->p->conv_to_widget_cb (peditor, value); - g_return_if_fail (value_wid->type == GCONF_VALUE_FLOAT); - - gtk_adjustment_set_value (GTK_ADJUSTMENT (peditor->p->ui_control), gconf_value_get_float (value_wid)); + switch (value_wid->type) { + case GCONF_VALUE_FLOAT: + gtk_adjustment_set_value (GTK_ADJUSTMENT (peditor->p->ui_control), gconf_value_get_float (value_wid)); + break; + case GCONF_VALUE_INT: + gtk_adjustment_set_value (GTK_ADJUSTMENT (peditor->p->ui_control), gconf_value_get_int (value_wid)); + break; + default: + g_warning ("Unknown type in range peditor: %d\n", value_wid->type); + } gconf_value_free (value_wid); } } @@ -1081,11 +1088,36 @@ static void peditor_numeric_range_widget_changed (GConfPropertyEditor *peditor, GtkAdjustment *adjustment) { - GConfValue *value, *value_wid; + GConfValue *value, *value_wid, *default_value; if (!peditor->p->inited) return; - value_wid = gconf_value_new (GCONF_VALUE_FLOAT); - gconf_value_set_float (value_wid, gtk_adjustment_get_value (adjustment)); + + /* We try to get the default type from the schemas. if not, we default + * to a float. + */ + default_value = gconf_client_get_default_from_schema (gconf_client_get_default (), + peditor->p->key, + NULL); + if (default_value) + value_wid = gconf_value_new (default_value->type); + else + value_wid = gconf_value_new (GCONF_VALUE_FLOAT); + + gconf_value_free (default_value); + + g_assert (value_wid); + + if (value_wid->type == GCONF_VALUE_INT) + gconf_value_set_int (value_wid, gtk_adjustment_get_value (adjustment)); + else if (value_wid->type == GCONF_VALUE_FLOAT) + gconf_value_set_float (value_wid, gtk_adjustment_get_value (adjustment)); + else { + g_warning ("unable to set a gconf key for %s of type %d\n", + peditor->p->key, + value_wid->type); + gconf_value_free (value_wid); + return; + } value = peditor->p->conv_from_widget_cb (peditor, value_wid); peditor_set_gconf_value (peditor, peditor->p->key, value); g_signal_emit (peditor, peditor_signals[VALUE_CHANGED], 0, peditor->p->key, value); @@ -1101,14 +1133,19 @@ gconf_peditor_new_numeric_range (GConfChangeSet *changeset, ...) { GObject *peditor; - GObject *adjustment; + GObject *adjustment = NULL; va_list var_args; g_return_val_if_fail (key != NULL, NULL); g_return_val_if_fail (range != NULL, NULL); - g_return_val_if_fail (GTK_IS_RANGE (range), NULL); + g_return_val_if_fail (GTK_IS_RANGE (range)||GTK_IS_SPIN_BUTTON (range), NULL); - adjustment = G_OBJECT (gtk_range_get_adjustment (GTK_RANGE (range))); + if (GTK_IS_RANGE (range)) + adjustment = G_OBJECT (gtk_range_get_adjustment (GTK_RANGE (range))); + else if (GTK_IS_SPIN_BUTTON (range)) + adjustment = G_OBJECT (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (range))); + else + g_assert_not_reached (); va_start (var_args, first_property_name); diff --git a/capplets/keyboard/ChangeLog b/capplets/keyboard/ChangeLog index 47d85e28c..7b986c8ea 100644 --- a/capplets/keyboard/ChangeLog +++ b/capplets/keyboard/ChangeLog @@ -1,3 +1,8 @@ +Thu Jun 26 07:39:34 2003 Jonathan Blandford + + * gnome-keyboard-properties.c: added the typing break (drwright) + preferences + 2003-06-24 Jody Goldberg * Release 2.3.3 diff --git a/capplets/keyboard/gnome-keyboard-properties.c b/capplets/keyboard/gnome-keyboard-properties.c index 80cbd05e1..94fd7444a 100644 --- a/capplets/keyboard/gnome-keyboard-properties.c +++ b/capplets/keyboard/gnome-keyboard-properties.c @@ -38,7 +38,6 @@ #include "capplet-stock-icons.h" #include <../accessibility/keyboard/accessibility-keyboard.h> - enum { RESPONSE_APPLY = 1, @@ -71,46 +70,6 @@ create_dialog (void) return dialog; } -static GConfEnumStringPair bell_enums[] = { - { 0, "off" }, - { 1, "on" }, - { 2, "custom" }, - { -1, NULL } -}; - -static GConfValue * -bell_from_widget (GConfPropertyEditor *peditor, const GConfValue *value) -{ - GConfValue *new_value; - - new_value = gconf_value_new (GCONF_VALUE_STRING); - gconf_value_set_string (new_value, - gconf_enum_to_string (bell_enums, gconf_value_get_int (value))); - - return new_value; -} - -static GConfValue * -bell_to_widget (GConfPropertyEditor *peditor, const GConfValue *value) -{ - GConfValue *new_value; - const gchar *str; - gint val = 2; - - str = (value && (value->type == GCONF_VALUE_STRING)) ? gconf_value_get_string (value) : NULL; - - new_value = gconf_value_new (GCONF_VALUE_INT); - if (value->type == GCONF_VALUE_STRING) { - gconf_string_to_enum (bell_enums, - str, - &val); - } - gconf_value_set_int (new_value, val); - - return new_value; -} - - static GConfValue * blink_from_widget (GConfPropertyEditor *peditor, const GConfValue *value) { @@ -135,23 +94,6 @@ blink_to_widget (GConfPropertyEditor *peditor, const GConfValue *value) return new_value; } -static void -bell_guard (GtkWidget *toggle, - GladeXML *dialog) -{ - gtk_widget_set_sensitive (WID ("bell_custom_fileentry"), GTK_TOGGLE_BUTTON (toggle)->active); -} - -static gboolean -mnemonic_activate (GtkWidget *toggle, - gboolean group_cycling, - GtkWidget *entry) -{ - if (! group_cycling) - gtk_widget_grab_focus (entry); - return FALSE; -} - static void accessibility_button_clicked (GtkWidget *widget, gpointer data) @@ -184,7 +126,6 @@ setup_dialog (GladeXML *dialog, { GObject *peditor; GnomeProgram *program; - gchar *filename; /* load all the images */ program = gnome_program_get (); @@ -192,8 +133,6 @@ setup_dialog (GladeXML *dialog, capplet_init_stock_icons (); gtk_image_set_from_stock (GTK_IMAGE (WID ("repeat_image")), KEYBOARD_REPEAT, keyboard_capplet_icon_get_size()); gtk_image_set_from_stock (GTK_IMAGE (WID ("cursor_image")), KEYBOARD_CURSOR, keyboard_capplet_icon_get_size()); - gtk_image_set_from_stock (GTK_IMAGE (WID ("volume_image")), KEYBOARD_VOLUME, keyboard_capplet_icon_get_size()); - gtk_image_set_from_stock (GTK_IMAGE (WID ("bell_image")), KEYBOARD_BELL, keyboard_capplet_icon_get_size()); peditor = gconf_peditor_new_boolean (changeset, "/desktop/gnome/peripherals/keyboard/repeat", WID ("repeat_toggle"), NULL); @@ -220,23 +159,18 @@ setup_dialog (GladeXML *dialog, "conv-from-widget-cb", blink_from_widget, NULL); + /* Ergonomics */ peditor = gconf_peditor_new_boolean - (changeset, "/desktop/gnome/peripherals/keyboard/click", WID ("volume_toggle"), NULL); - gconf_peditor_widget_set_guard (GCONF_PROPERTY_EDITOR (peditor), WID ("volume_hbox")); + (changeset, "/desktop/gnome/keyboard_break/enabled", WID ("break_enabled_toggle"), NULL); + gconf_peditor_widget_set_guard (GCONF_PROPERTY_EDITOR (peditor), WID ("break_details_table")); gconf_peditor_new_numeric_range - (changeset, "/desktop/gnome/peripherals/keyboard/click_volume", WID ("volume_scale"), - "conv-to-widget-cb", gconf_value_int_to_float, - "conv-from-widget-cb", gconf_value_float_to_int, - NULL); - - g_signal_connect (G_OBJECT (WID ("bell_custom_radio")), "toggled", (GCallback) bell_guard, dialog); - peditor = gconf_peditor_new_select_radio - (changeset, "/desktop/gnome/peripherals/keyboard/bell_mode", - gtk_radio_button_get_group (GTK_RADIO_BUTTON (WID ("bell_disabled_radio"))), - "conv-to-widget-cb", bell_to_widget, - "conv-from-widget-cb", bell_from_widget, - NULL); - g_signal_connect (G_OBJECT (WID ("bell_custom_radio")), "mnemonic_activate", (GCallback) mnemonic_activate, WID ("bell_custom_entry")); + (changeset, "/desktop/gnome/keyboard_break/type_time", WID ("break_enabled_spin"), NULL); + gconf_peditor_new_numeric_range + (changeset, "/desktop/gnome/keyboard_break/warn_time", WID ("break_warning_spin"), NULL); + gconf_peditor_new_numeric_range + (changeset, "/desktop/gnome/keyboard_break/break_time", WID ("break_interval_spin"), NULL); + gconf_peditor_new_boolean + (changeset, "/desktop/gnome/keyboard_break/allow_unlock", WID ("break_postponement_toggle"), NULL); g_signal_connect (G_OBJECT (WID ("keyboard_dialog")), "response", (GCallback) dialog_response, changeset); } @@ -288,6 +222,7 @@ main (int argc, char **argv) N_("Just apply settings and quit (compatibility only; now handled by daemon)"), NULL }, { "get-legacy", '\0', POPT_ARG_NONE, &get_legacy, 0, N_("Retrieve and store legacy settings"), NULL }, +// { "set_page", { NULL, '\0', 0, NULL, 0, NULL, NULL } }; diff --git a/capplets/keyboard/gnome-keyboard-properties.glade b/capplets/keyboard/gnome-keyboard-properties.glade index 09f5cf4e2..5f29bad58 100644 --- a/capplets/keyboard/gnome-keyboard-properties.glade +++ b/capplets/keyboard/gnome-keyboard-properties.glade @@ -2,7 +2,6 @@ - Keyboard Preferences @@ -20,7 +19,7 @@ 0 - + True GTK_BUTTONBOX_END @@ -134,16 +133,14 @@ True GTK_POS_TOP False - 2 - 2 False - 4 + 12 True False - 4 + 12 @@ -154,7 +151,6 @@ - 8 True False 8 @@ -425,9 +421,9 @@ True - Repeat Keys + <b>Repeat Keys</b> False - False + True GTK_JUSTIFY_LEFT False False @@ -457,7 +453,6 @@ - 8 True False 8 @@ -506,7 +501,7 @@ True True - _Blinks in text boxes and fields + _Cursor blinks in text boxes and fields True GTK_RELIEF_NORMAL False @@ -648,9 +643,9 @@ True - Cursor Blinks + <b>Cursor Blinks</b> False - False + True GTK_JUSTIFY_LEFT False False @@ -697,178 +692,41 @@ - - 4 + True False - 4 + 12 - - 0 - 0.5 - GTK_SHADOW_NONE + + 12 + True + False + 12 - - 8 + True - False - 8 + 0 + 0.5 + GTK_SHADOW_NONE - + True False 0 - + + 24 True - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - 0 - False - True - - - - - - True - False - 8 - - - - True - False - 4 + 0 + 0.5 + GTK_SHADOW_NONE - - True - True - Clic_k on keypress - True - GTK_RELIEF_NORMAL - False - False - True - - - 0 - False - False - - - - - - True - False - 4 - - - - True - _Volume: - True - False - GTK_JUSTIFY_CENTER - False - False - 0.5 - 0.5 - 0 - 0 - - - - - - 0 - False - False - - - - - - True - quiet - False - False - GTK_JUSTIFY_CENTER - False - False - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - True - True - False - GTK_POS_TOP - 1 - GTK_UPDATE_DISCONTINUOUS - False - 0 0 100 10 10 0 - - - 0 - True - True - - - - - - True - loud - False - False - GTK_JUSTIFY_CENTER - False - False - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - 0 - False - False - + @@ -877,225 +735,311 @@ True - - - 0 - True - True - - - - - - - - True - Keypress Click - False - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - - - label_item - - - - - 0 - True - True - - - - - - True - 0 - 0.5 - GTK_SHADOW_NONE - - - - 8 - True - False - 8 - - - - True - False - 0 - - True - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - 0 - False - True - - - - - - True - False - 8 - - - + + 3 True False - 4 + 3 - + True - True - _Off - True - GTK_RELIEF_NORMAL - False - False - True - - - 0 - False - False - - - - - - True - True - Bee_p - True - GTK_RELIEF_NORMAL - False - False - True - bell_disabled_radio - - - 0 - False - False - - - - - + 4 + 3 False - 4 + 3 + 12 - - False - False - C_ustom: + + True + Duration of work before forcing a break + True + 1 + 0 + False + GTK_UPDATE_ALWAYS + False + False + 1 1 100000 1 10 10 + + + 1 + 2 + 0 + 1 + + + + + + + + True + minutes + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 2 + 3 + 1 + 2 + + + + + + + True + _Warning time is + True + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + break_warning_spin + + + 0 + 1 + 1 + 2 + fill + + + + + + + True + minutes + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 2 + 3 + 0 + 1 + + + + + + + True + _Work interval lasts + True + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + break_enabled_spin + + + 0 + 1 + 0 + 1 + fill + + + + + + + True + Duration of warning before starting a break + True + 1 + 0 + False + GTK_UPDATE_ALWAYS + False + False + 1 1 100000 1 10 10 + + + 1 + 2 + 1 + 2 + + + + + + + + True + _Break interval lasts + True + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + break_interval_spin + + + 0 + 1 + 2 + 3 + fill + + + + + + + True + Duration of the break when typing is disallowed + True + 1 + 0 + False + GTK_UPDATE_ALWAYS + False + False + 1 1 100000 1 10 10 + + + 1 + 2 + 2 + 3 + fill + + + + + + + True + minutes + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 2 + 3 + 2 + 3 + fill + + + + + + + True + Check if breaks are allowed to be postponed + True + _Allow postponing of breaks True GTK_RELIEF_NORMAL False False True - bell_disabled_radio - 0 - False - False - - - - - - True - False - 10 - False - True - - - - True - True - True - True - 0 - - True - * - False - - - - - 0 - True - True + 0 + 3 + 3 + 4 + fill + 0 - False - False + True + True 0 - False + True True + + + + + True + Lock screen after a certain duration to help prevent repetitive keyboard use injuries + True + True + GTK_RELIEF_NORMAL + False + False + True + + + + True + <b>_Lock screen to enforce typing break</b> + True + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + - 0 - True - True + label_item - - - - - True - Keyboard Bell - False - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - - label_item + 0 + True + True @@ -1113,12 +1057,12 @@ - + True - Sound + Typing Break False False - GTK_JUSTIFY_CENTER + GTK_JUSTIFY_LEFT False False 0.5 diff --git a/configure.in b/configure.in index 884907b75..afba19cc5 100644 --- a/configure.in +++ b/configure.in @@ -252,6 +252,9 @@ AC_SUBST(GNOMECC_CAPPLETS_CLEANFILES) AC_SUBST(GNOMECC_CAPPLETS_CFLAGS) AC_SUBST(GNOMECC_CAPPLETS_LIBS) +AC_SUBST(GNOME_CFLAGS) +AC_SUBST(GNOME_LIBS) + AC_SUBST(GNOME_SETTINGS_DAEMON_CFLAGS) AC_SUBST(GNOME_SETTINGS_DAEMON_LIBS) @@ -370,6 +373,7 @@ capplets/accessibility/keyboard/Makefile capplets/accessibility/at-properties/Makefile capplets/network/Makefile capplets/windows/Makefile +typing-break/Makefile schemas/Makefile libsounds/Makefile vfs-methods/Makefile diff --git a/gnome-settings-daemon/ChangeLog b/gnome-settings-daemon/ChangeLog index 491de6f21..2909a7c88 100644 --- a/gnome-settings-daemon/ChangeLog +++ b/gnome-settings-daemon/ChangeLog @@ -1,3 +1,8 @@ +Thu Jun 26 07:37:20 2003 Jonathan Blandford + + * gnome-settings-typing-break.c: new module to handle the + typing-break-monitor. + 2003-06-24 Jody Goldberg * Release 2.3.3 diff --git a/gnome-settings-daemon/Makefile.am b/gnome-settings-daemon/Makefile.am index ad2f67d00..296d2166c 100644 --- a/gnome-settings-daemon/Makefile.am +++ b/gnome-settings-daemon/Makefile.am @@ -40,6 +40,10 @@ gnome_settings_daemon_SOURCES = \ xsettings-manager.h \ gnome-settings-keybindings.c \ gnome-settings-keybindings.h \ + gnome-settings-typing-break.c \ + gnome-settings-typing-break.h \ + reaper.c \ + reaper.h \ $(CORBA_GENERATED) # $(AccessX_files) diff --git a/gnome-settings-daemon/gnome-settings-daemon.c b/gnome-settings-daemon/gnome-settings-daemon.c index 085618a25..590094de5 100644 --- a/gnome-settings-daemon/gnome-settings-daemon.c +++ b/gnome-settings-daemon/gnome-settings-daemon.c @@ -53,6 +53,7 @@ #include "gnome-settings-keybindings.h" #include "gnome-settings-gtk1theme.h" #include "gnome-settings-xrdb.h" +#include "gnome-settings-typing-break.h" #include "GNOME_SettingsDaemon.h" @@ -280,6 +281,7 @@ gnome_settings_daemon_new (void) gnome_settings_keybindings_init (client); gnome_settings_gtk1_theme_init (client); gnome_settings_xrdb_init (client); + gnome_settings_typing_break_init (client); for (list = directories; list; list = list->next) { @@ -329,6 +331,7 @@ gnome_settings_daemon_new (void) gnome_settings_keybindings_load (client); gnome_settings_gtk1_theme_load (client); gnome_settings_xrdb_load (client); + gnome_settings_typing_break_load (client); return G_OBJECT (daemon); } diff --git a/gnome-settings-daemon/gnome-settings-typing-break.c b/gnome-settings-daemon/gnome-settings-typing-break.c new file mode 100644 index 000000000..5d32f43f9 --- /dev/null +++ b/gnome-settings-daemon/gnome-settings-typing-break.c @@ -0,0 +1,97 @@ +#include "gnome-settings-daemon.h" +#include "gnome-settings-typing-break.h" +#include +#include +#include +#include "reaper.h" +#include + +pid_t typing_monitor_pid = 0; +guint typing_monitor_idle_id = 0; + +static gboolean +typing_break_timeout (gpointer data) +{ + if (typing_monitor_pid > 0) + kill (typing_monitor_pid, SIGKILL); + + typing_monitor_idle_id = 0; + + return FALSE; +} + +static void +setup_typing_break (gboolean enabled) +{ + if (enabled) + { + if (typing_monitor_idle_id != 0) + g_source_remove (typing_monitor_idle_id); + if (typing_monitor_pid == 0) + { + GError *error = NULL; + gchar *argv[] = { "gnome-typing-monitor", NULL }; + + if (! g_spawn_async ("/", + argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL | + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, + &typing_monitor_pid, + &error)) + { + /* FIXME: put up a warning */ + g_print ("failed: %s\n", error->message); + g_error_free (error); + typing_monitor_pid = 0; + } + } + } + else + { + if (typing_monitor_pid != 0) + { + typing_monitor_idle_id = g_timeout_add (3000, typing_break_timeout, NULL); + } + } +} + +static void +child_exited_callback (VteReaper *reaper, + gint pid, + gint exit_status, + gpointer user_data) +{ + if (pid == typing_monitor_pid) + { + typing_monitor_pid = 0; + } +} + +static void +typing_break_callback (GConfEntry *entry) +{ + if (! strcmp (entry->key, "/desktop/gnome/typing_break/enabled")) + { + if (entry->value->type == GCONF_VALUE_BOOL) + setup_typing_break (gconf_value_get_bool (entry->value)); + } +} + +void +gnome_settings_typing_break_init (GConfClient *client) +{ + VteReaper *reaper; + + reaper = vte_reaper_get(); + g_signal_connect (G_OBJECT (reaper), "child_exited", child_exited_callback, NULL); + gnome_settings_daemon_register_callback ("/desktop/gnome/typing_break", typing_break_callback); +} + + +void +gnome_settings_typing_break_load (GConfClient *client) +{ + if (gconf_client_get_bool (client, "/desktop/gnome/typing_break/enabled", NULL)) + setup_typing_break (TRUE); +} diff --git a/gnome-settings-daemon/gnome-settings-typing-break.h b/gnome-settings-daemon/gnome-settings-typing-break.h new file mode 100644 index 000000000..92f8feaf2 --- /dev/null +++ b/gnome-settings-daemon/gnome-settings-typing-break.h @@ -0,0 +1,25 @@ +/* + * Copyright © 2003 Jonathan Blandford + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Red Hat not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. Red Hat makes no representations about the + * suitability of this software for any purpose. It is provided "as is" + * without express or implied warranty. + * + * Authors: Jonathan Blandford + */ + +#ifndef TYPING_BREAK_SETTINGS_H +#define TYPING_BREAK_SETTINGS_H + +#include + +void gnome_settings_typing_break_init (GConfClient *client); +void gnome_settings_typing_break_load (GConfClient *client); + +#endif diff --git a/gnome-settings-daemon/reaper.c b/gnome-settings-daemon/reaper.c new file mode 100644 index 000000000..c31adea4b --- /dev/null +++ b/gnome-settings-daemon/reaper.c @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2002 Red Hat, Inc. + * + * This 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ident "$Id$" +#include "../config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "reaper.h" + +#ifdef HAVE_LOCALE_H +#include +#else +#define bindtextdomain(package,dir) +#endif + +#ifdef ENABLE_NLS +#include +#define _(String) dgettext(PACKAGE, String) +#else +#define _(String) String +#endif + +static VteReaper *singleton_reaper = NULL; +struct reaper_info { + int signum; + pid_t pid; + int status; +}; + +static void +vte_reaper_signal_handler(int signum) +{ + struct reaper_info info; + int status; + + /* This might become more general-purpose in the future, but for now + * just forget about signals other than SIGCHLD. */ + info.signum = signum; + if (signum != SIGCHLD) { + return; + } + + if ((singleton_reaper != NULL) && (singleton_reaper->iopipe[0] != -1)) { + info.pid = waitpid(-1, &status, WNOHANG); + if (info.pid != -1) { + info.status = status; + if (write(singleton_reaper->iopipe[1], "", 0) == 0) { + write(singleton_reaper->iopipe[1], + &info, sizeof(info)); + } + } + } +} + +static gboolean +vte_reaper_emit_signal(GIOChannel *channel, GIOCondition condition, + gpointer data) +{ + struct reaper_info info; + if (condition != G_IO_IN) { + return FALSE; + } + g_assert(data == singleton_reaper); + read(singleton_reaper->iopipe[0], &info, sizeof(info)); + if (info.signum == SIGCHLD) { + g_signal_emit_by_name(data, "child-exited", + info.pid, info.status); + } + return TRUE; +} + +static void +vte_reaper_channel_destroyed(gpointer data) +{ + /* no-op */ +} + +static void +vte_reaper_init(VteReaper *reaper, gpointer *klass) +{ + struct sigaction action; + int ret; + + /* Open the signal pipe. */ + ret = pipe(reaper->iopipe); + if (ret == -1) { + g_error(_("Error creating signal pipe.")); + } + + /* Create the channel. */ + reaper->channel = g_io_channel_unix_new(reaper->iopipe[0]); + + /* Add the channel to the source list. */ + g_io_add_watch_full(reaper->channel, + G_PRIORITY_HIGH, + G_IO_IN, + vte_reaper_emit_signal, + reaper, + vte_reaper_channel_destroyed); + + /* Set the signal handler. */ + action.sa_handler = vte_reaper_signal_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_RESTART | SA_NOCLDSTOP; + sigaction(SIGCHLD, &action, NULL); +} + +static void +vte_reaper_finalize(GObject *reaper) +{ + GObjectClass *object_class; + struct sigaction action, old_action; + + /* Reset the signal handler if we still have it hooked. */ + action.sa_handler = SIG_DFL; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_RESTART | SA_NOCLDSTOP; + sigaction(SIGCHLD, NULL, &old_action); + if (old_action.sa_handler == vte_reaper_signal_handler) { + sigaction(SIGCHLD, &action, NULL); + } + + /* Remove the channel from the source list. */ + g_source_remove_by_user_data(reaper); + + /* Remove the channel. */ + g_io_channel_unref((VTE_REAPER(reaper))->channel); + + /* Close the pipes. */ + close((VTE_REAPER(reaper))->iopipe[1]); + close((VTE_REAPER(reaper))->iopipe[0]); + + /* Call the inherited destructor. */ + object_class = g_type_class_peek(G_TYPE_OBJECT); + if (G_OBJECT_CLASS(object_class)->finalize) { + (G_OBJECT_CLASS(object_class))->finalize(reaper); + } + singleton_reaper = NULL; +} + +static void +vte_reaper_class_init(VteReaperClass *klass, gpointer data) +{ + GObjectClass *gobject_class; + + klass->child_exited_signal = g_signal_new("child-exited", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + gtk_marshal_NONE__INT_INT, + G_TYPE_NONE, + 2, G_TYPE_INT, G_TYPE_INT); + + gobject_class = G_OBJECT_CLASS(klass); + gobject_class->finalize = vte_reaper_finalize; +} + +GType +vte_reaper_get_type(void) +{ + static GType reaper_type = 0; + static GTypeInfo reaper_type_info = { + sizeof(VteReaperClass), + + (GBaseInitFunc)NULL, + (GBaseFinalizeFunc)NULL, + + (GClassInitFunc)vte_reaper_class_init, + (GClassFinalizeFunc)NULL, + NULL, + + sizeof(VteReaper), + 0, + (GInstanceInitFunc) vte_reaper_init, + + (const GTypeValueTable *) NULL, + }; + if (reaper_type == 0) { + reaper_type = g_type_register_static(G_TYPE_OBJECT, + "VteReaper", + &reaper_type_info, + 0); + } + return reaper_type; +} + +/** + * vte_reaper_get: + * + * Finds the address of the global #VteReaper object, creating the object if + * necessary. + * + * Returns: the global #VteReaper object, which should not be unreffed. + */ +VteReaper * +vte_reaper_get(void) +{ + if (!VTE_IS_REAPER(singleton_reaper)) { + singleton_reaper = g_object_new(VTE_TYPE_REAPER, NULL); + } + return singleton_reaper; +} + +#ifdef REAPER_MAIN +GMainContext *context; +GMainLoop *loop; +pid_t child; + +static void +child_exited(GObject *object, int pid, int status, gpointer data) +{ + g_print("[parent] Child with pid %d exited with code %d, " + "was waiting for %d.\n", pid, status, GPOINTER_TO_INT(data)); + if (child == pid) { + g_print("[parent] Quitting.\n"); + g_main_loop_quit(loop); + } +} + +int +main(int argc, char **argv) +{ + VteReaper *reaper; + pid_t p, q; + + _vte_debug_parse_string(getenv("VTE_DEBUG_FLAGS")); + + g_type_init(); + context = g_main_context_default(); + loop = g_main_loop_new(context, FALSE); + reaper = vte_reaper_get(); + + g_print("[parent] Forking.\n"); + p = fork(); + switch (p) { + case -1: + g_print("[parent] Fork failed.\n"); + g_assert_not_reached(); + break; + case 0: + g_print("[child] Going to sleep.\n"); + sleep(10); + g_print("[child] Quitting.\n"); + _exit(30); + break; + default: + g_print("[parent] Starting to wait for %d.\n", p); + child = p; + g_signal_connect(G_OBJECT(reaper), + "child-exited", + G_CALLBACK(child_exited), + GINT_TO_POINTER(child)); + break; + } + + g_print("[parent] Forking.\n"); + q = fork(); + switch (q) { + case -1: + g_print("[parent] Fork failed.\n"); + g_assert_not_reached(); + break; + case 0: + g_print("[child] Going to sleep.\n"); + sleep(5); + _exit(5); + break; + default: + g_print("[parent] Not waiting for %d.\n", q); + break; + } + + + g_main_loop_run(loop); + + reaper = vte_reaper_get(); + g_object_unref(VTE_REAPER(reaper)); + + return 0; +} +#endif diff --git a/gnome-settings-daemon/reaper.h b/gnome-settings-daemon/reaper.h new file mode 100644 index 000000000..fb8521f69 --- /dev/null +++ b/gnome-settings-daemon/reaper.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2002 Red Hat, Inc. + * + * This 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef vte_reaper_h_included +#define vte_reaper_h_included + +#ident "$Id$" + +#include +#include +#include +#include + +G_BEGIN_DECLS + +struct _VteReaper { + GObject object; + GIOChannel *channel; + int iopipe[2]; +}; +typedef struct _VteReaper VteReaper; + +struct _VteReaperClass { + GObjectClass parent_class; + guint child_exited_signal; +}; +typedef struct _VteReaperClass VteReaperClass; + +GType vte_reaper_get_type(void); + +#define VTE_TYPE_REAPER (vte_reaper_get_type()) +#define VTE_REAPER(obj) (GTK_CHECK_CAST((obj), \ + VTE_TYPE_REAPER, \ + VteReaper)) +#define VTE_REAPER_CLASS(klass) GTK_CHECK_CLASS_CAST((klass), \ + VTE_TYPE_REAPER, \ + VteReaperClass) +#define VTE_IS_REAPER(obj) GTK_CHECK_TYPE((obj), VTE_TYPE_REAPER) +#define VTE_IS_REAPER_CLASS(klass) GTK_CHECK_CLASS_TYPE((klass), \ + VTE_TYPE_REAPER) +#define VTE_REAPER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + VTE_TYPE_REAPER, \ + VteReaperClass)) + +VteReaper *vte_reaper_get(void); + +G_END_DECLS + +#endif diff --git a/typing-break/.cvsignore b/typing-break/.cvsignore new file mode 100644 index 000000000..b798e21da --- /dev/null +++ b/typing-break/.cvsignore @@ -0,0 +1,3 @@ +Makefile +Makefile.in +gnome-typing-monitor diff --git a/typing-break/Makefile.am b/typing-break/Makefile.am new file mode 100644 index 000000000..44c1b10f8 --- /dev/null +++ b/typing-break/Makefile.am @@ -0,0 +1,28 @@ +INCLUDES = \ + @GNOME_CFLAGS@ \ + -DGNOMELOCALEDIR="\"$(datadir)/locale\"" \ + -DIMAGEDIR=\"$(GNOMECC_PIXMAPS_DIR)\" + +bin_PROGRAMS = gnome-typing-monitor + +gnome_typing_monitor_SOURCES = \ + main.c \ + drwright.c \ + drwright.h \ + drw-break-window.c \ + drw-break-window.h \ + drw-monitor.c \ + drw-monitor.h \ + drw-intl.h \ + eggtrayicon.c \ + eggtrayicon.h \ + drw-selection.c \ + drw-selection.h + +gnome_typing_monitor_LDADD = @GNOME_LIBS@ -L/usr/X11R6/lib -lXss +gnome_typing_monitor_LDFLAGS = -export-dynamic + +imagedir = $(GNOMECC_PIXMAPS_DIR) +image_DATA = stop.png bar.png bar-red.png bar-green.png bar-disabled.png ocean-stripes.png + +EXTRA_DIST = $(image_DATA) diff --git a/typing-break/bar-disabled.png b/typing-break/bar-disabled.png new file mode 100644 index 000000000..36bef4b4e Binary files /dev/null and b/typing-break/bar-disabled.png differ diff --git a/typing-break/bar-green.png b/typing-break/bar-green.png new file mode 100644 index 000000000..f1755b303 Binary files /dev/null and b/typing-break/bar-green.png differ diff --git a/typing-break/bar-red.png b/typing-break/bar-red.png new file mode 100644 index 000000000..06118d14b Binary files /dev/null and b/typing-break/bar-red.png differ diff --git a/typing-break/bar.png b/typing-break/bar.png new file mode 100644 index 000000000..5587fe65b Binary files /dev/null and b/typing-break/bar.png differ diff --git a/typing-break/drw-break-window.c b/typing-break/drw-break-window.c new file mode 100644 index 000000000..1d228c11b --- /dev/null +++ b/typing-break/drw-break-window.c @@ -0,0 +1,689 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Richard Hult + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include "drw-break-window.h" +#include "drw-intl.h" + +struct _DrwBreakWindowPriv { + GtkWidget *clock_label; + GtkWidget *break_label; + GtkWidget *image; + + GtkWidget *unlock_entry; + GtkWidget *unlock_button; + + GTimer *timer; + + gint break_time; + + gchar *break_text; + guint clock_timeout_id; + guint unlock_timeout_id; +}; + +#define UNLOCK_CANCEL 30*1000 + +/* Signals */ +enum { + DONE, + POSTPONE, + LAST_SIGNAL +}; + +static void drw_break_window_class_init (DrwBreakWindowClass *klass); +static void drw_break_window_init (DrwBreakWindow *window); +static void drw_break_window_finalize (GObject *object); +static GdkPixbuf * create_tile_pixbuf (GdkPixbuf *dest_pixbuf, + GdkPixbuf *src_pixbuf, + GdkRectangle *field_geom, + guint alpha, + GdkColor *bg_color); +static gboolean clock_timeout_cb (DrwBreakWindow *window); +static void unlock_clicked_cb (GtkWidget *button, + GtkWidget *window); +static gboolean label_expose_event_cb (GtkLabel *label, + GdkEventExpose *event, + gpointer user_data); +static void label_size_request_cb (GtkLabel *label, + GtkRequisition *requisition, + gpointer user_data); + + +static GObjectClass *parent_class; +static guint signals[LAST_SIGNAL]; + +GType +drw_break_window_get_type (void) +{ + static GType object_type = 0; + + if (!object_type) { + static const GTypeInfo object_info = { + sizeof (DrwBreakWindowClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) drw_break_window_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (DrwBreakWindow), + 0, /* n_preallocs */ + (GInstanceInitFunc) drw_break_window_init, + }; + + object_type = g_type_register_static (GTK_TYPE_WINDOW, + "DrwBreakWindow", + &object_info, + 0); + } + + return object_type; +} + +static void +drw_break_window_class_init (DrwBreakWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + + object_class->finalize = drw_break_window_finalize; + + signals[POSTPONE] = + g_signal_new ("postpone", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[DONE] = + g_signal_new ("done", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +drw_break_window_init (DrwBreakWindow *window) +{ + DrwBreakWindowPriv *priv; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *align; + gchar *str; + GdkPixbuf *tmp_pixbuf, *pixbuf, *tile_pixbuf; + GdkPixmap *pixmap; + GdkRectangle rect; + GdkColor color; + GtkWidget *outer_vbox; + GtkWidget *button_box; + gboolean allow_unlock; + + priv = g_new0 (DrwBreakWindowPriv, 1); + window->priv = priv; + + priv->break_time = 60 * gconf_client_get_int (gconf_client_get_default (), + "/desktop/gnome/typing_break/break_time", + NULL); + + allow_unlock = gconf_client_get_bool (gconf_client_get_default (), + "/desktop/gnome/typing_break/allow_unlock", + NULL); + + GTK_WINDOW (window)->type = GTK_WINDOW_POPUP; + + gtk_window_set_default_size (GTK_WINDOW (window), + gdk_screen_width (), + gdk_screen_height ()); + + gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE); + gtk_widget_realize (GTK_WIDGET (window)); + + tmp_pixbuf = gdk_pixbuf_get_from_drawable (NULL, + gdk_get_default_root_window (), + gdk_colormap_get_system (), + 0, + 0, + 0, + 0, + gdk_screen_width (), + gdk_screen_height ()); + + pixbuf = gdk_pixbuf_new_from_file (IMAGEDIR "/ocean-stripes.png", NULL); + + rect.x = 0; + rect.y = 0; + rect.width = gdk_screen_width (); + rect.height = gdk_screen_height (); + + color.red = 0; + color.blue = 0; + color.green = 0; + + tile_pixbuf = create_tile_pixbuf (NULL, + pixbuf, + &rect, + 155, + &color); + + g_object_unref (pixbuf); + + gdk_pixbuf_composite (tile_pixbuf, + tmp_pixbuf, + 0, + 0, + gdk_screen_width (), + gdk_screen_height (), + 0, + 0, + 1, + 1, + GDK_INTERP_NEAREST, + 225); + + g_object_unref (tile_pixbuf); + + pixmap = gdk_pixmap_new (GTK_WIDGET (window)->window, + gdk_screen_width (), + gdk_screen_height (), + -1); + + gdk_pixbuf_render_to_drawable_alpha (tmp_pixbuf, + pixmap, + 0, + 0, + 0, + 0, + gdk_screen_width (), + gdk_screen_height (), + GDK_PIXBUF_ALPHA_BILEVEL, + 0, + GDK_RGB_DITHER_NONE, + 0, + 0); + g_object_unref (tmp_pixbuf); + + gdk_window_set_back_pixmap (GTK_WIDGET (window)->window, pixmap, FALSE); + g_object_unref (pixmap); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE); + gtk_widget_show (frame); + + align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_widget_show (align); + + outer_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (outer_vbox); + + gtk_container_add (GTK_CONTAINER (window), outer_vbox); + + gtk_box_pack_start (GTK_BOX (outer_vbox), align, TRUE, TRUE, 0); + + if (allow_unlock) { + button_box = gtk_hbox_new (FALSE, 0); + gtk_widget_show (button_box); + + gtk_container_set_border_width (GTK_CONTAINER (button_box), 12); + + priv->unlock_button = gtk_button_new_with_label (_("Postpone break")); + gtk_widget_show (priv->unlock_button); + + g_signal_connect (priv->unlock_button, + "clicked", + G_CALLBACK (unlock_clicked_cb), + window); + + gtk_box_pack_end (GTK_BOX (button_box), priv->unlock_button, FALSE, TRUE, 0); + + priv->unlock_entry = gtk_entry_new (); + gtk_entry_set_has_frame (GTK_ENTRY (priv->unlock_entry), FALSE); + + gtk_box_pack_end (GTK_BOX (button_box), priv->unlock_entry, FALSE, TRUE, 4); + + gtk_box_pack_end (GTK_BOX (outer_vbox), button_box, FALSE, TRUE, 0); + } + + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox); + + gtk_container_add (GTK_CONTAINER (align), frame); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + priv->break_label = gtk_label_new (NULL); + gtk_widget_show (priv->break_label); + + g_signal_connect (priv->break_label, + "expose_event", + G_CALLBACK (label_expose_event_cb), + NULL); + + g_signal_connect_after (priv->break_label, + "size_request", + G_CALLBACK (label_size_request_cb), + NULL); + + str = g_strdup_printf ("%s", + _("Take a break!")); + gtk_label_set_markup (GTK_LABEL (priv->break_label), str); + g_free (str); + + gtk_box_pack_start (GTK_BOX (vbox), priv->break_label, FALSE, FALSE, 12); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 0); + + priv->image = gtk_image_new_from_file (IMAGEDIR "/stop.png"); + gtk_misc_set_alignment (GTK_MISC (priv->image), 1, 0.5); + gtk_widget_show (priv->image); + gtk_box_pack_start (GTK_BOX (hbox), priv->image, TRUE, TRUE, 8); + + priv->clock_label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (priv->clock_label), 0, 0.5); + gtk_widget_show (priv->clock_label); + gtk_box_pack_start (GTK_BOX (hbox), priv->clock_label, TRUE, TRUE, 8); + + g_signal_connect (priv->clock_label, + "expose_event", + G_CALLBACK (label_expose_event_cb), + NULL); + + g_signal_connect_after (priv->clock_label, + "size_request", + G_CALLBACK (label_size_request_cb), + NULL); + + gtk_window_stick (GTK_WINDOW (window)); + + priv->timer = g_timer_new (); + + /* Make sure we have a valid time label from the start. */ + clock_timeout_cb (window); + + priv->clock_timeout_id = g_timeout_add (1000, + (GSourceFunc) clock_timeout_cb, + window); +} + +static void +drw_break_window_finalize (GObject *object) +{ + DrwBreakWindow *window = DRW_BREAK_WINDOW (object); + DrwBreakWindowPriv *priv; + + priv = window->priv; + + if (priv->clock_timeout_id != 0) { + g_source_remove (priv->clock_timeout_id); + } + + if (priv->unlock_timeout_id != 0) { + g_source_remove (priv->unlock_timeout_id); + } + + g_free (priv); + window->priv = NULL; + + if (G_OBJECT_CLASS (parent_class)->finalize) { + (* G_OBJECT_CLASS (parent_class)->finalize) (object); + } +} + +GtkWidget * +drw_break_window_new (void) +{ + return g_object_new (DRW_TYPE_BREAK_WINDOW, NULL); +} + +static GdkPixbuf * +create_tile_pixbuf (GdkPixbuf *dest_pixbuf, + GdkPixbuf *src_pixbuf, + GdkRectangle *field_geom, + guint alpha, + GdkColor *bg_color) +{ + gboolean need_composite; + gboolean use_simple; + gdouble cx, cy; + gdouble colorv; + gint pwidth, pheight; + + need_composite = (alpha < 255 || gdk_pixbuf_get_has_alpha (src_pixbuf)); + use_simple = (dest_pixbuf == NULL); + + if (dest_pixbuf == NULL) + dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, field_geom->width, field_geom->height); + + if (need_composite && use_simple) + colorv = ((bg_color->red & 0xff00) << 8) | + (bg_color->green & 0xff00) | + ((bg_color->blue & 0xff00) >> 8); + else + colorv = 0; + + pwidth = gdk_pixbuf_get_width (src_pixbuf); + pheight = gdk_pixbuf_get_height (src_pixbuf); + + for (cy = 0; cy < field_geom->height; cy += pheight) { + for (cx = 0; cx < field_geom->width; cx += pwidth) { + if (need_composite && !use_simple) + gdk_pixbuf_composite + (src_pixbuf, dest_pixbuf, + cx, cy, + MIN (pwidth, field_geom->width - cx), + MIN (pheight, field_geom->height - cy), + cx, cy, + 1.0, 1.0, + GDK_INTERP_BILINEAR, + alpha); + else if (need_composite && use_simple) + gdk_pixbuf_composite_color + (src_pixbuf, dest_pixbuf, + cx, cy, + MIN (pwidth, field_geom->width - cx), + MIN (pheight, field_geom->height - cy), + cx, cy, + 1.0, 1.0, + GDK_INTERP_BILINEAR, + alpha, + 65536, 65536, 65536, + colorv, colorv); + else + gdk_pixbuf_copy_area + (src_pixbuf, + 0, 0, + MIN (pwidth, field_geom->width - cx), + MIN (pheight, field_geom->height - cy), + dest_pixbuf, + cx, cy); + } + } + + return dest_pixbuf; +} + +static gboolean +clock_timeout_cb (DrwBreakWindow *window) +{ + DrwBreakWindowPriv *priv; + gchar *txt; + gint minutes; + gint seconds; + + g_return_val_if_fail (DRW_IS_BREAK_WINDOW (window), FALSE); + + priv = window->priv; + + seconds = 1 + priv->break_time - g_timer_elapsed (priv->timer, NULL); + seconds = MAX (0, seconds); + + if (seconds == 0) { + /* Zero this out so the finalizer doesn't try to remove the + * source, which would be done in the timeout callback == + * no-no. + */ + priv->clock_timeout_id = 0; + + g_signal_emit (window, signals[DONE], 0, NULL); + //gtk_widget_destroy (GTK_WIDGET (window)); + + return FALSE; + } + + minutes = seconds / 60; + seconds -= minutes * 60; + + txt = g_strdup_printf ("%d:%02d", + minutes, + seconds); + gtk_label_set_markup (GTK_LABEL (priv->clock_label), txt); + g_free (txt); + + return TRUE; +} + +static void +unlock_entry_activate_cb (GtkWidget *entry, + DrwBreakWindow *window) +{ + const gchar *str; + const gchar *phrase; + + str = gtk_entry_get_text (GTK_ENTRY (entry)); + + phrase = gconf_client_get_string (gconf_client_get_default (), + "/desktop/gnome/typing_break/unlock_phrase", + NULL); + + if (!strcmp (str, phrase)) { + g_signal_emit (window, signals[POSTPONE], 0, NULL); + //gtk_widget_destroy (GTK_WIDGET (window)); + return; + } + + gtk_entry_set_text (GTK_ENTRY (entry), ""); +} + +static gboolean +grab_on_window (GdkWindow *window, + guint32 activate_time) +{ + if ((gdk_pointer_grab (window, TRUE, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK, + NULL, NULL, activate_time) == 0)) { + if (gdk_keyboard_grab (window, TRUE, + activate_time) == 0) + return TRUE; + else { + gdk_pointer_ungrab (activate_time); + return FALSE; + } + } + + return FALSE; +} + +static gboolean +unlock_cancel_cb (DrwBreakWindow *window) +{ + DrwBreakWindowPriv *priv; + + priv = window->priv; + + gtk_entry_set_text (GTK_ENTRY (priv->unlock_entry), ""); + gtk_widget_hide (priv->unlock_entry); + + priv->unlock_timeout_id = 0; + + return FALSE; +} + +static gboolean +unlock_entry_key_press_event_cb (GtkEntry *entry, + GdkEventKey *event, + DrwBreakWindow *window) +{ + DrwBreakWindowPriv *priv; + + priv = window->priv; + + if (event->keyval == GDK_Escape) { + if (priv->unlock_timeout_id) { + g_source_remove (priv->unlock_timeout_id); + } + + unlock_cancel_cb (window); + + return TRUE; + } + + g_source_remove (priv->unlock_timeout_id); + + priv->unlock_timeout_id = g_timeout_add (UNLOCK_CANCEL, (GSourceFunc) unlock_cancel_cb, window); + + return FALSE; +} + +static void +unlock_clicked_cb (GtkWidget *button, + GtkWidget *window) +{ + DrwBreakWindow *bw = DRW_BREAK_WINDOW (window); + DrwBreakWindowPriv *priv = bw->priv; + gchar *phrase; + + phrase = gconf_client_get_string (gconf_client_get_default (), + "/desktop/gnome/typing_break/unlock_phrase", + NULL); + + if (!phrase || !phrase[0]) { + g_signal_emit (window, signals[POSTPONE], 0, NULL); + + //gtk_widget_destroy (window); + return; + } + + if (GTK_WIDGET_VISIBLE (priv->unlock_entry)) { + gtk_widget_activate (priv->unlock_entry); + return; + } + + gtk_widget_show (priv->unlock_entry); + + priv->unlock_timeout_id = g_timeout_add (UNLOCK_CANCEL, (GSourceFunc) unlock_cancel_cb, bw); + + grab_on_window (priv->unlock_entry->window, gtk_get_current_event_time ()); + + gtk_widget_grab_focus (priv->unlock_entry); + + g_signal_connect (priv->unlock_entry, + "activate", + G_CALLBACK (unlock_entry_activate_cb), + bw); + + g_signal_connect (priv->unlock_entry, + "key_press_event", + G_CALLBACK (unlock_entry_key_press_event_cb), + bw); +} + +static void +get_layout_location (GtkLabel *label, + gint *xp, + gint *yp) +{ + GtkMisc *misc; + GtkWidget *widget; + gfloat xalign; + gint x, y; + + misc = GTK_MISC (label); + widget = GTK_WIDGET (label); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) { + xalign = misc->xalign; + } else { + xalign = 1.0 - misc->xalign; + } + + x = floor (widget->allocation.x + (int)misc->xpad + + ((widget->allocation.width - widget->requisition.width - 1) * xalign) + + 0.5); + + y = floor (widget->allocation.y + (int)misc->ypad + + ((widget->allocation.height - widget->requisition.height - 1) * misc->yalign) + + 0.5); + + if (xp) { + *xp = x; + } + + if (yp) { + *yp = y; + } +} + +static gboolean +label_expose_event_cb (GtkLabel *label, + GdkEventExpose *event, + gpointer user_data) +{ + gint x, y; + GdkColor color; + GtkWidget *widget; + GdkGC *gc; + + color.red = 0; + color.green = 0; + color.blue = 0; + color.pixel = 0; + + get_layout_location (label, &x, &y); + + widget = GTK_WIDGET (label); + gc = gdk_gc_new (widget->window); + gdk_gc_set_rgb_fg_color (gc, &color); + gdk_gc_set_clip_rectangle (gc, &event->area); + + gdk_draw_layout_with_colors (widget->window, + gc, + x + 1, + y + 1, + label->layout, + &color, + NULL); + g_object_unref (gc); + + gtk_paint_layout (widget->style, + widget->window, + GTK_WIDGET_STATE (widget), + FALSE, + &event->area, + widget, + "label", + x, y, + label->layout); + + return TRUE; +} + +static void +label_size_request_cb (GtkLabel *label, + GtkRequisition *requisition, + gpointer user_data) +{ + requisition->width += 1; + requisition->height += 1; +} diff --git a/typing-break/drw-break-window.h b/typing-break/drw-break-window.h new file mode 100644 index 000000000..0cba8f899 --- /dev/null +++ b/typing-break/drw-break-window.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Richard Hult + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DRW_BREAK_WINDOW_H__ +#define __DRW_BREAK_WINDOW_H__ + +#include + +#define DRW_TYPE_BREAK_WINDOW (drw_break_window_get_type ()) +#define DRW_BREAK_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DRW_TYPE_BREAK_WINDOW, DrwBreakWindow)) +#define DRW_BREAK_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), DRW_TYPE_BREAK_WINDOW, DrwBreakWindowClass)) +#define DRW_IS_BREAK_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DRW_TYPE_BREAK_WINDOW)) +#define DRW_IS_BREAK_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DRW_TYPE_BREAK_WINDOW)) +#define DRW_BREAK_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DRW_TYPE_BREAK_WINDOW, DrwBreakWindowClass)) + +typedef struct _DrwBreakWindow DrwBreakWindow; +typedef struct _DrwBreakWindowClass DrwBreakWindowClass; +typedef struct _DrwBreakWindowPriv DrwBreakWindowPriv; + +struct _DrwBreakWindow { + GtkWindow parent; + + DrwBreakWindowPriv *priv; +}; + +struct _DrwBreakWindowClass { + GtkWindowClass parent_class; +}; + +GType drw_break_window_get_type (void) G_GNUC_CONST; +GtkWidget * drw_break_window_new (void); + + +#endif /* __DRW_BREAK_WINDOW_H__ */ diff --git a/typing-break/drw-intl.h b/typing-break/drw-intl.h new file mode 100644 index 000000000..2ab90044a --- /dev/null +++ b/typing-break/drw-intl.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Richard Hult + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DRWRIGHT_INTL_H__ +#define __DRWRIGHT_INTL_H__ + +#ifdef ENABLE_NLS +#include +#define _(String) dgettext(GETTEXT_PACKAGE,String) +#ifdef gettext_noop +#define N_(String) gettext_noop(String) +#else +#define N_(String) (String) +#endif +#else /* NLS is disabled */ +#define _(String) (String) +#define N_(String) (String) +#define textdomain(String) (String) +#define gettext(String) (String) +#define dgettext(Domain,String) (String) +#define dcgettext(Domain,String,Type) (String) +#define bindtextdomain(Domain,Directory) (Domain) +#endif + +#endif /* __DRWRIGHT_INTL_H__ */ diff --git a/typing-break/drw-monitor.c b/typing-break/drw-monitor.c new file mode 100644 index 000000000..72da273a1 --- /dev/null +++ b/typing-break/drw-monitor.c @@ -0,0 +1,182 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Richard Hult + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "drw-monitor.h" + +struct _DrwMonitorPriv { + XScreenSaverInfo *ss_info; + guint timeout_id; + unsigned long last_idle; +}; + +/* Signals */ +enum { + ACTIVITY, + LAST_SIGNAL +}; + + +static void drw_monitor_class_init (DrwMonitorClass *klass); +static void drw_monitor_init (DrwMonitor *monitor); +static void drw_monitor_finalize (GObject *object); +static gboolean drw_monitor_setup (DrwMonitor *monitor); + +static GObjectClass *parent_class; +static guint signals[LAST_SIGNAL]; + + +GType +drw_monitor_get_type (void) +{ + static GType object_type = 0; + + if (!object_type) { + static const GTypeInfo object_info = { + sizeof (DrwMonitorClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) drw_monitor_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (DrwMonitor), + 0, /* n_preallocs */ + (GInstanceInitFunc) drw_monitor_init, + }; + + object_type = g_type_register_static (G_TYPE_OBJECT, + "DrwMonitor", + &object_info, 0); + } + + return object_type; +} + +static void +drw_monitor_class_init (DrwMonitorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + + object_class->finalize = drw_monitor_finalize; + + signals[ACTIVITY] = + g_signal_new ("activity", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +drw_monitor_init (DrwMonitor *monitor) +{ + DrwMonitorPriv *priv; + + priv = g_new0 (DrwMonitorPriv, 1); + monitor->priv = priv; + + drw_monitor_setup (monitor); +} + +static void +drw_monitor_finalize (GObject *object) +{ + DrwMonitor *monitor = DRW_MONITOR (object); + DrwMonitorPriv *priv; + + priv = monitor->priv; + + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + + if (priv->ss_info) { + XFree (priv->ss_info); + } + + g_free (priv); + monitor->priv = NULL; + + if (G_OBJECT_CLASS (parent_class)->finalize) { + (* G_OBJECT_CLASS (parent_class)->finalize) (object); + } +} + +static gboolean +drw_monitor_timeout (DrwMonitor *monitor) +{ + DrwMonitorPriv *priv; + + priv = monitor->priv; + + if (XScreenSaverQueryInfo (GDK_DISPLAY (), DefaultRootWindow (GDK_DISPLAY ()), priv->ss_info) != 0) { + if (priv->ss_info->idle < priv->last_idle) { + g_signal_emit (monitor, signals[ACTIVITY], 0, NULL); + } + + priv->last_idle = priv->ss_info->idle; + } + + return TRUE; +} + +static gboolean +drw_monitor_setup (DrwMonitor *monitor) +{ + DrwMonitorPriv *priv; + int event_base; + int error_base; + + priv = monitor->priv; + + if (!XScreenSaverQueryExtension (GDK_DISPLAY (), &event_base, &error_base)) { + return FALSE; + } + + priv->ss_info = XScreenSaverAllocInfo (); + + priv->timeout_id = g_timeout_add (3000, (GSourceFunc) drw_monitor_timeout, monitor); + + return TRUE; +} + +DrwMonitor * +drw_monitor_new (void) +{ + return g_object_new (DRW_TYPE_MONITOR, NULL); +} + diff --git a/typing-break/drw-monitor.h b/typing-break/drw-monitor.h new file mode 100644 index 000000000..bd4377570 --- /dev/null +++ b/typing-break/drw-monitor.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Richard Hult + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DRW_MONITOR_H__ +#define __DRW_MONITOR_H__ + +#include + +#define DRW_TYPE_MONITOR (drw_monitor_get_type ()) +#define DRW_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DRW_TYPE_MONITOR, DrwMonitor)) +#define DRW_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), DRW_TYPE_MONITOR, DrwMonitorClass)) +#define DRW_IS_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DRW_TYPE_MONITOR)) +#define DRW_IS_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DRW_TYPE_MONITOR)) +#define DRW_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DRW_TYPE_MONITOR, DrwMonitorClass)) + +typedef struct _DrwMonitor DrwMonitor; +typedef struct _DrwMonitorClass DrwMonitorClass; +typedef struct _DrwMonitorPriv DrwMonitorPriv; + +struct _DrwMonitor { + GObject parent; + + DrwMonitorPriv *priv; +}; + +struct _DrwMonitorClass { + GObjectClass parent_class; +}; + +GType drw_monitor_get_type (void) G_GNUC_CONST; +DrwMonitor *drw_monitor_new (void); + +#endif /* __DRW_MONITOR_H__ */ diff --git a/typing-break/drw-selection.c b/typing-break/drw-selection.c new file mode 100644 index 000000000..a0aa23599 --- /dev/null +++ b/typing-break/drw-selection.c @@ -0,0 +1,190 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Copyright © 2002 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Red Hat not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. Red Hat makes no representations about the + * suitability of this software for any purpose. It is provided "as is" + * without express or implied warranty. + * + * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT + * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Author: Owen Taylor, Red Hat, Inc. + */ + +#include +#include +#include + +#include "drw-selection.h" + +struct _DrwSelection +{ + GdkWindow *owner_window; + GtkWidget *invisible; +}; + +#define SELECTION_NAME "_CODEFACTORY_DRWRIGHT" + +static GdkFilterReturn drw_selection_filter (GdkXEvent *xevent, + GdkEvent *event, + gpointer data); +static void drw_selection_negotiate (DrwSelection *drw_selection); + +static void +drw_selection_reset (DrwSelection *drw_selection) +{ + if (drw_selection->owner_window) { + gdk_window_remove_filter (drw_selection->owner_window, + drw_selection_filter, drw_selection); + gdk_window_unref (drw_selection->owner_window); + drw_selection->owner_window = NULL; + } + + if (drw_selection->invisible) { + gtk_widget_destroy (drw_selection->invisible); + drw_selection->invisible = NULL; + } +} + +static void +drw_selection_clear (GtkWidget *widget, + GdkEventSelection *event, + gpointer user_data) +{ + DrwSelection *drw_selection = user_data; + + drw_selection_reset (drw_selection); + drw_selection_negotiate (drw_selection); +} + +static gboolean +drw_selection_find_existing (DrwSelection *drw_selection) +{ + Display *xdisplay = GDK_DISPLAY (); + Window old; + + gdk_error_trap_push (); + old = XGetSelectionOwner (xdisplay, + gdk_x11_get_xatom_by_name (SELECTION_NAME)); + if (old) { + XSelectInput (xdisplay, old, StructureNotifyMask); + drw_selection->owner_window = gdk_window_foreign_new (old); + } + XSync (xdisplay, False); + + if (gdk_error_trap_pop () == 0 && drw_selection->owner_window) { + gdk_window_add_filter (drw_selection->owner_window, + drw_selection_filter, drw_selection); + + XUngrabServer (xdisplay); + + return TRUE; + } else { + if (drw_selection->owner_window) { + gdk_window_unref (drw_selection->owner_window); + drw_selection->owner_window = NULL; + } + + return FALSE; + } +} + +static gboolean +drw_selection_claim (DrwSelection *drw_selection) +{ + drw_selection->invisible = gtk_invisible_new (); + g_signal_connect (drw_selection->invisible, "selection-clear-event", + G_CALLBACK (drw_selection_clear), drw_selection); + + + if (gtk_selection_owner_set (drw_selection->invisible, + gdk_atom_intern (SELECTION_NAME, FALSE), + GDK_CURRENT_TIME)) { + return TRUE; + } else { + drw_selection_reset (drw_selection); + return FALSE; + } +} + +static void +drw_selection_negotiate (DrwSelection *drw_selection) +{ + Display *xdisplay = GDK_DISPLAY (); + gboolean found = FALSE; + + /* We don't need both the XGrabServer() and the loop here; + * the XGrabServer() should make sure that we only go through + * the loop once. It also works if you remove the XGrabServer() + * and just have the loop, but then the selection ownership + * can get transfered a bunch of times before things + * settle down. + */ + while (!found) + { + XGrabServer (xdisplay); + + if (drw_selection_find_existing (drw_selection)) + found = TRUE; + else if (drw_selection_claim (drw_selection)) + found = TRUE; + + XUngrabServer (xdisplay); + } +} + +static GdkFilterReturn +drw_selection_filter (GdkXEvent *xevent, + GdkEvent *event, + gpointer data) +{ + DrwSelection *drw_selection = data; + XEvent *xev = (XEvent *)xevent; + + if (xev->xany.type == DestroyNotify && + xev->xdestroywindow.window == xev->xdestroywindow.event) + { + drw_selection_reset (drw_selection); + drw_selection_negotiate (drw_selection); + + return GDK_FILTER_REMOVE; + } + + return GDK_FILTER_CONTINUE; +} + +DrwSelection * +drw_selection_start () +{ + DrwSelection *drw_selection = g_new (DrwSelection, 1); + + drw_selection->owner_window = NULL; + drw_selection->invisible = NULL; + + drw_selection_negotiate (drw_selection); + + return drw_selection; +} + +void +drw_selection_stop (DrwSelection *drw_selection) +{ + drw_selection_reset (drw_selection); + g_free (drw_selection); +} + +gboolean +drw_selection_is_master (DrwSelection *drw_selection) +{ + return drw_selection->invisible != NULL; +} diff --git a/typing-break/drw-selection.h b/typing-break/drw-selection.h new file mode 100644 index 000000000..54fd779a3 --- /dev/null +++ b/typing-break/drw-selection.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Copyright © 2002 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Red Hat not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. Red Hat makes no representations about the + * suitability of this software for any purpose. It is provided "as is" + * without express or implied warranty. + * + * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT + * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Author: Owen Taylor, Red Hat, Inc. + */ + +#ifndef __DRW_SELECTION_H__ +#define __DRW_SELECTION_H__ + +typedef struct _DrwSelection DrwSelection; + +DrwSelection * drw_selection_start (); +void drw_selection_stop (DrwSelection *drw_selection); +gboolean drw_selection_is_master (DrwSelection *drw_selection); + +#endif /* __DRW_SELECTION_H__ */ diff --git a/typing-break/drwright.c b/typing-break/drwright.c new file mode 100644 index 000000000..5090522cb --- /dev/null +++ b/typing-break/drwright.c @@ -0,0 +1,947 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002-2003 Richard Hult + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "drwright.h" +#include "drw-intl.h" +#include "drw-break-window.h" +#include "drw-monitor.h" +#include "eggtrayicon.h" + +#define BLINK_TIMEOUT 200 +#define BLINK_TIMEOUT_MIN 120 +#define BLINK_TIMEOUT_FACTOR 100 + +#define POPUP_ITEM_ENABLED 1 +#define POPUP_ITEM_BREAK 2 + +typedef enum { + STATE_START, + STATE_IDLE, + STATE_TYPE, + STATE_WARN_TYPE, + STATE_WARN_IDLE, + STATE_BREAK_SETUP, + STATE_BREAK, + STATE_BREAK_DONE_SETUP, + STATE_BREAK_DONE +} DrwState; + +struct _DrWright { + /* Widgets. */ + GtkWidget *break_window; + + DrwMonitor *monitor; + + GtkItemFactory *popup_factory; + + DrwState state; + GTimer *timer; + GTimer *idle_timer; + + gint last_elapsed_time; + + gboolean is_active; + + /* Time settings. */ + gint type_time; + gint break_time; + gint warn_time; + + gboolean enabled; + + guint clock_timeout_id; + guint blink_timeout_id; + + gboolean blink_on; + + EggTrayIcon *icon; + GtkWidget *icon_image; + GtkWidget *icon_event_box; + GtkTooltips *tooltips; + + GdkPixbuf *neutral_bar; + GdkPixbuf *red_bar; + GdkPixbuf *green_bar; + GdkPixbuf *disabled_bar; + GdkPixbuf *composite_bar; + + GtkWidget *warn_dialog; +}; + +static void activity_detected_cb (DrwMonitor *monitor, + DrWright *drwright); +static gboolean maybe_change_state (DrWright *drwright); +static gboolean update_tooltip (DrWright *drwright); +static gboolean icon_button_press_cb (GtkWidget *widget, + GdkEventButton *event, + DrWright *drwright); +static void break_window_done_cb (GtkWidget *window, + DrWright *dr); +static void break_window_postpone_cb (GtkWidget *window, + DrWright *dr); +static void popup_enabled_cb (gpointer callback_data, + guint action, + GtkWidget *widget); +static void popup_break_cb (gpointer callback_data, + guint action, + GtkWidget *widget); +static void popup_preferences_cb (gpointer callback_data, + guint action, + GtkWidget *widget); +static void popup_quit_cb (gpointer callback_data, + guint action, + GtkWidget *widget); +static void popup_about_cb (gpointer callback_data, + guint action, + GtkWidget *widget); +static gchar * item_factory_trans_cb (const gchar *path, + gpointer data); +static void init_tray_icon (DrWright *dr); + + +#define GIF_CB(x) ((GtkItemFactoryCallback)(x)) + +static GtkItemFactoryEntry popup_items[] = { + { N_("/_Enabled"), NULL, GIF_CB (popup_enabled_cb), POPUP_ITEM_ENABLED, "", NULL }, + { N_("/_Take a Break"), NULL, GIF_CB (popup_break_cb), POPUP_ITEM_BREAK, "", NULL }, + { "/sep1", NULL, 0, 0, "", NULL }, + { N_("/_Preferences"), NULL, GIF_CB (popup_preferences_cb), 0, "", GTK_STOCK_PREFERENCES }, + { N_("/_About"), NULL, GIF_CB (popup_about_cb), 0, "", GNOME_STOCK_ABOUT }, + { "/sep2", NULL, 0, 0, "", NULL }, + { N_("/_Remove Icon"), "", GIF_CB (popup_quit_cb), 0, "", GTK_STOCK_REMOVE }, +}; + +GConfClient *client = NULL; +gboolean debug; + +static void +setup_debug_values (DrWright *dr) +{ + dr->type_time = 5; + dr->warn_time = 4; + dr->break_time = 10; +} + +static void +update_icon (DrWright *dr) +{ + GdkPixbuf *pixbuf; + GdkPixbuf *tmp_pixbuf; + gint width, height; + gfloat r; + gint offset; + gboolean set_pixbuf; + + if (!dr->enabled) { + gtk_image_set_from_pixbuf (GTK_IMAGE (dr->icon_image), dr->disabled_bar); + return; + } + + tmp_pixbuf = gdk_pixbuf_copy (dr->neutral_bar); + + width = gdk_pixbuf_get_width (tmp_pixbuf); + height = gdk_pixbuf_get_height (tmp_pixbuf); + + set_pixbuf = TRUE; + + switch (dr->state) { + case STATE_BREAK: + case STATE_BREAK_SETUP: + r = 1; + break; + + case STATE_BREAK_DONE: + case STATE_BREAK_DONE_SETUP: + case STATE_START: + r = 0; + break; + + case STATE_WARN_IDLE: + case STATE_WARN_TYPE: + r = ((float)(dr->type_time - dr->warn_time) / dr->type_time) + + (float) g_timer_elapsed (dr->timer, NULL) / (float) dr->warn_time; + break; + + default: + r = (float) g_timer_elapsed (dr->timer, NULL) / (float) dr->type_time; + break; + } + + offset = CLAMP ((height - 0) * (1.0 - r), 1, height - 0); + + switch (dr->state) { + case STATE_WARN_TYPE: + case STATE_WARN_IDLE: + pixbuf = dr->red_bar; + set_pixbuf = FALSE; + break; + + case STATE_BREAK_SETUP: + case STATE_BREAK: + pixbuf = dr->red_bar; + break; + + default: + pixbuf = dr->green_bar; + } + + gdk_pixbuf_composite (pixbuf, + tmp_pixbuf, + 0, + offset, + width, + height - offset, + 0, + 0, + 1.0, + 1.0, + GDK_INTERP_BILINEAR, + 255); + + if (set_pixbuf) { + gtk_image_set_from_pixbuf (GTK_IMAGE (dr->icon_image), tmp_pixbuf); + } + + if (dr->composite_bar) { + g_object_unref (dr->composite_bar); + } + + dr->composite_bar = tmp_pixbuf; +} + +static gboolean +blink_timeout_cb (DrWright *dr) +{ + gfloat r; + gint timeout; + + r = (dr->warn_time - g_timer_elapsed (dr->timer, NULL)) / dr->warn_time; + timeout = BLINK_TIMEOUT + BLINK_TIMEOUT_FACTOR * r; + + if (timeout < BLINK_TIMEOUT_MIN) { + timeout = BLINK_TIMEOUT_MIN; + } + + if (dr->blink_on || timeout == 0) { + gtk_image_set_from_pixbuf (GTK_IMAGE (dr->icon_image), dr->composite_bar); + } else { + gtk_image_set_from_pixbuf (GTK_IMAGE (dr->icon_image), dr->neutral_bar); + } + + dr->blink_on = !dr->blink_on; + + if (timeout) { + dr->blink_timeout_id = g_timeout_add (timeout, + (GSourceFunc) blink_timeout_cb, + dr); + } else { + dr->blink_timeout_id = 0; + } + + return FALSE; +} + +static void +start_blinking (DrWright *dr) +{ + if (!dr->blink_timeout_id) { + dr->blink_on = TRUE; + blink_timeout_cb (dr); + } + + /*gtk_widget_show (GTK_WIDGET (dr->icon));*/ +} + +static void +stop_blinking (DrWright *dr) +{ + if (dr->blink_timeout_id) { + g_source_remove (dr->blink_timeout_id); + dr->blink_timeout_id = 0; + } + + /*gtk_widget_hide (GTK_WIDGET (dr->icon));*/ +} + +static gboolean +maybe_change_state (DrWright *dr) +{ + gint elapsed_time; + gint elapsed_idle_time; + + if (debug) { + g_timer_reset (dr->idle_timer); + } + + elapsed_time = g_timer_elapsed (dr->timer, NULL); + elapsed_idle_time = g_timer_elapsed (dr->idle_timer, NULL); + + if (elapsed_time > dr->last_elapsed_time + dr->warn_time) { + /* If the timeout is delayed by the amount of warning time, then + * we must have been suspended or stopped, so we just start + * over. + */ + dr->state = STATE_START; + } + + switch (dr->state) { + case STATE_START: + if (dr->break_window) { + gtk_widget_destroy (dr->break_window); + dr->break_window = NULL; + } + + gtk_image_set_from_pixbuf (GTK_IMAGE (dr->icon_image), dr->neutral_bar); + + g_timer_start (dr->timer); + g_timer_start (dr->idle_timer); + + if (dr->enabled) { + dr->state = STATE_IDLE; + } + + update_tooltip (dr); + stop_blinking (dr); + break; + + case STATE_IDLE: + if (elapsed_idle_time >= dr->break_time) { + g_timer_start (dr->timer); + g_timer_start (dr->idle_timer); + } else if (dr->is_active) { + dr->state = STATE_TYPE; + } + break; + + case STATE_TYPE: + if (elapsed_time >= dr->type_time - dr->warn_time) { + dr->state = STATE_WARN_TYPE; + g_timer_start (dr->timer); + + start_blinking (dr); + } else if (elapsed_time >= dr->type_time) { + dr->state = STATE_BREAK_SETUP; + } + else if (!dr->is_active) { + dr->state = STATE_IDLE; + g_timer_start (dr->idle_timer); + } + break; + + case STATE_WARN_TYPE: + if (elapsed_time >= dr->warn_time) { + dr->state = STATE_BREAK_SETUP; + } + else if (!dr->is_active) { + dr->state = STATE_WARN_IDLE; + } + break; + + case STATE_WARN_IDLE: + if (elapsed_idle_time >= dr->break_time) { + dr->state = STATE_BREAK_DONE_SETUP; + } + else if (dr->is_active) { + dr->state = STATE_WARN_TYPE; + } + + break; + + case STATE_BREAK_SETUP: + stop_blinking (dr); + gtk_image_set_from_pixbuf (GTK_IMAGE (dr->icon_image), dr->red_bar); + + g_timer_start (dr->timer); + + dr->break_window = drw_break_window_new (); + + g_signal_connect (dr->break_window, + "done", + G_CALLBACK (break_window_done_cb), + dr); + + g_signal_connect (dr->break_window, + "postpone", + G_CALLBACK (break_window_postpone_cb), + dr); + + gtk_widget_show (dr->break_window); + + dr->state = STATE_BREAK; + break; + + case STATE_BREAK: + if (elapsed_time >= dr->break_time) { + dr->state = STATE_BREAK_DONE_SETUP; + } + break; + + case STATE_BREAK_DONE_SETUP: + stop_blinking (dr); + gtk_image_set_from_pixbuf (GTK_IMAGE (dr->icon_image), dr->green_bar); + + dr->state = STATE_BREAK_DONE; + break; + + case STATE_BREAK_DONE: + if (dr->is_active) { + dr->state = STATE_START; + if (dr->break_window) { + gtk_widget_destroy (dr->break_window); + dr->break_window = NULL; + } + } + break; + } + + dr->is_active = FALSE; + dr->last_elapsed_time = elapsed_time; + + update_icon (dr); + + return TRUE; +} + +static gboolean +update_tooltip (DrWright *dr) +{ + gint elapsed_time, min; + gchar *str; + + if (!dr->enabled) { + gtk_tooltips_set_tip (GTK_TOOLTIPS (dr->tooltips), + dr->icon_event_box, + _("Disabled"), _("Disabled")); + return TRUE; + } + + elapsed_time = g_timer_elapsed (dr->timer, NULL); + + switch (dr->state) { + case STATE_WARN_TYPE: + case STATE_WARN_IDLE: + min = ceil ((dr->warn_time - elapsed_time) / 60.0); + break; + + default: + min = ceil ((dr->type_time - elapsed_time) / 60.0); + break; + } + + if (min > 1) { + str = g_strdup_printf (_("%d minutes until the next break"), min); + } + else if (min == 1) { + str = g_strdup_printf (_("One minute until the next break")); + } else { + str = g_strdup_printf (_("Less than one minute until the next break")); + } + + gtk_tooltips_set_tip (GTK_TOOLTIPS (dr->tooltips), + dr->icon_event_box, + str, str); + + g_free (str); + + return TRUE; +} + +static void +activity_detected_cb (DrwMonitor *monitor, + DrWright *dr) +{ + dr->is_active = TRUE; + g_timer_start (dr->idle_timer); +} + +static void +gconf_notify_cb (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) +{ + DrWright *dr = user_data; + GtkWidget *item; + + if (!strcmp (entry->key, "/desktop/gnome/typing_break/type_time")) { + if (entry->value->type == GCONF_VALUE_INT) { + dr->type_time = 60 * gconf_value_get_int (entry->value); + dr->warn_time = MIN (dr->type_time / 10, 5*60); + + dr->state = STATE_START; + } + } +/* else if (!strcmp (entry->key, "/desktop/gnome/typing_break/warn_time")) { + if (entry->value->type == GCONF_VALUE_INT) { + dr->warn_time = 60 * gconf_value_get_int (entry->value); + dr->state = STATE_START; + } + } +*/ + else if (!strcmp (entry->key, "/desktop/gnome/typing_break/break_time")) { + if (entry->value->type == GCONF_VALUE_INT) { + dr->break_time = 60 * gconf_value_get_int (entry->value); + dr->state = STATE_START; + } + } + else if (!strcmp (entry->key, "/desktop/gnome/typing_break/enabled")) { + if (entry->value->type == GCONF_VALUE_BOOL) { + dr->enabled = gconf_value_get_bool (entry->value); + dr->state = STATE_START; + + item = gtk_item_factory_get_widget_by_action (dr->popup_factory, + POPUP_ITEM_BREAK); + gtk_widget_set_sensitive (item, dr->enabled); + + update_tooltip (dr); + } + } + + maybe_change_state (dr); +} + +static void +popup_enabled_cb (gpointer callback_data, + guint action, + GtkWidget *widget) +{ + DrWright *dr = callback_data; + GtkWidget *item; + gboolean enabled; + + item = gtk_item_factory_get_widget_by_action (dr->popup_factory, + POPUP_ITEM_ENABLED); + + enabled = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)); + + gconf_client_set_bool (client, "/desktop/gnome/typing_break/enabled", + enabled, + NULL); +} + +static void +popup_break_cb (gpointer callback_data, + guint action, + GtkWidget *widget) +{ + DrWright *dr = callback_data; + + if (dr->enabled) { + dr->state = STATE_BREAK_SETUP; + maybe_change_state (dr); + } +} + +static void +popup_preferences_cb (gpointer callback_data, + guint action, + GtkWidget *widget) +{ + /* Bring up gnome-keyboard-properties on the right page */ +} + +static void +popup_quit_cb (gpointer callback_data, + guint action, + GtkWidget *widget) +{ + GtkWidget *dialog; + gchar *str; + gint response; + GnomeClient *client; + + str = g_strdup_printf ("%s\n%s", + _("Quit DrWright?"), + _("Don't forget to take regular breaks.")); + + dialog = gtk_message_dialog_new (NULL, + 0, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + str); + + g_free (str); + + g_object_set (GTK_MESSAGE_DIALOG (dialog)->label, + "use-markup", TRUE, + "wrap", TRUE, + NULL); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + if (response != GTK_RESPONSE_DELETE_EVENT) { + gtk_widget_destroy (dialog); + } + + if (response == GTK_RESPONSE_YES) { + client = gnome_master_client (); + gnome_client_set_restart_style (client, GNOME_RESTART_NEVER); + + gtk_main_quit (); + } +} + +static void +popup_about_cb (gpointer callback_data, + guint action, + GtkWidget *widget) +{ + static GtkWidget *about_window; + GtkWidget *vbox; + GtkWidget *label; + GdkPixbuf *icon; + gchar *markup; + + if (about_window) { + gtk_window_present (GTK_WINDOW (about_window)); + return; + } + + about_window = gtk_dialog_new (); + + g_object_add_weak_pointer (G_OBJECT (about_window), (gpointer *) &about_window); + + gtk_dialog_add_button (GTK_DIALOG (about_window), + GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_dialog_set_default_response (GTK_DIALOG (about_window), + GTK_RESPONSE_OK); + + gtk_window_set_title (GTK_WINDOW (about_window), _("About GNOME Typing Monitor")); + icon = NULL; /*gdk_pixbuf_new_from_file (IMAGEDIR "/bar.png", NULL);*/ + if (icon != NULL) { + gtk_window_set_icon (GTK_WINDOW (about_window), icon); + g_object_unref (icon); + } + + gtk_window_set_resizable (GTK_WINDOW (about_window), FALSE); + gtk_window_set_position (GTK_WINDOW (about_window), + GTK_WIN_POS_CENTER_ON_PARENT); + gtk_window_set_type_hint (GTK_WINDOW (about_window), + GDK_WINDOW_TYPE_HINT_DIALOG); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (about_window)->vbox), vbox, FALSE, FALSE, 0); + + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); + markup = g_strdup_printf ("Typing Monitor " VERSION "\n\n" + "%s\n\n" + "%s\n" + "%s\n", + _("A computer break reminder."), + _("Written by Richard Hult <rhult@codefactory.se>"), + _("Eye candy added by Anders Carlsson")); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (markup); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + + gtk_widget_show_all (about_window); + gtk_dialog_run (GTK_DIALOG (about_window)); + gtk_widget_destroy (about_window); +} + +static void +popup_menu_position_cb (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer data) +{ + GtkWidget *w = data; + GtkRequisition requisition; + gint wx, wy; + + g_return_if_fail (w != NULL); + + gtk_widget_size_request (GTK_WIDGET (menu), &requisition); + + gdk_window_get_origin (w->window, &wx, &wy); + + if (*x < wx) + *x = wx; + else if (*x > wx + w->allocation.width) + *x = wx + w->allocation.width; + + if (*x + requisition.width > gdk_screen_width()) + *x = gdk_screen_width() - requisition.width; + + if (*y < wy) + *y = wy; + else if (*y > wy + w->allocation.height) + *y = wy + w->allocation.height; + + if (*y + requisition.height > gdk_screen_height()) + *y = gdk_screen_height() - requisition.height; + + *push_in = TRUE; +} + +static gboolean +icon_button_press_cb (GtkWidget *widget, + GdkEventButton *event, + DrWright *dr) +{ + GtkWidget *menu; + + if (event->button == 3) { + menu = gtk_item_factory_get_widget (dr->popup_factory, ""); + + gtk_menu_popup (GTK_MENU (menu), + NULL, + NULL, + popup_menu_position_cb, + dr->icon, + event->button, + event->time); + + return TRUE; + } + + return FALSE; +} + +static void +popup_menu_cb (GtkWidget *widget, + DrWright *dr) +{ + GtkWidget *menu; + + menu = gtk_item_factory_get_widget (dr->popup_factory, ""); + + gtk_menu_popup (GTK_MENU (menu), + NULL, + NULL, + popup_menu_position_cb, + dr->icon, + 0, + gtk_get_current_event_time()); +} + +static void +break_window_done_cb (GtkWidget *window, + DrWright *dr) +{ + gtk_widget_destroy (dr->break_window); + + dr->state = STATE_BREAK_DONE_SETUP; + dr->break_window = NULL; + + maybe_change_state (dr); +} + +static void +break_window_postpone_cb (GtkWidget *window, + DrWright *dr) +{ + gtk_widget_destroy (dr->break_window); + + dr->state = STATE_WARN_TYPE; + dr->break_window = NULL; + + g_timer_start (dr->timer); + start_blinking (dr); + update_icon (dr); + update_tooltip (dr); +} + +static char * +item_factory_trans_cb (const gchar *path, + gpointer data) +{ + return _((gchar*) path); +} + +static void +icon_event_box_destroy_cb (GtkWidget *widget, + DrWright *dr) +{ + gtk_widget_destroy (GTK_WIDGET (dr->icon)); + init_tray_icon (dr); +} + +static gboolean +icon_event_box_expose_event_cb (GtkWidget *widget, + GdkEventExpose *event, + DrWright *dr) +{ + if (GTK_WIDGET_HAS_FOCUS (widget)) { + gint focus_width, focus_pad; + gint x, y, width, height; + + gtk_widget_style_get (widget, + "focus-line-width", &focus_width, + "focus-padding", &focus_pad, + NULL); + x = widget->allocation.x + focus_pad; + y = widget->allocation.y + focus_pad; + width = widget->allocation.width - 2 * focus_pad; + height = widget->allocation.height - 2 * focus_pad; + + gtk_paint_focus (widget->style, widget->window, + GTK_WIDGET_STATE (widget), + &event->area, widget, "button", + x, y, width, height); + } + + return FALSE; +} + +static void +init_tray_icon (DrWright *dr) +{ + dr->icon = egg_tray_icon_new (_("Break reminder")); + + dr->icon_event_box = gtk_event_box_new (); + dr->icon_image = gtk_image_new_from_pixbuf (dr->neutral_bar); + gtk_container_add (GTK_CONTAINER (dr->icon_event_box), dr->icon_image); + + gtk_widget_add_events (GTK_WIDGET (dr->icon), GDK_BUTTON_PRESS_MASK | GDK_FOCUS_CHANGE_MASK); + gtk_container_add (GTK_CONTAINER (dr->icon), dr->icon_event_box); + gtk_widget_show_all (GTK_WIDGET (dr->icon)); + + GTK_WIDGET_SET_FLAGS (dr->icon_event_box, GTK_CAN_FOCUS); + + update_tooltip (dr); + update_icon (dr); + + g_signal_connect (dr->icon, + "button_press_event", + G_CALLBACK (icon_button_press_cb), + dr); + + g_signal_connect (dr->icon, + "destroy", + G_CALLBACK (icon_event_box_destroy_cb), + dr); + + g_signal_connect (dr->icon, + "popup_menu", + G_CALLBACK (popup_menu_cb), + dr); + + g_signal_connect_after (dr->icon_event_box, + "expose_event", + G_CALLBACK (icon_event_box_expose_event_cb), + dr); +} + +DrWright * +drwright_new (void) +{ + DrWright *dr; + GtkWidget *item; + + dr = g_new0 (DrWright, 1); + + client = gconf_client_get_default (); + + gconf_client_add_dir (client, + "/apps/drwright", + GCONF_CLIENT_PRELOAD_NONE, + NULL); + + gconf_client_notify_add (client, "/apps/drwright", + gconf_notify_cb, + dr, + NULL, + NULL); + + dr->type_time = 60 * gconf_client_get_int ( + client, "/desktop/gnome/typing_break/type_time", NULL); + +/* dr->warn_time = 60 * gconf_client_get_int ( + client, "/desktop/gnome/typing_break/warn_time", NULL); +*/ + dr->warn_time = MIN (dr->type_time / 10, 60*5); + + dr->break_time = 60 * gconf_client_get_int ( + client, "/desktop/gnome/typing_break/break_time", NULL); + + dr->enabled = gconf_client_get_bool ( + client, + "/desktop/gnome/typing_break/enabled", + NULL); + + if (debug) { + setup_debug_values (dr); + } + + dr->popup_factory = gtk_item_factory_new (GTK_TYPE_MENU, + "
", + NULL); + gtk_item_factory_set_translate_func (dr->popup_factory, + item_factory_trans_cb, + NULL, + NULL); + + gtk_item_factory_create_items (dr->popup_factory, + G_N_ELEMENTS (popup_items), + popup_items, + dr); + + item = gtk_item_factory_get_widget_by_action (dr->popup_factory, POPUP_ITEM_ENABLED); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), dr->enabled); + + item = gtk_item_factory_get_widget_by_action (dr->popup_factory, POPUP_ITEM_BREAK); + gtk_widget_set_sensitive (item, dr->enabled); + + dr->timer = g_timer_new (); + dr->idle_timer = g_timer_new (); + + dr->state = STATE_START; + + dr->monitor = drw_monitor_new (); + + g_signal_connect (dr->monitor, + "activity", + G_CALLBACK (activity_detected_cb), + dr); + + dr->neutral_bar = gdk_pixbuf_new_from_file (IMAGEDIR "/bar.png", NULL); + dr->red_bar = gdk_pixbuf_new_from_file (IMAGEDIR "/bar-red.png", NULL); + dr->green_bar = gdk_pixbuf_new_from_file (IMAGEDIR "/bar-green.png", NULL); + dr->disabled_bar = gdk_pixbuf_new_from_file (IMAGEDIR "/bar-disabled.png", NULL); + + dr->tooltips = gtk_tooltips_new (); + + init_tray_icon (dr); + + g_timeout_add (15*1000, + (GSourceFunc) update_tooltip, + dr); + g_timeout_add (500, + (GSourceFunc) maybe_change_state, + dr); + + return dr; +} + diff --git a/typing-break/drwright.h b/typing-break/drwright.h new file mode 100644 index 000000000..706bc83a6 --- /dev/null +++ b/typing-break/drwright.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 Richard Hult + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DR_WRIGHT_H__ +#define __DR_WRIGHT_H__ + +typedef struct _DrWright DrWright; + +DrWright * drwright_new (void); + +#endif /* __DR_WRIGHT_H__ */ diff --git a/typing-break/eggtrayicon.c b/typing-break/eggtrayicon.c new file mode 100644 index 000000000..065d84976 --- /dev/null +++ b/typing-break/eggtrayicon.c @@ -0,0 +1,341 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* eggtrayicon.c + * Copyright (C) 2002 Anders Carlsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "eggtrayicon.h" + +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +static GtkPlugClass *parent_class = NULL; + +static void egg_tray_icon_init (EggTrayIcon *icon); +static void egg_tray_icon_class_init (EggTrayIconClass *klass); + +static void egg_tray_icon_update_manager_window (EggTrayIcon *icon); + +GType +egg_tray_icon_get_type (void) +{ + static GType our_type = 0; + + if (our_type == 0) + { + static const GTypeInfo our_info = + { + sizeof (EggTrayIconClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) egg_tray_icon_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EggTrayIcon), + 0, /* n_preallocs */ + (GInstanceInitFunc) egg_tray_icon_init + }; + + our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0); + } + + return our_type; +} + +static void +egg_tray_icon_init (EggTrayIcon *icon) +{ + icon->stamp = 1; + + gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK); +} + +static void +egg_tray_icon_class_init (EggTrayIconClass *klass) +{ + parent_class = g_type_class_peek_parent (klass); +} + +static GdkFilterReturn +egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data) +{ + EggTrayIcon *icon = user_data; + XEvent *xev = (XEvent *)xevent; + + if (xev->xany.type == ClientMessage && + xev->xclient.message_type == icon->manager_atom && + xev->xclient.data.l[1] == icon->selection_atom) + { + egg_tray_icon_update_manager_window (icon); + } + else if (xev->xany.window == icon->manager_window) + { + if (xev->xany.type == DestroyNotify) + { + egg_tray_icon_update_manager_window (icon); + } + } + + return GDK_FILTER_CONTINUE; +} + +static void +egg_tray_icon_send_manager_message (EggTrayIcon *icon, + long message, + Window window, + long data1, + long data2, + long data3) +{ + XClientMessageEvent ev; + Display *display; + + ev.type = ClientMessage; + ev.window = window; + ev.message_type = icon->system_tray_opcode_atom; + ev.format = 32; + ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window); + ev.data.l[1] = message; + ev.data.l[2] = data1; + ev.data.l[3] = data2; + ev.data.l[4] = data3; + +#if HAVE_GTK_MULTIHEAD + display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); +#else + display = gdk_display; +#endif + + gdk_error_trap_push (); + XSendEvent (display, + icon->manager_window, False, NoEventMask, (XEvent *)&ev); + XSync (display, False); + gdk_error_trap_pop (); +} + +static void +egg_tray_icon_send_dock_request (EggTrayIcon *icon) +{ + egg_tray_icon_send_manager_message (icon, + SYSTEM_TRAY_REQUEST_DOCK, + icon->manager_window, + gtk_plug_get_id (GTK_PLUG (icon)), + 0, 0); +} + +static void +egg_tray_icon_update_manager_window (EggTrayIcon *icon) +{ + Display *xdisplay; + +#if HAVE_GTK_MULTIHEAD + xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); +#else + xdisplay = gdk_display; +#endif + + if (icon->manager_window != None) + { + GdkWindow *gdkwin; + +#if HAVE_GTK_MULTIHEAD + gdkwin = gdk_window_lookup_for_display (display, + icon->manager_window); +#else + gdkwin = gdk_window_lookup (icon->manager_window); +#endif + + gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon); + } + + XGrabServer (xdisplay); + + icon->manager_window = XGetSelectionOwner (xdisplay, + icon->selection_atom); + + if (icon->manager_window != None) + XSelectInput (xdisplay, + icon->manager_window, StructureNotifyMask); + + XUngrabServer (xdisplay); + XFlush (xdisplay); + + if (icon->manager_window != None) + { + GdkWindow *gdkwin; + +#if HAVE_GTK_MULTIHEAD + gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)), + icon->manager_window); +#else + gdkwin = gdk_window_lookup (icon->manager_window); +#endif + + gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon); + + /* Send a request that we'd like to dock */ + egg_tray_icon_send_dock_request (icon); + } +} + +EggTrayIcon * +egg_tray_icon_new_for_xscreen (Screen *xscreen, const char *name) +{ + EggTrayIcon *icon; + char buffer[256]; + GdkWindow *root_window; + + g_return_val_if_fail (xscreen != NULL, NULL); + + icon = g_object_new (EGG_TYPE_TRAY_ICON, NULL); + gtk_window_set_title (GTK_WINDOW (icon), name); + +#if HAVE_GTK_MULTIHEAD + gtk_plug_construct_for_display (GTK_PLUG (icon), + gdk_screen_get_display (screen), 0); +#else + gtk_plug_construct (GTK_PLUG (icon), 0); +#endif + + gtk_widget_realize (GTK_WIDGET (icon)); + + /* Now see if there's a manager window around */ + g_snprintf (buffer, sizeof (buffer), + "_NET_SYSTEM_TRAY_S%d", + XScreenNumberOfScreen (xscreen)); + + icon->selection_atom = XInternAtom (DisplayOfScreen (xscreen), + buffer, False); + + icon->manager_atom = XInternAtom (DisplayOfScreen (xscreen), + "MANAGER", False); + + icon->system_tray_opcode_atom = XInternAtom (DisplayOfScreen (xscreen), + "_NET_SYSTEM_TRAY_OPCODE", False); + + egg_tray_icon_update_manager_window (icon); + +#if HAVE_GTK_MULTIHEAD + root_window = gdk_screen_get_root_window (screen); +#else + root_window = gdk_window_lookup (gdk_x11_get_default_root_xwindow ()); +#endif + + /* Add a root window filter so that we get changes on MANAGER */ + gdk_window_add_filter (root_window, + egg_tray_icon_manager_filter, icon); + + return icon; +} + +#if HAVE_GTK_MULTIHEAD +EggTrayIcon * +egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name) +{ + EggTrayIcon *icon; + char buffer[256]; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + return egg_tray_icon_new_for_xscreen (GDK_SCREEN_XSCREEN (screen), name); +} +#endif + +EggTrayIcon* +egg_tray_icon_new (const gchar *name) +{ + return egg_tray_icon_new_for_xscreen (DefaultScreenOfDisplay (gdk_display), name); +} + +guint +egg_tray_icon_send_message (EggTrayIcon *icon, + gint timeout, + const gchar *message, + gint len) +{ + guint stamp; + + g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0); + g_return_val_if_fail (timeout >= 0, 0); + g_return_val_if_fail (message != NULL, 0); + + if (icon->manager_window == None) + return 0; + + if (len < 0) + len = strlen (message); + + stamp = icon->stamp++; + + /* Get ready to send the message */ + egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE, + (Window)gtk_plug_get_id (GTK_PLUG (icon)), + timeout, len, stamp); + + /* Now to send the actual message */ + gdk_error_trap_push (); + while (len > 0) + { + XClientMessageEvent ev; + Display *xdisplay; + +#if HAVE_GTK_MULTIHEAD + xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); +#else + xdisplay = gdk_display; +#endif + + ev.type = ClientMessage; + ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon)); + ev.format = 8; + ev.message_type = XInternAtom (xdisplay, + "_NET_SYSTEM_TRAY_MESSAGE_DATA", False); + if (len > 20) + { + memcpy (&ev.data, message, 20); + len -= 20; + message += 20; + } + else + { + memcpy (&ev.data, message, len); + len = 0; + } + + XSendEvent (xdisplay, + icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev); + XSync (xdisplay, False); + } + gdk_error_trap_pop (); + + return stamp; +} + +void +egg_tray_icon_cancel_message (EggTrayIcon *icon, + guint id) +{ + g_return_if_fail (EGG_IS_TRAY_ICON (icon)); + g_return_if_fail (id > 0); + + egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE, + (Window)gtk_plug_get_id (GTK_PLUG (icon)), + id, 0, 0); +} diff --git a/typing-break/eggtrayicon.h b/typing-break/eggtrayicon.h new file mode 100644 index 000000000..724bc3da8 --- /dev/null +++ b/typing-break/eggtrayicon.h @@ -0,0 +1,76 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* eggtrayicon.h + * Copyright (C) 2002 Anders Carlsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_TRAY_ICON_H__ +#define __EGG_TRAY_ICON_H__ + +#include +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_TRAY_ICON (egg_tray_icon_get_type ()) +#define EGG_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TRAY_ICON, EggTrayIcon)) +#define EGG_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TRAY_ICON, EggTrayIconClass)) +#define EGG_IS_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TRAY_ICON)) +#define EGG_IS_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TRAY_ICON)) +#define EGG_TRAY_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TRAY_ICON, EggTrayIconClass)) + +typedef struct _EggTrayIcon EggTrayIcon; +typedef struct _EggTrayIconClass EggTrayIconClass; + +struct _EggTrayIcon +{ + GtkPlug parent_instance; + + guint stamp; + + Atom selection_atom; + Atom manager_atom; + Atom system_tray_opcode_atom; + Window manager_window; +}; + +struct _EggTrayIconClass +{ + GtkPlugClass parent_class; +}; + +GType egg_tray_icon_get_type (void); + +#if EGG_TRAY_ENABLE_MULTIHEAD +EggTrayIcon *egg_tray_icon_new_for_screen (GdkScreen *screen, + const gchar *name); +#endif + +EggTrayIcon *egg_tray_icon_new (const gchar *name); + +guint egg_tray_icon_send_message (EggTrayIcon *icon, + gint timeout, + const char *message, + gint len); +void egg_tray_icon_cancel_message (EggTrayIcon *icon, + guint id); + + + +G_END_DECLS + +#endif /* __EGG_TRAY_ICON_H__ */ diff --git a/typing-break/main.c b/typing-break/main.c new file mode 100644 index 000000000..47557cb1f --- /dev/null +++ b/typing-break/main.c @@ -0,0 +1,174 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002-2003 Richard Hult + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include "drw-intl.h" +#include "drw-selection.h" +#include "drwright.h" + +gboolean debug = FALSE; + +static gboolean +session_die (GnomeClient *client, gpointer client_data) +{ + gtk_main_quit (); + + return TRUE; +} + +static gboolean +session_save (GnomeClient *client, + gint phase, + GnomeRestartStyle rstyle, + gint shutdown, + GnomeInteractStyle istyle, + gint fast, + gpointer user_data) +{ + gchar *argv[2]; + + /* Only with glib 2.2: + * argv[0] = g_get_application_name (); + */ + + argv[0] = user_data; + argv[1] = "-n"; + + gnome_client_set_clone_command (client, 2, argv); + gnome_client_set_restart_command (client, 2, argv); + + return TRUE; +} + +static gboolean +have_tray (void) +{ + Screen *xscreen = DefaultScreenOfDisplay (gdk_display); + Atom selection_atom; + char *selection_atom_name; + + selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", + XScreenNumberOfScreen (xscreen)); + selection_atom = XInternAtom (DisplayOfScreen (xscreen), selection_atom_name, False); + g_free (selection_atom_name); + + if (XGetSelectionOwner (DisplayOfScreen (xscreen), selection_atom)) { + return TRUE; + } else { + return FALSE; + } +} + +int +main (int argc, char *argv[]) +{ + gint i; + DrWright *drwright; + DrwSelection *selection; + GnomeClient *client; + gboolean no_check = FALSE; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + i = 1; + while (i < argc) { + const gchar *arg = argv[i]; + + if (strcmp (arg, "--debug") == 0 || + strcmp (arg, "-d") == 0) { + debug = TRUE; + } + else if (strcmp (arg, "-n") == 0) { + no_check = TRUE; + } + else if (strcmp (arg, "-?") == 0) { + g_printerr ("Usage: %s [--debug]\n", argv[0]); + return 0; + } + + ++i; + } + + gnome_program_init (PACKAGE, VERSION, LIBGNOMEUI_MODULE, + argc, argv, NULL); + + selection = drw_selection_start (); + if (!drw_selection_is_master (selection)) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, + _("The typing monitor is already running.")); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + return 0; + } + + if (!no_check && !have_tray ()) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, + _("The typing monitor uses the notification area to display " + "information. You don't seem to have a notification area " + "on your panel. You can add it by right-clicking on your " + "panel and choose 'Add to panel -> Utilities -> Notification area'.")); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + } + + client = gnome_master_client (); + + gnome_client_set_priority (client, 70); + if (!debug) { + gnome_client_set_restart_style (client, GNOME_RESTART_IMMEDIATELY); + } else { + /* Don't respawn in debug mode. */ + gnome_client_set_restart_style (client, GNOME_RESTART_IF_RUNNING); + } + + g_signal_connect (client, + "save_yourself", + G_CALLBACK (session_save), + argv[0]); + + g_signal_connect (client, + "die", + G_CALLBACK (session_die), + NULL); + + drwright = drwright_new (); + + gtk_main (); + + return 0; +} diff --git a/typing-break/ocean-stripes.png b/typing-break/ocean-stripes.png new file mode 100644 index 000000000..d0570962e Binary files /dev/null and b/typing-break/ocean-stripes.png differ diff --git a/typing-break/stock_stop.png b/typing-break/stock_stop.png new file mode 100644 index 000000000..4beba47d9 Binary files /dev/null and b/typing-break/stock_stop.png differ diff --git a/typing-break/stop.png b/typing-break/stop.png new file mode 100644 index 000000000..b786dd724 Binary files /dev/null and b/typing-break/stop.png differ