]> git.ipfire.org Git - thirdparty/plymouth.git/commitdiff
Add initial boot log viewer from Matthias
authorRay Strode <rstrode@redhat.com>
Tue, 29 Jul 2008 18:01:09 +0000 (14:01 -0400)
committerRay Strode <rstrode@redhat.com>
Tue, 12 Aug 2008 17:18:51 +0000 (13:18 -0400)
Since plymouth conceals boot messages from the user during boot
up, it should provide a way for users to get at the boot
messages after login.  In particular, if there was a problem
during boot up, the user should get notified at the login
screen.

This commit adds the first cut at a log viewer without
any of the login screen integration bits.

configure.ac
src/Makefile.am
src/viewer/Makefile.am [new file with mode: 0644]
src/viewer/plymouth-log-viewer.c [new file with mode: 0644]

index 5f8337dbefda479e8a3e5df588abf08d48351392..da4d8b93482cdb93a3093143ecc9dedd85c6a46a 100644 (file)
@@ -42,6 +42,10 @@ PLYMOUTH_LIBS="-lm -ldl $IMAGE_LIBS"
 AC_SUBST(PLYMOUTH_CFLAGS)
 AC_SUBST(PLYMOUTH_LIBS)
 
+PKG_CHECK_MODULES(GTK, [gtk+-2.0 >= 2.12.0 ])
+AC_SUBST(GTK_CFLAGS)
+AC_SUBST(GTK_LIBS)
+
 AC_ARG_ENABLE(tracing, AS_HELP_STRING([--enable-tracing],[enable verbose tracing code]),enable_tracing=$enableval,enable_tracing=yes)
 
 if test x$enable_tracing = xyes; then
@@ -89,6 +93,7 @@ AC_OUTPUT([Makefile
            src/splash-plugins/details/Makefile
            src/Makefile
            src/client/Makefile
+           src/viewer/Makefile
            src/tests/Makefile
            src/libply/tests/Makefile
            src/client/tests/Makefile
index 8b6ca5b5599fad983f9baae867dbed53f7f2d88a..9c046ac5cf6d005b4f8bde0eeafefd78a38d35f1 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS = libply libplybootsplash . splash-plugins client tests
+SUBDIRS = libply libplybootsplash . splash-plugins client viewer tests
 INCLUDES = -I$(top_srcdir)                                                    \
            -I$(srcdir)/libply                                                 \
            -I$(srcdir)/libplybootsplash                                       \
diff --git a/src/viewer/Makefile.am b/src/viewer/Makefile.am
new file mode 100644 (file)
index 0000000..333a4aa
--- /dev/null
@@ -0,0 +1,12 @@
+SUBDIRS = .
+
+INCLUDES = -I$(top_srcdir)                                                    \
+           -I$(srcdir)
+plymouth_log_viewerdir = $(bindir)
+plymouth_log_viewer_PROGRAMS = plymouth-log-viewer
+
+plymouth_log_viewer_CFLAGS = $(GTK_CFLAGS) -DPLYMOUTH_LOG_DIRECTORY=\"$(localstatedir)/log\"
+plymouth_log_viewer_LDADD = $(GTK_LIBS)
+plymouth_log_viewer_SOURCES = $(srcdir)/plymouth-log-viewer.c
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/viewer/plymouth-log-viewer.c b/src/viewer/plymouth-log-viewer.c
new file mode 100644 (file)
index 0000000..f44becd
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ *  bootmessages - Display boot messages
+ *
+ *  Copyright (C) 2008 Red Hat, Inc.
+ *
+ *  Author: Matthias Clasen
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+/* Compile with:
+ * 
+ * cc -o bootmessages bootmessages.c `pkg-config --cflags --libs gtk+-2.0`
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#ifndef GETTEXT_PACKAGE
+#define GETTEXT_PACKAGE "bootmessages"
+#endif
+
+#ifndef DEFAULT_LOG
+#define DEFAULT_LOG "/var/log/boot.log"
+#endif
+
+static gboolean show_icon = FALSE;
+static gboolean force = FALSE;
+
+static GOptionEntry entries[] = 
+{
+  { "icon", 0, 0, G_OPTION_ARG_NONE, &show_icon, N_("Show a status icon if there are errors"), NULL },
+  { "force", 0, 0, G_OPTION_ARG_NONE, &force, N_("Show the icon even without errors"), NULL },
+  { NULL }
+};
+
+static void
+popup_menu (GtkStatusIcon *icon, 
+            guint          button,
+            guint          activate_time,
+            gpointer       user_data)
+{
+  GtkWidget *menu = user_data;
+  
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, 
+                  gtk_status_icon_position_menu, icon, 
+                  button, activate_time);
+}
+
+static void
+activate_icon (GtkStatusIcon *icon, 
+               gpointer       user_data)
+{
+  GtkWidget *window = user_data;
+
+  gtk_window_present (GTK_WINDOW (window));
+}
+
+static char *
+parse_etc_sysconfig_i18n (void)
+{
+  char *content;
+  gsize length;
+  char *lang;
+  char *p, *q;
+
+  lang = NULL;
+
+  if (!g_file_get_contents ("/etc/sysconfig/i18n", &content, &length, NULL))
+    return NULL;
+
+  p = strstr (content, "LANG=");
+  if (!p)
+    goto out;
+
+  p = strchr (p, '"');
+  if (!p)
+    goto out;
+
+  p++;
+  q = strchr (p, '"');
+
+  if (!q)
+    goto out; 
+
+  lang = g_strndup (p, q - p);
+
+out:
+  g_free (content);
+
+  g_print ("boot lang: %s\n", lang);
+  return lang;
+}
+
+static GtkTextBuffer *
+read_boot_log (const char  *file, 
+               int         *seen_errors, 
+               GError     **error)
+{
+  char *content;
+  char *content_utf8;
+  gsize length;
+  char *p, *q;
+  GtkTextBuffer *buffer;
+  GtkTextTag *blue;
+  GtkTextIter iter;
+  const char *failed;
+  const char *warning;
+  GString *partial;
+  char *boot_lang;
+  char *lang;
+
+  if (!g_file_get_contents (file, &content, &length, error))
+    return NULL;
+
+  /* 
+   * We need to briefly change to the locale from /etc/sysconfig/i18n to:
+   * - pick the right initscripts translations
+   * - convert boot.log to UTF-8
+   */
+
+  boot_lang = parse_etc_sysconfig_i18n ();
+  lang = setlocale (LC_ALL, NULL);
+
+  if (boot_lang == NULL)
+    boot_lang = g_strdup (lang);
+
+  setlocale (LC_ALL, boot_lang);
+
+  bind_textdomain_codeset ("initscripts", "UTF-8");
+  failed = dgettext ("initscripts", "FAILED");
+  warning = dgettext ("initscripts", "WARNING"); 
+
+  content_utf8 = g_locale_to_utf8 (content, length, NULL, NULL, NULL);
+  if (content_utf8)
+    {
+      g_free (content);
+      content = content_utf8;   
+    }
+  
+  setlocale (LC_ALL, lang);
+
+  if (strstr (content, failed) != NULL)
+    *seen_errors = 2;
+  else if (strstr (content, warning) != NULL)
+    *seen_errors = 1;
+  else
+    *seen_errors = 0;
+
+  buffer = gtk_text_buffer_new (NULL);
+  gtk_text_buffer_create_tag (buffer, "blue", "foreground", "blue", NULL);
+  gtk_text_buffer_create_tag (buffer, "green", "foreground", "green", NULL);
+  gtk_text_buffer_create_tag (buffer, "red", "foreground", "red", NULL);
+  gtk_text_buffer_create_tag (buffer, "yellow", "foreground", "yellow", NULL);
+
+  partial = g_string_new ("");
+
+  p = content;
+  while (*p) 
+    {
+      switch (*p) 
+        {
+          case '\r': 
+            /* keep isolated \r */
+            if (p[1] != '\r' && p[-1] != '\r' &&
+                p[1] != '\n' && p[-1] != '\n')
+              {
+                gtk_text_buffer_get_end_iter (buffer, &iter);
+                gtk_text_buffer_insert (buffer, &iter, p, 1);
+              }
+            p++;
+            break;
+          case '\t':
+            gtk_text_buffer_get_end_iter (buffer, &iter);
+            gtk_text_buffer_insert (buffer, &iter, "        ", 8); 
+            p++;
+            break;
+          case '\033':
+            if (strncmp (p, "\033[0;34m", 7) == 0 && (q = strstr (p, "\033[0;39m")))
+              {
+                p += 7;
+                gtk_text_buffer_get_end_iter (buffer, &iter);
+                gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, p, q - p, "blue", NULL); 
+                p = q + 7; 
+              }
+            else if (strncmp (p, "\033[60G", 5) == 0) 
+              {
+                gtk_text_buffer_get_end_iter (buffer, &iter);
+                gtk_text_buffer_insert (buffer, &iter, "\t", 1);
+                p += 5;
+              }
+            else if (strncmp (p, "\033[0;31m", 7) == 0 && (q = strstr (p, "\033[0;39m")))
+              {
+                p += 7;
+                gtk_text_buffer_get_end_iter (buffer, &iter);
+                gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, p, q - p, "red", NULL); 
+                p = q + 7; 
+              }
+            else if (strncmp (p, "\033[0;32m", 7) == 0 && (q = strstr (p, "\033[0;39m")))
+              {
+                p += 7;
+                gtk_text_buffer_get_end_iter (buffer, &iter);
+                gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, p, q - p, "green", NULL); 
+                p = q + 7; 
+              }
+            else if (strncmp (p, "\033[0;33m", 7) == 0 && (q = strstr (p, "\033[0;39m")))
+              {
+                p += 7;
+                gtk_text_buffer_get_end_iter (buffer, &iter);
+                gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, p, q - p, "yellow", NULL); 
+                p = q + 7; 
+              }
+            else if (strncmp (p, "\033%G", 3) == 0) 
+              p += 3;
+            else
+              p++;
+            break;
+          default:
+            /* GtkTextBuffer doesn't let us insert partial utf-8 characters */
+            g_string_append_c (partial, *p);
+            if (g_utf8_get_char_validated (partial->str, partial->len) != (gunichar)-2)
+              {
+                gtk_text_buffer_get_end_iter (buffer, &iter);
+                gtk_text_buffer_insert (buffer, &iter, partial->str, partial->len);
+                g_string_truncate (partial, 0);
+              }
+            p++;
+            break;
+        }
+    }
+
+  g_string_free (partial, TRUE);
+  g_free (content);
+
+  return buffer;
+}
+
+static void
+close_window (GtkWidget *window)
+{
+  if (show_icon)
+    gtk_widget_hide (window);
+  else
+    gtk_main_quit ();
+}
+
+static GtkWidget *
+create_window (GtkTextBuffer *buffer)
+{
+  GtkWidget *window;
+  GtkWidget *scrolledwin;
+  GtkWidget *box;
+  GtkWidget *terminal;
+  GtkWidget *bbox;
+  GtkWidget *close_button;
+  PangoTabArray *tabs;
+  int width, height;
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
+  gtk_window_set_title (GTK_WINDOW (window), _("Boot messages"));
+  gtk_container_set_border_width (GTK_CONTAINER (window), 12);
+
+  width = MIN (800, 0.75 * gdk_screen_get_width (gdk_screen_get_default ()));
+  height = MIN (600, 0.75 * gdk_screen_get_height (gdk_screen_get_default ()));
+  gtk_window_set_default_size (GTK_WINDOW (window), width, height);
+
+  box = gtk_vbox_new (FALSE, 0);
+  scrolledwin = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin), 
+                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwin), 
+                                       GTK_SHADOW_IN);
+  terminal = gtk_text_view_new_with_buffer (buffer);
+  gtk_text_view_set_editable (GTK_TEXT_VIEW (terminal), FALSE);
+  tabs = pango_tab_array_new_with_positions (1, TRUE, PANGO_TAB_LEFT, width - 130);
+  gtk_text_view_set_tabs (GTK_TEXT_VIEW (terminal), tabs);
+  gtk_text_view_set_left_margin (GTK_TEXT_VIEW (terminal), 12);
+  gtk_text_view_set_right_margin (GTK_TEXT_VIEW (terminal), 12);
+
+  bbox = gtk_hbutton_box_new ();
+  gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
+  close_button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+
+  gtk_container_add (GTK_CONTAINER (window), box);
+  gtk_box_pack_start (GTK_BOX (box), scrolledwin, TRUE, TRUE, 6);
+  gtk_container_add (GTK_CONTAINER (scrolledwin), terminal);
+  gtk_box_pack_start (GTK_BOX (box), bbox, FALSE, TRUE, 6);
+  gtk_box_pack_start (GTK_BOX (bbox), close_button, FALSE, TRUE, 6);
+
+  g_signal_connect (window, "delete-event", 
+                    G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+  g_signal_connect_swapped (close_button, "clicked", 
+                            G_CALLBACK (close_window), window);
+
+  gtk_widget_show_all (box);
+
+  return window;
+}
+
+int
+main (int argc, char *argv[])
+{
+  GtkStatusIcon *icon;
+  GtkWidget *menu;
+  GtkWidget *quit_item;
+  GtkWidget *window;
+  GtkTextBuffer *buffer;
+  GError *error = NULL;
+  int seen_errors;
+  gchar *file;
+
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+  textdomain (GETTEXT_PACKAGE);
+
+  if (!gtk_init_with_args (&argc, &argv, 
+                           _("[FILE]"), entries, GETTEXT_PACKAGE, &error))
+    {
+      g_print ("%s\n", error ? error->message : "Beep");
+      exit (1);
+    }
+
+  if (argc > 1)
+    file = argv[1];
+  else
+    file = DEFAULT_LOG;
+
+  buffer = read_boot_log (file, &seen_errors, &error);
+  if (buffer == NULL)
+    {
+      g_print ("%s\n", error ? error->message : "Blop");
+      exit (1);
+    }
+
+  window = create_window (buffer);
+
+  if (show_icon)
+    {
+      menu = gtk_menu_new ();
+      quit_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_QUIT, NULL);
+      gtk_menu_append (GTK_MENU (menu), quit_item);
+      gtk_widget_show_all (menu);
+      g_signal_connect (quit_item, "activate", G_CALLBACK (gtk_main_quit), NULL);
+  
+      icon = gtk_status_icon_new ();
+      if (seen_errors == 2)
+        gtk_status_icon_set_from_stock (icon, GTK_STOCK_DIALOG_WARNING);
+      else if (seen_errors == 1 || force)
+        gtk_status_icon_set_from_stock (icon, GTK_STOCK_INFO);
+      else
+        exit (0);
+
+      gtk_status_icon_set_tooltip (icon, _("Boot messages"));
+
+      g_signal_connect (icon, "activate", G_CALLBACK (activate_icon), window);
+      g_signal_connect (icon, "popup-menu", G_CALLBACK (popup_menu), menu);
+    }
+  else
+    gtk_window_present (GTK_WINDOW (window));
+
+  gtk_main ();
+
+  return 0;
+}