]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - unix/gtkwin.c
Rationalise and document log options somewhat.
[PuTTY.git] / unix / gtkwin.c
index 524754f7b8f1db0bc0feda48edafa539063324c5..b1c86dd896aff5e5423f4a4146a402bcfacfa725 100644 (file)
@@ -84,8 +84,33 @@ struct gui_data {
        *restartitem;
     GtkWidget *sessionsmenu;
 #ifndef NO_BACKING_PIXMAPS
+    /*
+     * Server-side pixmap which we use to cache the terminal window's
+     * contents. When we draw text in the terminal, we draw it to this
+     * pixmap first, and then blit from there to the actual window;
+     * this way, X expose events can be handled with an absolute
+     * minimum of network traffic, by just sending a command to
+     * re-blit an appropriate rectangle from this pixmap.
+     */
     GdkPixmap *pixmap;
 #endif
+#ifdef DRAW_TEXT_CAIRO
+    /*
+     * If we're drawing using Cairo, we cache the same image on the
+     * client side in a Cairo surface.
+     *
+     * In GTK2+Cairo, this happens _as well_ as having the server-side
+     * pixmap cache above; in GTK3+Cairo, server-side pixmaps are
+     * deprecated, so we _just_ have this client-side cache. In the
+     * latter case that means we have to transmit a big wodge of
+     * bitmap data over the X connection on every expose event; but
+     * GTK3 apparently deliberately provides no way to avoid that
+     * inefficiency, and at least this way we don't _also_ have to
+     * redo any font rendering just because the window was temporarily
+     * covered.
+     */
+    cairo_surface_t *surface;
+#endif
 #if GTK_CHECK_VERSION(2,0,0)
     GtkIMContext *imc;
 #endif
@@ -135,6 +160,9 @@ struct gui_data {
     int ngtkargs;
     guint32 input_event_time; /* Timestamp of the most recent input event. */
     int reconfiguring;
+#if GTK_CHECK_VERSION(3,4,0)
+    gdouble cumulative_scroll;
+#endif
     /* Cached things out of conf that we refer to a lot */
     int bold_style;
     int window_border;
@@ -515,17 +543,31 @@ gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
        need_size = 1;
     }
 
+    {
+        int backing_w = w * inst->font_width + 2*inst->window_border;
+        int backing_h = h * inst->font_height + 2*inst->window_border;
+
 #ifndef NO_BACKING_PIXMAPS
-    if (inst->pixmap) {
-       gdk_pixmap_unref(inst->pixmap);
-       inst->pixmap = NULL;
-    }
+        if (inst->pixmap) {
+            gdk_pixmap_unref(inst->pixmap);
+            inst->pixmap = NULL;
+        }
 
-    inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(widget),
-                                 (w * inst->font_width + 2*inst->window_border),
-                                 (h * inst->font_height + 2*inst->window_border), -1);
+        inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(widget),
+                                      backing_w, backing_h, -1);
 #endif
 
+#ifdef DRAW_TEXT_CAIRO
+        if (inst->surface) {
+            cairo_surface_destroy(inst->surface);
+            inst->surface = NULL;
+        }
+
+        inst->surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
+                                                   backing_w, backing_h);
+#endif
+    }
+
     draw_backing_rect(inst);
 
     if (need_size && inst->term) {
@@ -563,40 +605,21 @@ static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
 
-    if (inst->term) {
-        struct draw_ctx adctx, *dctx = &adctx;
+    /*
+     * GTK3 window redraw: we always expect Cairo to be enabled, so
+     * that inst->surface exists, and pixmaps to be disabled, so that
+     * inst->pixmap does not exist. Hence, we just blit from
+     * inst->surface to the window.
+     */
+    if (inst->surface) {
         GdkRectangle dirtyrect;
 
-        dctx->inst = inst;
-        dctx->uctx.type = DRAWTYPE_CAIRO;
-        dctx->uctx.u.cairo.widget = widget;
-        dctx->uctx.u.cairo.cr = cr;
-        cairo_setup_dctx(dctx);
-
         gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
 
-        /*
-         * As in window.c, we clear the 'immediately' flag in the
-         * term_paint() call if the terminal has an update pending, in
-         * case we're constrained within this event to only draw on
-         * the exposed rectangle of the window. (Because if the whole
-         * of a character cell needs a redraw due to a terminal
-         * contents change, the last thing we want is to give it a
-         * _partial_ redraw here due to system-imposed clipping, and
-         * then have the next main terminal update believe it's been
-         * redrawn in full.)
-         *
-         * I don't actually know if GTK draw events will constrain us
-         * in this way, but it's best to be careful...
-         */
-        term_paint(inst->term, dctx,
-                   (dirtyrect.x - inst->window_border) / inst->font_width,
-                   (dirtyrect.y - inst->window_border) / inst->font_height,
-                   (dirtyrect.x + dirtyrect.width -
-                    inst->window_border) / inst->font_width,
-                   (dirtyrect.y + dirtyrect.height -
-                    inst->window_border) / inst->font_height,
-                   !inst->term->window_update_pending);
+        cairo_set_source_surface(cr, inst->surface, 0, 0);
+        cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
+                        dirtyrect.width, dirtyrect.height);
+        cairo_fill(cr);
     }
 
     return TRUE;
@@ -608,8 +631,8 @@ gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
 
 #ifndef NO_BACKING_PIXMAPS
     /*
-     * Pass the exposed rectangle to terminal.c, which will call us
-     * back to do the actual painting.
+     * Draw to the exposed part of the window from the server-side
+     * backing pixmap.
      */
     if (inst->pixmap) {
        gdk_draw_pixmap(gtk_widget_get_window(widget),
@@ -621,17 +644,18 @@ gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
                        event->area.width, event->area.height);
     }
 #else
-    if (inst->term) {
-        Context ctx = get_ctx(inst);
-        term_paint(inst->term, ctx,
-                   (event->area.x - inst->window_border) / inst->font_width,
-                   (event->area.y - inst->window_border) / inst->font_height,
-                   (event->area.x + event->area.width -
-                    inst->window_border) / inst->font_width,
-                   (event->area.y + event->area.height -
-                    inst->window_border) / inst->font_height,
-                   !inst->term->window_update_pending);
-        free_ctx(ctx);
+    /*
+     * Failing that, draw from the client-side Cairo surface. (We
+     * should never be compiled in a context where we have _neither_
+     * inst->surface nor inst->pixmap.)
+     */
+    if (inst->surface) {
+        cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
+        cairo_set_source_surface(cr, inst->surface, 0, 0);
+        cairo_rectangle(cr, event->area.x, event->area.y,
+                       event->area.width, event->area.height);
+        cairo_fill(cr);
+        cairo_destroy(cr);
     }
 #endif
 
@@ -795,10 +819,6 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
     }
 
     if (event->type == GDK_KEY_PRESS) {
-       /*
-        * NYI: Compose key (!!! requires Unicode faff before even trying)
-        */
-
        /*
         * If Alt has just been pressed, we start potentially
         * accumulating an Alt+numberpad code. We do this by
@@ -1762,6 +1782,61 @@ void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data)
 }
 #endif
 
+#define SCROLL_INCREMENT_LINES 5
+
+#if GTK_CHECK_VERSION(3,4,0)
+gboolean scroll_internal(struct gui_data *inst, gdouble delta, guint state,
+                        gdouble ex, gdouble ey)
+{
+    int shift, ctrl, alt, x, y, raw_mouse_mode;
+
+    show_mouseptr(inst, 1);
+
+    shift = state & GDK_SHIFT_MASK;
+    ctrl = state & GDK_CONTROL_MASK;
+    alt = state & inst->meta_mod_mask;
+
+    x = (ex - inst->window_border) / inst->font_width;
+    y = (ey - inst->window_border) / inst->font_height;
+
+    raw_mouse_mode =
+        send_raw_mouse && !(shift && conf_get_int(inst->conf,
+                                                  CONF_mouse_override));
+
+    inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES;
+
+    if (!raw_mouse_mode) {
+        int scroll_lines = (int)inst->cumulative_scroll; /* rounds toward 0 */
+        if (scroll_lines) {
+            term_scroll(inst->term, 0, scroll_lines);
+            inst->cumulative_scroll -= scroll_lines;
+        }
+        return TRUE;
+    } else {
+        int scroll_events = (int)(inst->cumulative_scroll /
+                                  SCROLL_INCREMENT_LINES);
+        if (scroll_events) {
+            int button;
+
+            inst->cumulative_scroll -= scroll_events * SCROLL_INCREMENT_LINES;
+
+            if (scroll_events > 0) {
+                button = MBT_WHEEL_DOWN;
+            } else {
+                button = MBT_WHEEL_UP;
+                scroll_events = -scroll_events;
+            }
+
+            while (scroll_events-- > 0) {
+                term_mouse(inst->term, button, translate_button(button),
+                           MA_CLICK, x, y, shift, ctrl, alt);
+            }
+        }
+        return TRUE;
+    }
+}
+#endif
+
 gboolean button_internal(struct gui_data *inst, guint32 timestamp,
                         GdkEventType type, guint ebutton, guint state,
                         gdouble ex, gdouble ey)
@@ -1783,11 +1858,11 @@ gboolean button_internal(struct gui_data *inst, guint32 timestamp,
 
     if (!raw_mouse_mode) {
         if (ebutton == 4 && type == GDK_BUTTON_PRESS) {
-            term_scroll(inst->term, 0, -5);
+            term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES);
             return TRUE;
         }
         if (ebutton == 5 && type == GDK_BUTTON_PRESS) {
-            term_scroll(inst->term, 0, +5);
+            term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES);
             return TRUE;
         }
     }
@@ -1847,8 +1922,15 @@ gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
 gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
-    guint button;
 
+#if GTK_CHECK_VERSION(3,4,0)
+    gdouble dx, dy;
+    if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) {
+        return scroll_internal(inst, dy, event->state, event->x, event->y);
+    } else
+        return FALSE;
+#else
+    guint button;
     if (event->direction == GDK_SCROLL_UP)
        button = 4;
     else if (event->direction == GDK_SCROLL_DOWN)
@@ -1858,6 +1940,7 @@ gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)
 
     return button_internal(inst, event->time, GDK_BUTTON_PRESS,
                           button, event->state, event->x, event->y);
+#endif
 }
 #endif
 
@@ -3008,11 +3091,8 @@ void do_beep(void *frontend, int mode)
 int char_width(Context ctx, int uc)
 {
     /*
-     * Under X, any fixed-width font really _is_ fixed-width.
-     * Double-width characters will be dealt with using a separate
-     * font. For the moment we can simply return 1.
-     * 
-     * FIXME: but is that also true of Pango?
+     * In this front end, double-width characters are handled using a
+     * separate font, so this can safely just return 1 always.
      */
     return 1;
 }
@@ -3021,30 +3101,28 @@ Context get_ctx(void *frontend)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
     struct draw_ctx *dctx;
-    GdkWindow *target;
 
     if (!gtk_widget_get_window(inst->area))
        return NULL;
 
-#ifndef NO_BACKING_PIXMAPS
-    target = inst->pixmap;
-#else
-    target = gtk_widget_get_window(inst->area);
-#endif
-
     dctx = snew(struct draw_ctx);
     dctx->inst = inst;
     dctx->uctx.type = inst->drawtype;
 #ifdef DRAW_TEXT_GDK
     if (dctx->uctx.type == DRAWTYPE_GDK) {
-        dctx->uctx.u.gdk.target = target;
+        /* If we're doing GDK-based drawing, then we also expect
+         * inst->pixmap to exist. */
+        dctx->uctx.u.gdk.target = inst->pixmap;
         dctx->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area));
     }
 #endif
 #ifdef DRAW_TEXT_CAIRO
     if (dctx->uctx.type == DRAWTYPE_CAIRO) {
         dctx->uctx.u.cairo.widget = GTK_WIDGET(inst->area);
-        dctx->uctx.u.cairo.cr = gdk_cairo_create(target);
+        /* If we're doing Cairo drawing, we expect inst->surface to
+         * exist, and we draw to that first, regardless of whether we
+         * subsequently copy the results to inst->pixmap. */
+        dctx->uctx.u.cairo.cr = cairo_create(inst->surface);
         cairo_setup_dctx(dctx);
     }
 #endif
@@ -3071,23 +3149,31 @@ void free_ctx(Context ctx)
 
 static void draw_update(struct draw_ctx *dctx, int x, int y, int w, int h)
 {
-#ifndef NO_BACKING_PIXMAPS
-#ifdef DRAW_TEXT_GDK
-    if (dctx->uctx.type == DRAWTYPE_GDK) {
-        gdk_draw_pixmap(gtk_widget_get_window(dctx->inst->area),
-                        dctx->uctx.u.gdk.gc, dctx->inst->pixmap,
-                        x, y, x, y, w, h);
-    }
-#endif
-#ifdef DRAW_TEXT_CAIRO /* FIXME: and not GTK3 where a cairo_t is all we have */
+#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS
     if (dctx->uctx.type == DRAWTYPE_CAIRO) {
-        GdkGC *gc = gdk_gc_new(gtk_widget_get_window(dctx->inst->area));
-        gdk_draw_pixmap(gtk_widget_get_window(dctx->inst->area),
-                        gc, dctx->inst->pixmap, x, y, x, y, w, h);
-        gdk_gc_unref(gc);
+        /*
+         * If inst->surface and inst->pixmap both exist, then we've
+         * just drawn new content to the former which we must copy to
+         * the latter.
+         */
+        cairo_t *cr = gdk_cairo_create(dctx->inst->pixmap);
+        cairo_set_source_surface(cr, dctx->inst->surface, 0, 0);
+        cairo_rectangle(cr, x, y, w, h);
+        cairo_fill(cr);
+        cairo_destroy(cr);
     }
 #endif
-#endif
+
+    /*
+     * Now we just queue a window redraw, which will cause
+     * inst->surface or inst->pixmap (whichever is appropriate for our
+     * compile mode) to be copied to the real window when we receive
+     * the resulting "expose" or "draw" event.
+     *
+     * Amazingly, this one API call is actually valid in all versions
+     * of GTK :-)
+     */
+    gtk_widget_queue_draw_area(dctx->inst->area, x, y, w, h);
 }
 
 static void draw_set_colour(struct draw_ctx *dctx, int col)
@@ -3285,10 +3371,11 @@ static void draw_stretch_after(struct draw_ctx *dctx, int x, int y,
 static void draw_backing_rect(struct gui_data *inst)
 {
     struct draw_ctx *dctx = get_ctx(inst);
+    int w = inst->width * inst->font_width + 2*inst->window_border;
+    int h = inst->height * inst->font_height + 2*inst->window_border;
     draw_set_colour(dctx, 258);
-    draw_rectangle(dctx, 1, 0, 0,
-                   inst->width * inst->font_width + 2*inst->window_border,
-                   inst->height * inst->font_height + 2*inst->window_border);
+    draw_rectangle(dctx, 1, 0, 0, w, h);
+    draw_update(dctx, 0, 0, w, h);
     free_ctx(dctx);
 }
 
@@ -3303,7 +3390,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
     struct gui_data *inst = dctx->inst;
-    int ncombining, combining;
+    int ncombining;
     int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold;
     int monochrome =
         gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1;
@@ -3400,11 +3487,20 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
                    rlen*widefactor*inst->font_width, inst->font_height);
 
     draw_set_colour(dctx, nfg);
-    for (combining = 0; combining < ncombining; combining++) {
+    if (ncombining > 1) {
+        assert(len == 1);
+        unifont_draw_combining(&dctx->uctx, inst->fonts[fontid],
+                               x*inst->font_width+inst->window_border,
+                               (y*inst->font_height+inst->window_border+
+                                inst->fonts[0]->ascent),
+                               text, ncombining, widefactor > 1,
+                               bold, inst->font_width);
+    } else {
         unifont_draw_text(&dctx->uctx, inst->fonts[fontid],
                           x*inst->font_width+inst->window_border,
-                          y*inst->font_height+inst->window_border+inst->fonts[0]->ascent,
-                          text + combining, len, widefactor > 1,
+                          (y*inst->font_height+inst->window_border+
+                           inst->fonts[0]->ascent),
+                          text, len, widefactor > 1,
                           bold, inst->font_width);
     }
 
@@ -3653,7 +3749,7 @@ static void help(FILE *fp) {
 "  -ut, +ut                  Do(default) or do not update utmp\n"
 "  -ls, +ls                  Do(default) or do not make shell a login shell\n"
 "  -sb, +sb                  Do(default) or do not display a scrollbar\n"
-"  -log PATH                 Log all output to a file\n"
+"  -log PATH, -sessionlog PATH  Log all output to a file\n"
 "  -nethack                  Map numeric keypad to hjklyubn direction keys\n"
 "  -xrm RESOURCE-STRING      Set an X resource\n"
 "  -e COMMAND [ARGS...]      Execute command (consumes all remaining args)\n"
@@ -4726,6 +4822,9 @@ int pt_main(int argc, char **argv)
     inst->quit_fn_scheduled = FALSE;
     inst->idle_fn_scheduled = FALSE;
     inst->drawtype = DRAWTYPE_DEFAULT;
+#if GTK_CHECK_VERSION(3,4,0)
+    inst->cumulative_scroll = 0.0;
+#endif
 
     /* defer any child exit handling until we're ready to deal with
      * it */
@@ -4808,6 +4907,12 @@ int pt_main(int argc, char **argv)
 
     init_clipboard(inst);
 
+    set_geom_hints(inst);
+
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_window_set_default_geometry(GTK_WINDOW(inst->window),
+                                    inst->width, inst->height);
+#else
     {
         int w = inst->font_width * inst->width + 2*inst->window_border;
         int h = inst->font_height * inst->height + 2*inst->window_border;
@@ -4817,6 +4922,8 @@ int pt_main(int argc, char **argv)
         gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h);
 #endif
     }
+#endif
+
     inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0));
     inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
     inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
@@ -4833,8 +4940,6 @@ int pt_main(int argc, char **argv)
 
     gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));
 
-    set_geom_hints(inst);
-
     gtk_widget_show(inst->area);
     if (conf_get_int(inst->conf, CONF_scrollbar))
        gtk_widget_show(inst->sbar);
@@ -4899,7 +5004,11 @@ int pt_main(int argc, char **argv)
     gtk_widget_add_events(GTK_WIDGET(inst->area),
                          GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
-                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK);
+                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK
+#if GTK_CHECK_VERSION(3,4,0)
+                          | GDK_SMOOTH_SCROLL_MASK
+#endif
+        );
 
     {
        extern const char *const *const main_icon[];