/* GDM - The GNOME Display Manager * Copyright (C) 1998, 1999, 2000 Martin K. Petersen * * 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 "config.h" #include #include #include #include #include #include "gdm.h" #include "gdmcommon.h" #include "gdmconfig.h" #include "gdm-common.h" #include "gdm-daemon-config-keys.h" #include "greeter.h" #include "greeter_item.h" #include "greeter_events.h" #include "greeter_system.h" #include "greeter_canvas_item.h" #include "greeter_configuration.h" #include "greeter_canvas_text.h" #include "greeter_parser.h" /* Keep track of buttons so they can be set sensitive/insensitive */ GtkButton *gtk_ok_button = NULL; GtkButton *gtk_start_again_button = NULL; static void apply_tint (GdkPixbuf *pixbuf, guint32 tint_color) { guchar *pixels; guint r, g, b; gboolean has_alpha; guint w, h, stride; guint pixel_stride; guchar *line; int i; pixels = gdk_pixbuf_get_pixels (pixbuf); has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); r = (tint_color & 0xff0000) >> 16; g = (tint_color & 0x00ff00) >> 8; b = (tint_color & 0x0000ff); w = gdk_pixbuf_get_width (pixbuf); h = gdk_pixbuf_get_height (pixbuf); stride = gdk_pixbuf_get_rowstride (pixbuf); pixel_stride = (has_alpha) ? 4 : 3; while (h-->0) { line = pixels; for (i = 0; i < w; i++) { line[0] = line[0] * r / 0xff; line[1] = line[1] * g / 0xff; line[2] = line[2] * b / 0xff; line += pixel_stride; } pixels += stride; } } static GdkPixbuf * transform_pixbuf (GdkPixbuf *orig, gboolean has_tint, guint32 tint_color, double alpha, gint width, gint height) { GdkPixbuf *scaled; gint p_width, p_height; p_width = gdk_pixbuf_get_width (orig); p_height = gdk_pixbuf_get_height (orig); if (p_width != width || p_height != height || alpha < 1.0 || has_tint) { int alpha_i; if (alpha >= 1.0) alpha_i = 0xff; else if (alpha <= 0.0) alpha_i = 0; else alpha_i = (guint) floor (0xff*alpha); if (alpha != 0xff) { scaled = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height); gdk_pixbuf_fill (scaled, 0); gdk_pixbuf_composite (orig, scaled, 0, 0, width, height, 0, 0, (double)width/p_width, (double)height/p_height, GDK_INTERP_BILINEAR, alpha_i); } else scaled = gdk_pixbuf_scale_simple (orig, width, height, GDK_INTERP_BILINEAR); } else scaled = g_object_ref (orig); if (has_tint) apply_tint (scaled, tint_color); return scaled; } static void activate_button (GtkWidget *widget, gpointer data) { const char *id = data; if (id != NULL) greeter_item_run_action_callback (id); } static void menubar_done (GtkMenuShell *menushell, gpointer data) { GreeterItemInfo *entry_info = greeter_lookup_id ("user-pw-entry"); GtkWidget *entry = GNOME_CANVAS_WIDGET (entry_info->item)->widget; gtk_widget_grab_focus (entry); } static GtkWidget * make_menubar (void) { GtkWidget *w, *menu; GtkWidget *menubar = gtk_menu_bar_new (); /* FIXME: add translatable string here */ w = gtk_menu_item_new_with_label ("Menu"); gtk_menu_shell_append (GTK_MENU_SHELL (menubar), w); gtk_widget_show (GTK_WIDGET (w)); menu = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (w), menu); w = gtk_menu_item_new_with_mnemonic (_("Select _Language...")); gtk_menu_shell_append (GTK_MENU_SHELL (menu), w); gtk_widget_show (GTK_WIDGET (w)); g_signal_connect (G_OBJECT (w), "activate", G_CALLBACK (activate_button), "language_button"); w = gtk_menu_item_new_with_mnemonic (_("Select _Session...")); gtk_menu_shell_append (GTK_MENU_SHELL (menu), w); gtk_widget_show (GTK_WIDGET (w)); g_signal_connect (G_OBJECT (w), "activate", G_CALLBACK (activate_button), "session_button"); greeter_system_append_system_menu (menu); /* Add a quit/disconnect item when in xdmcp mode or flexi mode */ /* Do note that the order is important, we always want "Quit" for * flexi, even if not local (non-local xnest). and Disconnect * only for xdmcp */ if ( ! ve_string_empty (g_getenv ("GDM_FLEXI_SERVER"))) { w = gtk_menu_item_new_with_mnemonic (_("_Quit")); } else if (ve_string_empty (g_getenv ("GDM_IS_LOCAL"))) { w = gtk_menu_item_new_with_mnemonic (_("D_isconnect")); } else { w = NULL; } if (w != NULL) { GtkWidget *sep; /* add separator before the quit */ sep = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (menu), sep); gtk_widget_show (GTK_WIDGET (sep)); gtk_menu_shell_append (GTK_MENU_SHELL (menu), w); gtk_widget_show (GTK_WIDGET (w)); g_signal_connect (G_OBJECT (w), "activate", G_CALLBACK (gtk_main_quit), NULL); } g_signal_connect (G_OBJECT(gtk_menu_item_get_submenu( gtk_container_get_children(GTK_CONTAINER(menubar))->data)), "selection-done", G_CALLBACK (menubar_done), NULL); return menubar; } static void get_gdk_color_from_rgb (GdkColor *c, guint32 rgb) { c->red = ((rgb & 0xff0000) >> 16) * 0x101; c->green = ((rgb & 0xff00) >> 8) * 0x101; c->blue = (rgb & 0xff) * 0x101; c->pixel = 0; } static void menu_position_func (GtkMenu *menu, int *x, int *y, gboolean *push_in, GreeterItemInfo *item) { GtkAllocation rect; GtkRequisition requisition; rect = item->allocation; gtk_widget_size_request (GTK_WIDGET (menu), &requisition); *x = rect.x; if (requisition.height <= rect.y) *y = rect.y - requisition.height; else *y = rect.y + rect.height; *push_in = TRUE; } /* * The button with this handler never appears in the F10 menu, so * it can make use of callback data. */ static void greeter_options_handler (GreeterItemInfo *item, GtkWidget *menubar) { gtk_menu_popup (GTK_MENU(gtk_menu_item_get_submenu( gtk_container_get_children(GTK_CONTAINER(menubar))->data)), NULL, NULL, (GtkMenuPositionFunc)menu_position_func, item, 0, gtk_get_current_event_time()); } static void greeter_item_run_button_action_callback (GtkButton *button, const char *id) { GreeterItemInfo *entry_info = greeter_lookup_id ("user-pw-entry"); GtkWidget *entry = GNOME_CANVAS_WIDGET (entry_info->item)->widget; greeter_item_run_action_callback (id); gtk_widget_grab_focus (entry); } void greeter_item_create_canvas_item (GreeterItemInfo *item) { GnomeCanvasGroup *group; GtkJustification just; GtkWidget *entry; GtkWidget *gtkbutton; GtkWidget *list; GtkWidget *swin; double x1, y1, x2, y2; int i; GtkAllocation rect; char *text; GtkTooltips *tooltips; char *num_locale; GdkColor c; if (item->item != NULL) return; if ( ! greeter_item_is_visible (item)) return; g_assert (item->parent->group_item); if (item->fixed_children != NULL || item->box_children != NULL) { item->group_item = (GnomeCanvasGroup *)gnome_canvas_item_new (item->parent->group_item, GNOME_TYPE_CANVAS_GROUP, "x", (gdouble) 0.0, "y", (gdouble) 0.0, NULL); group = item->group_item; } else group = item->parent->group_item; rect = item->allocation; x1 = (gdouble) rect.x; y1 = (gdouble) rect.y; x2 = (gdouble) rect.x + rect.width; y2 = (gdouble) rect.y + rect.height; switch (item->item_type) { case GREETER_ITEM_TYPE_RECT: item->item = gnome_canvas_item_new (group, GNOME_TYPE_CANVAS_RECT, "x1", x1, "y1", y1, "x2", x2, "y2", y2, "fill_color_rgba", item->data.rect.colors[GREETER_ITEM_STATE_NORMAL], NULL); break; case GREETER_ITEM_TYPE_SVG: num_locale = g_strdup (setlocale (LC_NUMERIC, NULL)); setlocale (LC_NUMERIC, "C"); for (i = 0; i < GREETER_ITEM_STATE_MAX; i++) { if (item->data.pixmap.files[i] != NULL) { if (i > 0 && item->data.pixmap.files[0] != NULL && item->data.pixmap.pixbufs[0] != NULL && strcmp (item->data.pixmap.files[0], item->data.pixmap.files[i]) == 0) item->data.pixmap.pixbufs[i] = g_object_ref (item->data.pixmap.pixbufs[0]); else item->data.pixmap.pixbufs[i] = gdk_pixbuf_new_from_file_at_size (item->data.pixmap.files[i], rect.width, rect.height, NULL); } else item->data.pixmap.pixbufs[i] = NULL; } setlocale (LC_NUMERIC, num_locale); g_free (num_locale); num_locale = NULL; /* Fall through */ case GREETER_ITEM_TYPE_PIXMAP: for (i = 0; i < GREETER_ITEM_STATE_MAX; i++) { GdkPixbuf *pb = item->data.pixmap.pixbufs[i]; if (pb != NULL) { item->data.pixmap.pixbufs[i] = transform_pixbuf (pb, (item->data.pixmap.have_tint & (1<data.pixmap.tints[i], (double)item->data.pixmap.alphas[i] / 256.0, rect.width, rect.height); g_object_unref (pb); } } if (item->data.pixmap.pixbufs[GREETER_ITEM_STATE_NORMAL] != NULL) item->item = gnome_canvas_item_new (group, GNOME_TYPE_CANVAS_PIXBUF, "x", (gdouble) x1, "y", (gdouble) y1, "pixbuf", item->data.pixmap.pixbufs[GREETER_ITEM_STATE_NORMAL], NULL); break; case GREETER_ITEM_TYPE_LABEL: text = gdm_common_expand_text (item->data.text.orig_text); /* Justification is taken from the anchor */ if (item->anchor == GTK_ANCHOR_NORTH_WEST || item->anchor == GTK_ANCHOR_SOUTH_WEST || item->anchor == GTK_ANCHOR_WEST) just = GTK_JUSTIFY_LEFT; else if (item->anchor == GTK_ANCHOR_NORTH_EAST || item->anchor == GTK_ANCHOR_SOUTH_EAST || item->anchor == GTK_ANCHOR_EAST) just = GTK_JUSTIFY_RIGHT; else just = GTK_JUSTIFY_CENTER; item->item = gnome_canvas_item_new (group, GREETER_TYPE_CANVAS_TEXT, "text", "", "x", x1, "y", y1, "anchor", item->anchor, "font_desc", item->data.text.fonts[GREETER_ITEM_STATE_NORMAL], "fill_color_rgba", item->data.text.colors[GREETER_ITEM_STATE_NORMAL], "justification", just, NULL); greeter_canvas_item_break_set_string (item, text, TRUE /* markup */, item->data.text.real_max_width, NULL /* width */, NULL /* height */, NULL /* canvas */, item->item); g_free (text); /* if there is an accelerator we do an INCREDIBLE hack */ if (strchr (item->data.text.orig_text, '_') != NULL) { GreeterItemInfo *button; GtkWidget *fake_button = gtk_button_new_with_mnemonic (item->data.text.orig_text); gtk_widget_show (fake_button); GTK_WIDGET_UNSET_FLAGS (fake_button, GTK_CAN_FOCUS); gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (canvas)), GNOME_TYPE_CANVAS_WIDGET, "widget", fake_button, "x", (double)-999.0, "y", (double)-999.0, "height", (double)20.0, "width", (double)20.0, NULL); button = item->my_button; if (button == NULL) button = item; g_signal_connect_data (G_OBJECT (fake_button), "clicked", G_CALLBACK (activate_button), g_strdup (button->id), (GClosureNotify)g_free, 0 /* connect_flags */); } break; case GREETER_ITEM_TYPE_BUTTON: gtkbutton = gtk_button_new_with_mnemonic (item->data.text.orig_text); gtk_widget_set_name (gtkbutton, item->id); if (strcmp (ve_sure_string (item->id), "ok_button") == 0) { gtk_ok_button = GTK_BUTTON (gtkbutton); } else if (strcmp (ve_sure_string (item->id), "cancel_button") == 0) { gtk_start_again_button = GTK_BUTTON (gtkbutton); } g_signal_connect (G_OBJECT (gtkbutton), "clicked", G_CALLBACK (greeter_item_run_button_action_callback), item->id); item->item = gnome_canvas_item_new (group, GNOME_TYPE_CANVAS_WIDGET, "widget", gtkbutton, "x", x1, "y", y1, "height", (double)rect.height, "width", (double)rect.width, NULL); break; case GREETER_ITEM_TYPE_ENTRY: entry = gtk_entry_new (); gtk_widget_set_name (entry, "user-pw-entry"); gtk_entry_set_has_frame (GTK_ENTRY (entry), FALSE); if (gdm_config_get_bool (GDM_KEY_ENTRY_INVISIBLE)) gtk_entry_set_invisible_char (GTK_ENTRY (entry), 0); else if (gdm_config_get_bool (GDM_KEY_ENTRY_CIRCLES)) gtk_entry_set_invisible_char (GTK_ENTRY (entry), 0x25cf); gtk_widget_modify_font (entry, item->data.text.fonts[GREETER_ITEM_STATE_NORMAL]); get_gdk_color_from_rgb (&c, item->data.text.colors[GREETER_ITEM_STATE_NORMAL]); gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &c); if (item->id != NULL && strcmp (item->id, "user-pw-entry") == 0) { /* HACK! Add a menubar, this is kind of evil isn't it, * should probably be done in the pam item setup thingie. * but this is really widget kind of thing. I dunno where * this belongs but it's a hack here. */ item->data.text.menubar = make_menubar (); gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (canvas)), GNOME_TYPE_CANVAS_WIDGET, "widget", item->data.text.menubar, "x", (double)x1, "y", (double)y1, "height", (double)rect.height, "width", (double)rect.width, NULL); greeter_item_register_action_callback ("options_button", (ActionFunc)greeter_options_handler, item->data.text.menubar); /* Here add a tooltip, so that the user knows about F10 */ tooltips = gtk_tooltips_new (); gtk_tooltips_set_tip (tooltips, GTK_WIDGET (entry), _("Answer questions here and press Enter " "when done. For a menu press F10."), NULL); /* FIXME: how to make this accessible??? */ } item->item = gnome_canvas_item_new (group, GNOME_TYPE_CANVAS_WIDGET, "widget", entry, "x", x1, "y", y1, "height", (double)rect.height, "width", (double)rect.width, NULL); /* cursor blinking is evil on remote displays, don't do it forever */ gdm_common_setup_blinking_entry (entry); break; case GREETER_ITEM_TYPE_LIST: /* Note a list type must be setup later and we will add the list store * to it then, depending on the type. Likely userlist is the * only type we support */ if (item->data.list.combo_type) { list = gtk_combo_box_new_text (); item->item = gnome_canvas_item_new (group, GNOME_TYPE_CANVAS_WIDGET, "widget", list, "x", x1, "y", y1, "height", (double)rect.height, "width", (double)rect.width, NULL); } else { list = gtk_tree_view_new (); swin = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swin), GTK_SHADOW_NONE); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (swin), list); item->item = gnome_canvas_item_new (group, GNOME_TYPE_CANVAS_WIDGET, "widget", swin, "x", x1, "y", y1, "height", (double)rect.height, "width", (double)rect.width, NULL); } break; } if (item->item_type == GREETER_ITEM_TYPE_RECT || item->item_type == GREETER_ITEM_TYPE_SVG || item->item_type == GREETER_ITEM_TYPE_PIXMAP || item->item_type == GREETER_ITEM_TYPE_LABEL) g_signal_connect (G_OBJECT (item->item), "event", G_CALLBACK (greeter_item_event_handler), item); } /* This is so evil it hurts */ static char * make_ugly_long_string_with_line_breaks (const char *orig) { const char *p; int n_chars, i; GString *foo = g_string_new (NULL); gboolean in_element = FALSE; n_chars = g_utf8_strlen (orig, -1); p = orig; i = 0; while (i < n_chars) { gunichar ch = g_utf8_get_char (p); if (ch == '<') in_element = TRUE; else if (ch == '>') in_element = FALSE; g_string_append_unichar (foo, ch); if ( ! in_element) g_string_append_unichar (foo, '\n'); p = g_utf8_next_char (p); i++; } return g_string_free (foo, FALSE); } /* This function is very VERY evil */ static gboolean append_word (GString *str, GString *line, GString *word, const char *after, int max_width, const char *textattr, GnomeCanvasItem *canvas_item) { int width, height; char *post = make_ugly_long_string_with_line_breaks (after); char *try = g_strconcat (str->str, word->str, "\n", post, NULL); g_free (post); gnome_canvas_item_set (GNOME_CANVAS_ITEM (canvas_item), textattr, try, NULL); g_free (try); pango_layout_get_pixel_size (GNOME_CANVAS_TEXT (canvas_item)->layout, &width, &height); if (width > max_width) { if ( ! ve_string_empty (line->str)) { if (str->len > 0 && str->str[str->len-1] == ' ') { g_string_truncate (str, str->len-1); } g_string_append_unichar (str, '\n'); } g_string_assign (line, word->str); g_string_append (str, word->str); g_string_assign (word, ""); return TRUE; } else { g_string_append (line, word->str); g_string_append (str, word->str); g_string_assign (word, ""); return FALSE; } } /* This function is very VERY evil */ /* Note that it should just use pango and do all the right things rather then the utter hacks it tries to do. But I couldn't figure out how to do this simply with pango and how to interact with markup properly (it seems easy to do if there is no markup */ void greeter_canvas_item_break_set_string (GreeterItemInfo *info, const char *orig, gboolean markup, int max_width, int *width, int *height, GnomeCanvas *canvas, GnomeCanvasItem *real_item) { PangoLogAttr *attrs; int n_chars; GString *str; GString *word; GString *line; int i; int n_attrs; int ia; const char *p; int in_current_row; GnomeCanvasItem *canvas_item; const char *textattr; int lwidth, lheight; str = g_string_new (NULL); word = g_string_new (NULL); line = g_string_new (NULL); /* avoid errors and even possible crashes */ if (markup && ! pango_parse_markup (orig, -1, 0, NULL, NULL, NULL, NULL)) { markup = FALSE; } textattr = markup ? "markup" : "text"; /* A gross hack */ if (real_item != NULL) canvas_item = real_item; else canvas_item = gnome_canvas_item_new (gnome_canvas_root (canvas), GREETER_TYPE_CANVAS_TEXT, textattr, "", "x", 0.0, "y", 0.0, "font_desc", info->data.text.fonts[GREETER_ITEM_STATE_NORMAL], NULL); if (max_width == 0) { gnome_canvas_item_set (GNOME_CANVAS_ITEM (canvas_item), textattr, orig, NULL); pango_layout_get_pixel_size (GNOME_CANVAS_TEXT (canvas_item)->layout, width, height); if (real_item != canvas_item) { gtk_object_destroy (GTK_OBJECT (canvas_item)); g_string_free (line, TRUE); g_string_free (word, TRUE); g_string_free (str, TRUE); } return; } n_chars = g_utf8_strlen (orig, -1); gnome_canvas_item_set (GNOME_CANVAS_ITEM (canvas_item), textattr, orig, NULL); pango_layout_get_pixel_size (GNOME_CANVAS_TEXT (canvas_item)->layout, &lwidth, &lheight); if (lwidth <= max_width) { if (width != NULL) *width = lwidth; if (height != NULL) *height = lheight; if (real_item != canvas_item) { gtk_object_destroy (GTK_OBJECT (canvas_item)); g_string_free (line, TRUE); g_string_free (word, TRUE); g_string_free (str, TRUE); } return; } pango_layout_get_log_attrs (GNOME_CANVAS_TEXT (canvas_item)->layout, &attrs, &n_attrs); i = 0; ia = 0; in_current_row = 0; p = orig; while (i < n_chars) { gunichar ch; ch = g_utf8_get_char (p); if (markup && ch == '<') { while (i < n_chars) { g_string_append_unichar (word, ch); p = g_utf8_next_char (p); i++; if (ch == '>') { ch = g_utf8_get_char (p); break; } else { ch = g_utf8_get_char (p); } } if (i >= n_chars) break; } if (attrs[ia].is_line_break && in_current_row > 0) { if (append_word (str, line, word, p, max_width, textattr, canvas_item)) in_current_row = 0; } in_current_row++; g_string_append_unichar (word, ch); p = g_utf8_next_char (p); i++; ia++; /* eeek! */ if (ia >= n_attrs) { while (i < n_chars) { ch = g_utf8_get_char (p); g_string_append_unichar (word, ch); p = g_utf8_next_char (p); i++; } break; } } if ( ! ve_string_empty (word->str)) append_word (str, line, word, "", max_width, textattr, canvas_item); gnome_canvas_item_set (GNOME_CANVAS_ITEM (canvas_item), textattr, str->str, NULL); pango_layout_get_pixel_size (GNOME_CANVAS_TEXT (canvas_item)->layout, width, height); if (real_item != canvas_item) gtk_object_destroy (GTK_OBJECT (canvas_item)); g_free (attrs); g_string_free (line, TRUE); g_string_free (word, TRUE); g_string_free (str, TRUE); }