Patchwork [4/8] gtk: add virtual console support (v2)

login
register
mail settings
Submitter Anthony Liguori
Date Feb. 26, 2012, 11:46 p.m.
Message ID <1330299995-8688-5-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/143138/
State New
Headers show

Comments

Anthony Liguori - Feb. 26, 2012, 11:46 p.m.
This enables VteTerminal to be used to render the text consoles.  VteTerminal is
the same widget used by gnome-terminal which means it's VT100 emulation is as
good as they come.

It's also screen reader accessible, supports copy/paste, proper scrolling and
most of the other features you would expect from a terminal widget.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
v1 -> v2
 - make sure to activate the menu item when switching tabs
 - fix sizing of non-0 pages
---
 console.c |    4 +-
 console.h |    4 +-
 ui/gtk.c  |  160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 164 insertions(+), 4 deletions(-)
Kevin Wolf - Feb. 27, 2012, 10:45 a.m.
Am 27.02.2012 00:46, schrieb Anthony Liguori:
> This enables VteTerminal to be used to render the text consoles.  VteTerminal is
> the same widget used by gnome-terminal which means it's VT100 emulation is as
> good as they come.
> 
> It's also screen reader accessible, supports copy/paste, proper scrolling and
> most of the other features you would expect from a terminal widget.
> 
> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
> ---
> v1 -> v2
>  - make sure to activate the menu item when switching tabs
>  - fix sizing of non-0 pages
> ---
>  console.c |    4 +-
>  console.h |    4 +-
>  ui/gtk.c  |  160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 164 insertions(+), 4 deletions(-)
> 
> diff --git a/console.c b/console.c
> index 6434ed0..c12f02a 100644
> --- a/console.c
> +++ b/console.c
> @@ -1551,9 +1551,9 @@ static CharDriverState *text_console_init(QemuOpts *opts)
>  
>  static VcHandler *vc_handler = text_console_init;
>  
> -int vc_init(QemuOpts *opts, CharDriverState **_chr)
> +CharDriverState *vc_init(QemuOpts *opts)
>  {
> -    return vc_handler(opts, _chr);
> +    return vc_handler(opts);
>  }
>  
>  void register_vc_handler(VcHandler *handler)
> diff --git a/console.h b/console.h
> index 9b4b390..27d7929 100644
> --- a/console.h
> +++ b/console.h
> @@ -363,9 +363,9 @@ void qemu_console_resize(DisplayState *ds, int width, int height);
>  void qemu_console_copy(DisplayState *ds, int src_x, int src_y,
>                         int dst_x, int dst_y, int w, int h);
>  
> -typedef int (VcHandler)(QemuOpts *, CharDriverState **);
> +typedef CharDriverState *(VcHandler)(QemuOpts *);
>  
> -int vc_init(QemuOpts *opts, CharDriverState **_chr);
> +CharDriverState *vc_init(QemuOpts *opts);
>  void register_vc_handler(VcHandler *handler);
>  
>  /* sdl.c */
> diff --git a/ui/gtk.c b/ui/gtk.c
> index 591a987..0579a55 100644
> --- a/ui/gtk.c
> +++ b/ui/gtk.c
> @@ -56,6 +56,8 @@
>  #define dprintf(fmt, ...) do { } while (0)
>  #endif
>  
> +#define MAX_VCS 10
> +
>  typedef struct VirtualConsole
>  {
>      GtkWidget *menu_item;
> @@ -79,6 +81,9 @@ typedef struct GtkDisplayState
>      GtkWidget *view_menu;
>      GtkWidget *vga_item;
>  
> +    int nb_vcs;
> +    VirtualConsole vc[MAX_VCS];
> +
>      GtkWidget *show_tabs_item;
>  
>      GtkWidget *vbox;
> @@ -400,6 +405,15 @@ static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
>  
>      if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vga_item))) {
>          gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), 0);
> +    } else {
> +        int i;
> +
> +        for (i = 0; i < s->nb_vcs; i++) {
> +            if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vc[i].menu_item))) {
> +                gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), i + 1);
> +                break;
> +            }
> +        }
>      }
>  }
>  
> @@ -418,16 +432,154 @@ static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
>                             gpointer data)
>  {
>      GtkDisplayState *s = data;
> +    guint last_page;
>  
>      if (!gtk_widget_get_realized(s->notebook)) {
>          return;
>      }
>  
> +    last_page = gtk_notebook_get_current_page(nb);
> +
> +    if (last_page) {
> +        gtk_widget_set_size_request(s->vc[last_page - 1].terminal, -1, -1);
> +    }
> +
> +    if (arg2 == 0) {
> +        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->vga_item), TRUE);
> +    } else {
> +        VirtualConsole *vc = &s->vc[arg2 - 1];
> +        VteTerminal *term = VTE_TERMINAL(vc->terminal);
> +        int width, height;
> +
> +        width = 80 * vte_terminal_get_char_width(term);
> +        height = 25 * vte_terminal_get_char_height(term);
> +
> +        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
> +        gtk_widget_set_size_request(vc->terminal, width, height);
> +    }        
> +
>      gd_update_cursor(s, TRUE);
>  }
>  
> +/** Virtual Console Callbacks **/
> +
> +static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
> +{
> +    VirtualConsole *vc = chr->opaque;
> +
> +    return write(vc->fd, buf, len);
> +}
> +
> +static int nb_vcs;
> +static CharDriverState *vcs[MAX_VCS];
> +
> +static CharDriverState *gd_vc_handler(QemuOpts *opts)
> +{
> +    CharDriverState *chr;
> +
> +    chr = g_malloc0(sizeof(*chr));
> +    chr->chr_write = gd_vc_chr_write;
> +
> +    vcs[nb_vcs++] = chr;
> +
> +    return chr;
> +}
> +
>  void early_gtk_display_init(void)
>  {
> +    register_vc_handler(gd_vc_handler);
> +}
> +
> +static gboolean gd_vc_in(GIOChannel *chan, GIOCondition cond, void *opaque)
> +{
> +    VirtualConsole *vc = opaque;
> +    uint8_t buffer[1024];
> +    ssize_t len;
> +
> +    len = read(vc->fd, buffer, sizeof(buffer));
> +    if (len <= 0) {
> +        return FALSE;
> +    }
> +
> +    qemu_chr_be_write(vc->chr, buffer, len);
> +
> +    return TRUE;
> +}
> +
> +static GSList *gd_vc_init(GtkDisplayState *s, VirtualConsole *vc, int index, GSList *group)
> +{
> +    const char *label;
> +    char buffer[32];
> +    char path[32];
> +    VtePty *pty;
> +    GIOChannel *chan;
> +    GtkWidget *scrolled_window;
> +    GtkAdjustment *hadjustment, *vadjustment;

Doesn't build without modifications:

ui/gtk.c: In function 'gd_vc_init':
ui/gtk.c:661:20: error: variable 'hadjustment' set but not used
[-Werror=unused-but-set-variable]
cc1: all warnings being treated as errors

Kevin

Patch

diff --git a/console.c b/console.c
index 6434ed0..c12f02a 100644
--- a/console.c
+++ b/console.c
@@ -1551,9 +1551,9 @@  static CharDriverState *text_console_init(QemuOpts *opts)
 
 static VcHandler *vc_handler = text_console_init;
 
-int vc_init(QemuOpts *opts, CharDriverState **_chr)
+CharDriverState *vc_init(QemuOpts *opts)
 {
-    return vc_handler(opts, _chr);
+    return vc_handler(opts);
 }
 
 void register_vc_handler(VcHandler *handler)
diff --git a/console.h b/console.h
index 9b4b390..27d7929 100644
--- a/console.h
+++ b/console.h
@@ -363,9 +363,9 @@  void qemu_console_resize(DisplayState *ds, int width, int height);
 void qemu_console_copy(DisplayState *ds, int src_x, int src_y,
                        int dst_x, int dst_y, int w, int h);
 
-typedef int (VcHandler)(QemuOpts *, CharDriverState **);
+typedef CharDriverState *(VcHandler)(QemuOpts *);
 
-int vc_init(QemuOpts *opts, CharDriverState **_chr);
+CharDriverState *vc_init(QemuOpts *opts);
 void register_vc_handler(VcHandler *handler);
 
 /* sdl.c */
diff --git a/ui/gtk.c b/ui/gtk.c
index 591a987..0579a55 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -56,6 +56,8 @@ 
 #define dprintf(fmt, ...) do { } while (0)
 #endif
 
+#define MAX_VCS 10
+
 typedef struct VirtualConsole
 {
     GtkWidget *menu_item;
@@ -79,6 +81,9 @@  typedef struct GtkDisplayState
     GtkWidget *view_menu;
     GtkWidget *vga_item;
 
+    int nb_vcs;
+    VirtualConsole vc[MAX_VCS];
+
     GtkWidget *show_tabs_item;
 
     GtkWidget *vbox;
@@ -400,6 +405,15 @@  static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
 
     if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vga_item))) {
         gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), 0);
+    } else {
+        int i;
+
+        for (i = 0; i < s->nb_vcs; i++) {
+            if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vc[i].menu_item))) {
+                gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), i + 1);
+                break;
+            }
+        }
     }
 }
 
@@ -418,16 +432,154 @@  static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
                            gpointer data)
 {
     GtkDisplayState *s = data;
+    guint last_page;
 
     if (!gtk_widget_get_realized(s->notebook)) {
         return;
     }
 
+    last_page = gtk_notebook_get_current_page(nb);
+
+    if (last_page) {
+        gtk_widget_set_size_request(s->vc[last_page - 1].terminal, -1, -1);
+    }
+
+    if (arg2 == 0) {
+        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->vga_item), TRUE);
+    } else {
+        VirtualConsole *vc = &s->vc[arg2 - 1];
+        VteTerminal *term = VTE_TERMINAL(vc->terminal);
+        int width, height;
+
+        width = 80 * vte_terminal_get_char_width(term);
+        height = 25 * vte_terminal_get_char_height(term);
+
+        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
+        gtk_widget_set_size_request(vc->terminal, width, height);
+    }        
+
     gd_update_cursor(s, TRUE);
 }
 
+/** Virtual Console Callbacks **/
+
+static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
+{
+    VirtualConsole *vc = chr->opaque;
+
+    return write(vc->fd, buf, len);
+}
+
+static int nb_vcs;
+static CharDriverState *vcs[MAX_VCS];
+
+static CharDriverState *gd_vc_handler(QemuOpts *opts)
+{
+    CharDriverState *chr;
+
+    chr = g_malloc0(sizeof(*chr));
+    chr->chr_write = gd_vc_chr_write;
+
+    vcs[nb_vcs++] = chr;
+
+    return chr;
+}
+
 void early_gtk_display_init(void)
 {
+    register_vc_handler(gd_vc_handler);
+}
+
+static gboolean gd_vc_in(GIOChannel *chan, GIOCondition cond, void *opaque)
+{
+    VirtualConsole *vc = opaque;
+    uint8_t buffer[1024];
+    ssize_t len;
+
+    len = read(vc->fd, buffer, sizeof(buffer));
+    if (len <= 0) {
+        return FALSE;
+    }
+
+    qemu_chr_be_write(vc->chr, buffer, len);
+
+    return TRUE;
+}
+
+static GSList *gd_vc_init(GtkDisplayState *s, VirtualConsole *vc, int index, GSList *group)
+{
+    const char *label;
+    char buffer[32];
+    char path[32];
+    VtePty *pty;
+    GIOChannel *chan;
+    GtkWidget *scrolled_window;
+    GtkAdjustment *hadjustment, *vadjustment;
+    int master_fd, slave_fd, ret;
+    struct termios tty;
+
+    snprintf(buffer, sizeof(buffer), "vc%d", index);
+    snprintf(path, sizeof(path), "<QEMU>/View/VC%d", index);
+
+    vc->chr = vcs[index];
+
+    if (vc->chr->label) {
+        label = vc->chr->label;
+    } else {
+        label = buffer;
+    }
+
+    vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, label);
+    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
+    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(vc->menu_item), path);
+    gtk_accel_map_add_entry(path, GDK_KEY_2 + index, GDK_CONTROL_MASK | GDK_MOD1_MASK);
+
+    vc->terminal = vte_terminal_new();
+
+    ret = openpty(&master_fd, &slave_fd, NULL, NULL, NULL);
+    g_assert(ret != -1);
+
+    /* Set raw attributes on the pty. */
+    tcgetattr(slave_fd, &tty);
+    cfmakeraw(&tty);
+    tcsetattr(slave_fd, TCSAFLUSH, &tty);
+
+    pty = vte_pty_new_foreign(master_fd, NULL);
+
+    vte_terminal_set_pty_object(VTE_TERMINAL(vc->terminal), pty);
+
+    vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->terminal), -1);
+
+    vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->terminal));
+    hadjustment = NULL;
+
+    scrolled_window = gtk_scrolled_window_new(NULL, vadjustment);
+    gtk_container_add(GTK_CONTAINER(scrolled_window), vc->terminal);
+
+    vte_terminal_set_size(VTE_TERMINAL(vc->terminal), 80, 25);
+
+    vc->fd = slave_fd;
+    vc->chr->opaque = vc;
+    vc->scrolled_window = scrolled_window;
+
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vc->scrolled_window),
+                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), scrolled_window, gtk_label_new(label));
+    g_signal_connect(vc->menu_item, "activate",
+                     G_CALLBACK(gd_menu_switch_vc), s);
+
+    gtk_menu_append(GTK_MENU(s->view_menu), vc->menu_item);
+
+    qemu_chr_generic_open(vc->chr);
+    if (vc->chr->init) {
+        vc->chr->init(vc->chr);
+    }
+
+    chan = g_io_channel_unix_new(vc->fd);
+    g_io_add_watch(chan, G_IO_IN, gd_vc_in, vc);
+
+    return group;
 }
 
 /** Window Creation **/
@@ -467,6 +619,7 @@  static void gd_create_menus(GtkDisplayState *s)
     GtkAccelGroup *accel_group;
     GSList *group = NULL;
     GtkWidget *separator;
+    int i;
 
     accel_group = gtk_accel_group_new();
     s->file_menu = gtk_menu_new();
@@ -493,6 +646,13 @@  static void gd_create_menus(GtkDisplayState *s)
     gtk_accel_map_add_entry("<QEMU>/View/VGA", GDK_KEY_1, GDK_CONTROL_MASK | GDK_MOD1_MASK);
     gtk_menu_append(GTK_MENU(s->view_menu), s->vga_item);
 
+    for (i = 0; i < nb_vcs; i++) {
+        VirtualConsole *vc = &s->vc[i];
+
+        group = gd_vc_init(s, vc, i, group);
+        s->nb_vcs++;
+    }
+
     separator = gtk_separator_menu_item_new();
     gtk_menu_append(GTK_MENU(s->view_menu), separator);