Hello community,

here is the log from the commit of package headmore for openSUSE:Factory 
checked in at 2016-11-03 11:14:10
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/headmore (Old)
 and      /work/SRC/openSUSE:Factory/.headmore.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "headmore"

Changes:
--------
--- /work/SRC/openSUSE:Factory/headmore/headmore.changes        2016-10-31 
09:54:53.000000000 +0100
+++ /work/SRC/openSUSE:Factory/.headmore.new/headmore.changes   2016-11-03 
11:14:12.000000000 +0100
@@ -1,0 +2,11 @@
+Tue Nov  1 14:19:32 UTC 2016 - h...@suse.com
+
+- Upgrade to release 1.1.1 with accumulated feature enhancements:
+  * Do not use the log file  (.headmore.log) under home directory
+    anymore. Connection log is now directly printed in terminal.
+  * Use F10 key instead of escape key to quit the viewer.
+  * Draw VNC mouse cursor locally when zoomed out far.
+  * Improve handling of rapid input of Alt key combinations.
+  * Improve handling of viewer controls at lower FPS.
+
+-------------------------------------------------------------------

Old:
----
  headmore-1.0.tar.gz

New:
----
  headmore-1.1.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ headmore.spec ++++++
--- /var/tmp/diff_new_pack.gE8dyp/_old  2016-11-03 11:14:15.000000000 +0100
+++ /var/tmp/diff_new_pack.gE8dyp/_new  2016-11-03 11:14:15.000000000 +0100
@@ -1,6 +1,7 @@
 #
 # spec file for package headmore
 #
+# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
 # Copyright (c) 2016 Howard Guo <guohou...@gmail.com>
 #
 # All modifications and additions to the file contributed by third parties
@@ -15,17 +16,22 @@
 # Please submit bugfixes or comments via http://bugs.opensuse.org/
 #
 
+
 Name:           headmore
-Version:        1.0
+Version:        1.1.1
 Release:        0
-License:        GPL-3.0
 Summary:        VNC client for character terminals
-Url:            https://github.com/HouzuoGuo/headmore
+License:        GPL-3.0
 Group:          Productivity/Networking/Other
+Url:            https://github.com/HouzuoGuo/headmore
 Source:         %{name}-%{version}.tar.gz
+BuildRequires:  libgcrypt-devel
+BuildRequires:  libjpeg8-devel
+BuildRequires:  libopenssl-devel
+BuildRequires:  libpng16-compat-devel
 BuildRequires:  pkg-config
-BuildRequires:  pkgconfig(caca) pkgconfig(libvncclient)
-BuildRequires:  libgcrypt-devel libjpeg8-devel libopenssl-devel 
libpng16-compat-devel
+BuildRequires:  pkgconfig(caca)
+BuildRequires:  pkgconfig(libvncclient)
 
 %description
 headmore is a client for Virtual Network Computing (VNC),
@@ -38,14 +44,12 @@
 %setup -q
 
 %build
-gzip %{name}.1
+gzip headmore.1
 make
 
 %install
-mkdir -p %{buildroot}/%{_bindir}/
-install -m 0755 %{name} %{buildroot}/%{_bindir}/%{name}
-mkdir -p %{buildroot}/%{_mandir}/man1/
-install -m 0644 %{name}.1.gz %{buildroot}/%{_mandir}/man1/%{name}.1.gz
+install -D -m 0755 %{name} %{buildroot}/%{_bindir}/%{name}
+install -D -m 0644 %{name}.1.gz %{buildroot}/%{_mandir}/man1/%{name}.1.gz
 
 %files
 %defattr(-,root,root)

++++++ headmore-1.0.tar.gz -> headmore-1.1.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/headmore-1.0/geo.c new/headmore-1.1.1/geo.c
--- old/headmore-1.0/geo.c      1970-01-01 01:00:00.000000000 +0100
+++ new/headmore-1.1.1/geo.c    2016-11-02 09:26:31.000000000 +0100
@@ -0,0 +1,150 @@
+#include <string.h>
+#include "geo.h"
+
+struct geo_facts geo_facts_of(struct vnc *vnc, caca_display_t * disp,
+                             caca_canvas_t * view)
+{
+       struct geo_facts ret;
+       ret.px_width = caca_get_display_width(disp);
+       ret.px_height = caca_get_display_height(disp);
+       ret.ch_width = caca_get_canvas_width(view);
+       ret.ch_height = caca_get_canvas_height(view);
+       ret.vnc_width = vnc->conn->width;
+       ret.vnc_height = vnc->conn->height;
+       return ret;
+}
+
+void geo_init(struct geo *g, struct geo_facts facts)
+{
+       memset(g, 0, sizeof(struct geo));
+       /* Fill the zoom table */
+       int i;
+       g->zoom_lvls[0] = 1.0;
+       for (i = 0; i < GEO_ZOOM_MAX_LVL; i++) {
+               g->zoom_lvls[i + 1] = g->zoom_lvls[i] * GEO_ZOOM_STEP;
+       }
+       /*
+        * Mouse speed is the inverse of zoom.
+        * At maximum zoom, mouse moves slowest at speed of 1px/move.
+        * Each zoom step lower speeds up mouse movement by 2px/move.
+        */
+       for (i = 0; i < GEO_ZOOM_MAX_LVL + 1; i++) {
+               g->mouse_speed[i] = 1 + (GEO_ZOOM_MAX_LVL - i) * 2;
+       }
+
+       /* Begin by placing image right at the centre */
+       g->view_x = 0.5;
+       g->view_y = 0.5;
+       geo_zoom(g, facts, 0);
+
+       /* Place mouse pointer at centre of screen and depress all buttons */
+       g->mouse_x = facts.vnc_width / 2;
+       g->mouse_y = facts.vnc_height / 2;
+}
+
+void geo_zoom(struct geo *g, struct geo_facts facts, int offset)
+{
+       g->zoom += offset;
+       if (g->zoom < 0) {
+               g->zoom = 0;
+       } else if (g->zoom > GEO_ZOOM_MAX_LVL) {
+               g->zoom = GEO_ZOOM_MAX_LVL;
+       }
+
+       g->zoom_x =
+           (g->zoom <
+            0) ? 1.0 / g->zoom_lvls[-g->zoom] : g->zoom_lvls[g->zoom];
+       g->zoom_y =
+           g->zoom_x * facts.ch_width / facts.ch_height *
+           facts.vnc_height / facts.vnc_width * facts.ch_height /
+           facts.ch_width * facts.px_width / facts.px_height;
+
+       if (g->zoom_y > g->zoom_x) {
+               float tmp = g->zoom_x;
+               g->zoom_x = tmp * tmp / g->zoom_y;
+               g->zoom_y = tmp;
+       }
+}
+
+void geo_pan(struct geo *g, int pan_x, int pan_y)
+{
+       if (pan_x != 0) {
+               if (g->zoom_x > 1.0) {
+                       g->view_x += pan_x * (GEO_PAN_STEP / g->zoom_x);
+               }
+               if (g->view_x < 0.0) {
+                       g->view_x = 0.0;
+               } else if (g->view_x > 1.0) {
+                       g->view_x = 1.0;
+               }
+       }
+       if (pan_y != 0) {
+               if (g->zoom_y > 1.0) {
+                       g->view_y += pan_y * (GEO_PAN_STEP / g->zoom_y);
+               }
+               if (g->view_y < 0.0) {
+                       g->view_y = 0.0;
+               } else if (g->view_y > 1.0) {
+                       g->view_y = 1.0;
+               }
+       }
+}
+
+void geo_move_mouse(struct geo *g, struct geo_facts facts, int step_x,
+                   int step_y)
+{
+       int speed = g->mouse_speed[g->zoom];
+       g->mouse_x += step_x * speed;
+       if (g->mouse_x < 0) {
+               g->mouse_x = 0;
+       } else if (g->mouse_x >= facts.vnc_width) {
+               g->mouse_x = facts.vnc_width - 1;
+       }
+       g->mouse_y += step_y * speed;
+       if (g->mouse_y < 0) {
+               g->mouse_y = 0;
+       } else if (g->mouse_y >= facts.vnc_height) {
+               g->mouse_y = facts.vnc_height - 1;
+       }
+}
+
+void geo_zoom_to_cursor(struct geo *g, struct geo_facts facts)
+{
+       /* Zoom in to approximately 67% of maximum zoom level */
+       geo_zoom(g, facts, GEO_ZOOM_CURSOR_LVL - g->zoom);
+       /* Calculate position of mouse cursor relative to the entire canvas */
+       g->view_x = (float)g->mouse_x / (float)facts.vnc_width;
+       g->view_y = (float)g->mouse_y / (float)facts.vnc_height;
+}
+
+struct geo_dither_params geo_get_dither_params(struct geo *g,
+                                              struct geo_facts facts)
+{
+       struct geo_dither_params ret;
+       ret.facts = facts;
+       float delta_x = (g->zoom_x > 1.0) ? g->view_x : 0.5;
+       float delta_y = (g->zoom_y > 1.0) ? g->view_y : 0.5;
+       ret.x = facts.ch_width * (1.0 - g->zoom_x) * delta_x;
+       ret.y = facts.ch_height * (1.0 - g->zoom_y) * delta_y;
+       ret.width = facts.ch_width * g->zoom_x + 1;
+       ret.height = facts.ch_height * g->zoom_y + 1;
+       return ret;
+}
+
+int geo_dither_ch_px_x(struct geo_dither_params *params, int px_x)
+{
+       float dx = (float)px_x / params->facts.vnc_width;
+       return dx * params->width + params->x;
+}
+
+int geo_dither_ch_px_y(struct geo_dither_params *params, int px_y)
+{
+       float dy = (float)px_y / params->facts.vnc_height;
+       return dy * params->height + params->y;
+}
+
+int geo_dither_numch_x(struct geo_dither_params *params, int num_pixels_x)
+{
+       return geo_dither_ch_px_x(params,
+                                 num_pixels_x) - geo_dither_ch_px_x(params, 0);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/headmore-1.0/geo.h new/headmore-1.1.1/geo.h
--- old/headmore-1.0/geo.h      1970-01-01 01:00:00.000000000 +0100
+++ new/headmore-1.1.1/geo.h    2016-11-02 09:26:31.000000000 +0100
@@ -0,0 +1,60 @@
+#ifndef GEO_H
+#define GEO_H
+
+#include <caca.h>
+#include "vnc.h"
+
+#define GEO_PAN_STEP 0.20
+#define GEO_ZOOM_STEP 1.20f
+#define GEO_ZOOM_MAX_LVL 15
+#define GEO_ZOOM_CURSOR_LVL 11 /* zoom level for zooming into mouse cursor */
+
+/* Geometry facts of terminal emulator and VNC connection. */
+struct geo_facts {
+       int px_width, px_height;
+       int ch_width, ch_height;
+       int vnc_width, vnc_height;
+};
+
+/* Return geometry facts of the VNC connection and caca terminal. */
+struct geo_facts geo_facts_of(struct vnc *vnc, caca_display_t * disp,
+                             caca_canvas_t * canvas);
+
+/* Keep track of geometry of remote frame-buffer and canvas. */
+struct geo {
+       float view_x, zoom_x, view_y, zoom_y;
+       int zoom;
+       float zoom_lvls[GEO_ZOOM_MAX_LVL + 1];
+
+       int mouse_speed[GEO_ZOOM_MAX_LVL + 1];
+       int mouse_x, mouse_y;
+};
+
+/* Initialise geometry structure. */
+void geo_init(struct geo *g, struct geo_facts facts);
+/* Calculate geometry after zooming in (+) or out (-). */
+void geo_zoom(struct geo *g, struct geo_facts facts, int offset);
+/* Pan the canvas several steps up (-y), down (+y), left (-x), or right (+x). 
*/
+void geo_pan(struct geo *g, int pan_x, int pan_y);
+/* Move mouse pointer several steps up (-y) down (+y), left (-x) or right 
(+x).*/
+void geo_move_mouse(struct geo *g, struct geo_facts facts, int step_x,
+                   int step_y);
+/* Move view to the location of mouse cursor and zoom in there. */
+void geo_zoom_to_cursor(struct geo *g, struct geo_facts facts);
+
+/* Calculated parameters for dithering algorithm. */
+struct geo_dither_params {
+       struct geo_facts facts;
+       int x, y, width, height;
+};
+/* Calculate and return input parameters for dithering algorithm. */
+struct geo_dither_params geo_get_dither_params(struct geo *g,
+                                              struct geo_facts facts);
+/* Return the the character location of the VNC pixel on X axis. */
+int geo_dither_ch_px_x(struct geo_dither_params *params, int px_x);
+/* Return the the character location of the VNC pixel on Y axis. */
+int geo_dither_ch_px_y(struct geo_dither_params *params, int px_y);
+/* Return (approx.) number of characters it would take to draw those pixels on 
X axis. */
+int geo_dither_numch_x(struct geo_dither_params *params, int num_pixels_x);
+
+#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/headmore-1.0/headmore.1 new/headmore-1.1.1/headmore.1
--- old/headmore-1.0/headmore.1 2016-10-18 09:22:01.000000000 +0200
+++ new/headmore-1.1.1/headmore.1       2016-11-02 09:26:31.000000000 +0100
@@ -19,10 +19,6 @@
 For QWERTY keyboard users, the left hand side controls are:
 
 .TP
-.B Escape
-Disconnect from VNC server and leave the viewer immediately.
-.
-.TP
 .B Back-tick (`)
 Switch keyboard input between viewer/mouse control and VNC desktop. When you 
have just connected, your keyboard input is directed at viewer/mouse control.
 .
@@ -43,6 +39,10 @@
 And the right hand side controls are:
 
 .TP
+.B F10
+Disconnect from VNC server and leave the viewer immediately.
+.
+.TP
 .B I, J, K, L
 Move mouse cursor up, left, down, or right. Cursor speed is determined by 
current zoom, it move slower when zoomed in.
 .
@@ -85,14 +85,20 @@
 
 .SH INPUT QUIRKS
 
-While nearly all keyboard input will be successfully sent to VNC desktop, 
there are however several quirks to bear in mind:
+While nearly all keyboard input will be successfully sent to VNC desktop, bear 
in mind several quirks caused by limitations of character terminal, universal 
to all terminal emulators:
 
 .IP \[bu]
-If you have a computer mouse and your terminal emulator somehow forwards mouse 
clicks to headmore, headmore may do random things or quit unexpectedly.
+If you have a computer mouse and your terminal forwards mouse input headmore, 
headmore may react with random key input or quit unexpectedly.
+.IP \[bu]
+Meta key combinations such as Meta+D cannot be directly typed into VNC, you 
have to toggle hold Meta key and then type the modified key.
+.IP \[bu]
+Other modifier keys (Control and Alt) only work with lower case letters a-z. 
For other combinations such as Alt+F4 or Ctrl+Shift+A, toggle hold the modifier 
key and then press the modified key.
+.IP \[bu]
+Rapid input of Alt key combinations may occasionally result in the modified 
being typed without Alt modifier. Type Alt key combinations slowly to avoid 
encountering this issue.
 .IP \[bu]
-All modifier keys only work with lower case letters a-z. For other 
combinations such as Alt+F4, toggle hold Alt key and then type F4 in VNC.
+Typing escape key and another key in short succession may result in an Alt 
combination being typed unexpectedly. Wait a short while after the escape key 
to avoid encountering this issue.
 .IP \[bu]
-While most control characters such as Ctrl+C and Ctrl+D work reliably in VNC 
input, a few of them (such as Ctrl+Z) do not work in certain terminal 
emulators, you have to toggle hold control key and then press the other 
character individually.
+While most control characters such as Ctrl+C and Ctrl+D work reliably in VNC 
input, a few of them (such as Ctrl+Z) do not work in certain terminal 
emulators, in such cases you have to toggle hold Ctrl and then press the letter 
key.
 
 .SH DESIGN RATIONALE
 .B headmore
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/headmore-1.0/viewer.c new/headmore-1.1.1/viewer.c
--- old/headmore-1.0/viewer.c   2016-10-18 09:22:01.000000000 +0200
+++ new/headmore-1.1.1/viewer.c 2016-11-02 09:26:31.000000000 +0100
@@ -18,12 +18,12 @@
 /* Messages to display in a static help menu. */
 static char const *viewer_help[] = {
        "============ LEFT HAND ============",
-       "Esc   Disconnect and quit          ",
        "`     Toggle input to viewer/VNC   ",
        "~     Click back-tick in VNC       ",
        "wasd  Pan viewer                   ",
        "q/e   Zoom out/in                  ",
        "============ RIGHT HAND ===========",
+       "F10  Quit                          ",
        "ijkl Move mouse cursor             ",
        "u/o  Click L/R mouse button        ",
        "789  Toggle hold L/M/R mouse button",
@@ -38,104 +38,91 @@
 };
 
 /* Return the RFB client the viewer is connected to. */
-static struct _rfbClient *rfb(struct viewer *viewer)
+static struct _rfbClient *rfb(struct viewer *v)
 {
-       return viewer->vnc->conn;
+       return v->vnc->conn;
 }
 
-bool viewer_init(struct viewer * viewer, struct vnc * vnc)
+bool viewer_init(struct viewer * v, struct vnc * vnc)
 {
-       memset(viewer, 0, sizeof(struct viewer));
-       /* Fill the zoom table */
-       int i;
-       viewer->zoom_lvls[0] = 1.0;
-       for (i = 0; i < VIEWER_ZOOM_MAX_LVL; i++) {
-               viewer->zoom_lvls[i + 1] =
-                   viewer->zoom_lvls[i] * VIEWER_ZOOM_STEP;
-       }
-       /*
-        * Mouse speed is the inverse of zoom.
-        * At maximum zoom, mouse moves slowest at speed of 1px/move.
-        * Each zoom step lower speeds up mouse movement by 2px/move.
-        */
-       for (i = 0; i < VIEWER_ZOOM_MAX_LVL + 1; i++) {
-               viewer->mouse_speed[i] = 1 + (VIEWER_ZOOM_MAX_LVL - i) * 2;
-       }
-       viewer->view = caca_create_canvas(0, 0);
-       if (!viewer->view) {
+       memset(v, 0, sizeof(struct viewer));
+       /* Initialise visuals */
+       v->view = caca_create_canvas(0, 0);
+       if (!v->view) {
                fprintf(stderr, "Failed to create caca canvas\n");
                return false;
        }
-       viewer->disp = caca_create_display_with_driver(viewer->view, "ncurses");
-       if (!viewer->disp) {
+       v->disp = caca_create_display_with_driver(v->view, "ncurses");
+       if (!v->disp) {
                fprintf(stderr, "Failed to create caca display\n");
                return false;
        }
-       viewer->vnc = vnc;
-       caca_set_display_title(viewer->disp, rfb(viewer)->desktopName);
+       v->vnc = vnc;
+       caca_set_display_title(v->disp, rfb(v)->desktopName);
+
+       /* Initialise parameters for geometry calculation */
+       geo_init(&v->geo, viewer_geo(v));
 
-       /* Begin by placing image right at the centre */
-       viewer->view_x = 0.5;
-       viewer->view_y = 0.5;
-       viewer_zoom(viewer, 0);
-
-       /* Place mouse pointer at centre of screen and depress all buttons */
-       viewer->mouse_x = rfb(viewer)->width / 2;
-       viewer->mouse_y = rfb(viewer)->height / 2;
-       viewer_vnc_send_pointer(viewer);
+       /* Tell VNC to place mouse pointer to default position */
+       viewer_vnc_send_pointer(v);
        return true;
 }
 
-void viewer_disp_status(struct viewer *viewer)
+struct geo_facts viewer_geo(struct viewer *v)
 {
-       caca_set_color_ansi(viewer->view, CACA_WHITE, CACA_BLUE);
+       return geo_facts_of(v->vnc, v->disp, v->view);
+}
+
+void viewer_disp_status(struct viewer *v)
+{
+       caca_set_color_ansi(v->view, CACA_WHITE, CACA_BLUE);
        char *conn_remark = "";
-       if (!viewer->vnc->connected) {
+       if (!v->vnc->connected) {
                conn_remark = "(Disconnected)";
        }
        char *who_has_input = "Input to viewer";
-       if (viewer->input2vnc) {
+       if (v->input2vnc) {
                who_has_input = "Input to VNC (~ to disengage)";
        }
        char held_controls[80] = { 0 };
        bool disp_held_controls = false;
-       if (viewer->mouse_left) {
+       if (v->mouse_left) {
                disp_held_controls = true;
                strcat(held_controls, " LMouse");
        }
-       if (viewer->mouse_middle) {
+       if (v->mouse_middle) {
                disp_held_controls = true;
                strcat(held_controls, " MMouse");
        }
-       if (viewer->mouse_right) {
+       if (v->mouse_right) {
                disp_held_controls = true;
                strcat(held_controls, " RMouse");
        }
-       if (viewer->hold_lctrl) {
+       if (v->hold_lctrl) {
                disp_held_controls = true;
                strcat(held_controls, " LCtrl");
        }
-       if (viewer->hold_lshift) {
+       if (v->hold_lshift) {
                disp_held_controls = true;
                strcat(held_controls, " LShift");
        }
-       if (viewer->hold_lalt) {
+       if (v->hold_lalt) {
                disp_held_controls = true;
                strcat(held_controls, " LAlt");
        }
-       if (viewer->hold_lsuper) {
+       if (v->hold_lsuper) {
                disp_held_controls = true;
                strcat(held_controls, " LSuper");
        }
-       if (viewer->hold_ralt) {
+       if (v->hold_ralt) {
                disp_held_controls = true;
                strcat(held_controls, " RAlt");
        }
-       if (viewer->hold_rshift) {
+       if (v->hold_rshift) {
                disp_held_controls = true;
                strcat(held_controls, " RShift");
        }
-       if (viewer->hold_rctrl) {
+       if (v->hold_rctrl) {
                disp_held_controls = true;
                strcat(held_controls, " RCtrl");
        }
@@ -144,225 +131,136 @@
                strcat(held_controls_msg, "| Holding down:");
                strcat(held_controls_msg, held_controls);
        }
-       caca_printf(viewer->view, 0, 0, "h:Help | %s:%d%s | %s %s",
-                   rfb(viewer)->serverHost, rfb(viewer)->serverPort,
-                   conn_remark, who_has_input, held_controls_msg);
+       caca_printf(v->view, 0, 0, "h:Help | %s:%d%s | %s %s",
+                   rfb(v)->serverHost, rfb(v)->serverPort, conn_remark,
+                   who_has_input, held_controls_msg);
 }
 
-void viewer_disp_help(struct viewer *viewer)
+void viewer_disp_help(struct viewer *v)
 {
-       caca_set_color_ansi(viewer->view, CACA_WHITE, CACA_BLUE);
+       caca_set_color_ansi(v->view, CACA_WHITE, CACA_BLUE);
        int i;
        for (i = 0; viewer_help[i] != NULL; i++) {
-               caca_put_str(viewer->view, 0, 1 + i, viewer_help[i]);
+               caca_put_str(v->view, 0, 1 + i, viewer_help[i]);
        }
 }
 
-void viewer_zoom(struct viewer *viewer, int offset)
+void viewer_redraw(struct viewer *v)
 {
-       viewer->zoom_lvl += offset;
-       if (viewer->zoom_lvl < 0) {
-               viewer->zoom_lvl = 0;
-       } else if (viewer->zoom_lvl > VIEWER_ZOOM_MAX_LVL) {
-               viewer->zoom_lvl = VIEWER_ZOOM_MAX_LVL;
-       }
-
-       int viewer_width = caca_get_canvas_width(viewer->view);
-       int viewer_height = caca_get_canvas_height(viewer->view);
-       viewer->zoom_x =
-           (viewer->zoom_lvl <
-            0) ? 1.0 /
-           viewer->zoom_lvls[-viewer->zoom_lvl] : viewer->
-           zoom_lvls[viewer->zoom_lvl];
-       viewer->zoom_y =
-           viewer->zoom_x * viewer_width / viewer_height *
-           rfb(viewer)->height / rfb(viewer)->width * viewer_height /
-           viewer_width * caca_get_display_width(viewer->disp) /
-           caca_get_display_height(viewer->disp);
-
-       if (viewer->zoom_y > viewer->zoom_x) {
-               float tmp = viewer->zoom_x;
-               viewer->zoom_x = tmp * tmp / viewer->zoom_y;
-               viewer->zoom_y = tmp;
-       }
-}
-
-void viewer_pan(struct viewer *viewer, int pan_x, int pan_y)
-{
-       if (pan_x != 0) {
-               if (viewer->zoom_x > 1.0) {
-                       viewer->view_x +=
-                           pan_x * (VIEWER_PAN_STEP / viewer->zoom_x);
-               }
-               if (viewer->view_x < 0.0) {
-                       viewer->view_x = 0.0;
-               } else if (viewer->view_x > 1.0) {
-                       viewer->view_x = 1.0;
-               }
-       }
-       if (pan_y != 0) {
-               if (viewer->zoom_y > 1.0) {
-                       viewer->view_y +=
-                           pan_y * (VIEWER_PAN_STEP / viewer->zoom_y);
-               }
-               if (viewer->view_y < 0.0) {
-                       viewer->view_y = 0.0;
-               } else if (viewer->view_y > 1.0) {
-                       viewer->view_y = 1.0;
-               }
-       }
-}
-
-void viewer_redraw(struct viewer *viewer)
-{
-       caca_set_color_ansi(viewer->view, CACA_WHITE, CACA_BLACK);
-       caca_clear_canvas(viewer->view);
+       caca_clear_canvas(v->view);
        /*
         * Run the latest frame-buffer content through Floyd–Steinberg 
algorithm -
         * it seems to offer higher quality over other algorithm choices.
         * The dither parameters are tailored for a connection on 32-bit RGB 
colours.
         */
-       if (viewer->fb_dither != NULL) {
-               caca_free_dither(viewer->fb_dither);
+       if (v->fb_dither != NULL) {
+               caca_free_dither(v->fb_dither);
+       }
+       struct geo_facts facts = viewer_geo(v);
+       v->fb_dither =
+           caca_create_dither(32, facts.vnc_width, facts.vnc_height,
+                              facts.vnc_width * 4, 0x000000ff, 0x0000ff00,
+                              0x00ff0000, 0);
+       caca_set_dither_algorithm(v->fb_dither, "fstein");
+       caca_set_dither_gamma(v->fb_dither, 1.0);
+       struct geo_dither_params params =
+           geo_get_dither_params(&v->geo, viewer_geo(v));
+       caca_dither_bitmap(v->view, params.x, params.y, params.width,
+                          params.height, v->fb_dither, rfb(v)->frameBuffer);
+       /*
+        * Mouse cursors are usually wider than 14 pixels. If it will not take
+        * more than 5 characters to draw the cusor, then consider it very
+        * difficult to spot on the VNC canvas, and draw an easy to spot block
+        * right there.
+        */
+       if (geo_dither_numch_x(&params, 12) < 5) {
+               caca_set_color_ansi(v->view, CACA_WHITE, CACA_RED);
+               int ch_x = geo_dither_ch_px_x(&params, v->geo.mouse_x);
+               int ch_y = geo_dither_ch_px_y(&params, v->geo.mouse_y);
+               caca_fill_box(v->view, ch_x - 1, ch_y - 1, 3, 3, '*');
        }
-       viewer->fb_dither =
-           caca_create_dither(32, rfb(viewer)->width,
-                              rfb(viewer)->height,
-                              rfb(viewer)->width * 4, 0x000000ff,
-                              0x0000ff00, 0x00ff0000, 0);
-       caca_set_dither_algorithm(viewer->fb_dither, "fstein");
-       caca_set_dither_gamma(viewer->fb_dither, 1.0);
-       int viewer_width = caca_get_canvas_width(viewer->view);
-       int viewer_height = caca_get_canvas_height(viewer->view);
-       float delta_x = (viewer->zoom_x > 1.0) ? viewer->view_x : 0.5;
-       float delta_y = (viewer->zoom_y > 1.0) ? viewer->view_y : 0.5;
-
-       /* Draw frame-buffer and other things on the canvas */
-       caca_dither_bitmap(viewer->view,
-                          viewer_width * (1.0 -
-                                          viewer->zoom_x) * delta_x,
-                          viewer_height * (1.0 -
-                                           viewer->zoom_y) * delta_y,
-                          viewer_width * viewer->zoom_x + 1,
-                          viewer_height * viewer->zoom_y + 1,
-                          viewer->fb_dither, rfb(viewer)->frameBuffer);
-       viewer_disp_status(viewer);
-       if (viewer->disp_help) {
-               viewer_disp_help(viewer);
+       viewer_disp_status(v);
+       if (v->disp_help) {
+               viewer_disp_help(v);
        }
-       caca_refresh_display(viewer->disp);
+       caca_refresh_display(v->disp);
 }
 
-void viewer_ev_loop(struct viewer *viewer)
+void viewer_ev_loop(struct viewer *v)
 {
+       int ev_accept =
+           CACA_EVENT_KEY_PRESS | CACA_EVENT_RESIZE | CACA_EVENT_QUIT;
        while (true) {
                /* Listen to the latest event */
                caca_event_t ev;
-               caca_get_event(viewer->disp,
-                              CACA_EVENT_KEY_PRESS | CACA_EVENT_RESIZE |
-                              CACA_EVENT_QUIT, &ev, 1000000 / VIEWER_FPS);
+               caca_get_event(v->disp, ev_accept, &ev,
+                              VIEWER_FRAME_INTVL_USEC);
                /* Certain types of events are caca calling quit */
                enum caca_event_type ev_type = caca_get_event_type(&ev);
                if (ev_type & CACA_EVENT_QUIT || ev_type & CACA_EVENT_NONE) {
                        return;
                }
                /* Handle previously banked escape key (VNC input), send it to 
VNC. */
-               if (viewer->last_vnc_esc != 0
-                   && get_time_usec() - viewer->last_vnc_esc >=
-                   1000000 / VIEWER_FPS) {
-                       viewer->last_vnc_esc = 0;
-                       viewer_vnc_click_key(viewer,
-                                            cacakey2vnc(CACA_KEY_ESCAPE));
-               }
-               /* Handle previously banked escape key (viewer control), stop 
the viewer. */
-               if (viewer->last_viewer_esc != 0
-                   && get_time_usec() - viewer->last_viewer_esc >=
-                   1000000 / VIEWER_FPS) {
-                       return;
+               if (v->last_vnc_esc != 0
+                   && get_time_usec() - v->last_vnc_esc >=
+                   VIEWER_FRAME_INTVL_USEC) {
+                       v->last_vnc_esc = 0;
+                       viewer_vnc_click_key(v, cacakey2vnc(CACA_KEY_ESCAPE));
                }
                /* Redraw at a constant frame rate when there is no key input */
                if (!(ev_type & CACA_EVENT_KEY_PRESS)) {
-                       viewer_redraw(viewer);
+                       viewer_redraw(v);
                        continue;
                }
                int ev_char = caca_get_event_key_ch(&ev);
                /* Input never gets directed at VNC if it is disconnected */
-               if (!viewer->vnc->connected) {
-                       viewer->input2vnc = false;
+               if (!v->vnc->connected) {
+                       v->input2vnc = false;
                }
                /* A key input is directed at either VNC or viewer controls */
-               if (viewer->input2vnc && ev_char != '`') {
-                       viewer_input_to_vnc(viewer, ev_char);
-               } else {
-                       viewer_handle_control(viewer, ev_char);
+               if (v->input2vnc && ev_char != '`') {
+                       viewer_input_to_vnc(v, ev_char);
+               } else if (!viewer_handle_control(v, ev_char)) {
+                       return;
                }
        }
 }
 
-void viewer_vnc_click_key(struct viewer *viewer, int vnc_key)
+void viewer_vnc_click_key(struct viewer *v, int vnc_key)
 {
-       SendKeyEvent(rfb(viewer), vnc_key, TRUE);
-       SendKeyEvent(rfb(viewer), vnc_key, FALSE);
+       SendKeyEvent(rfb(v), vnc_key, TRUE);
+       SendKeyEvent(rfb(v), vnc_key, FALSE);
 }
 
-void viewer_vnc_click_ctrl_key_combo(struct viewer *viewer, int vnc_key)
+void viewer_vnc_click_ctrl_key_combo(struct viewer *v, int vnc_key)
 {
-       SendKeyEvent(rfb(viewer), XK_Control_L, TRUE);
-       SendKeyEvent(rfb(viewer), vnc_key, TRUE);
-       SendKeyEvent(rfb(viewer), vnc_key, FALSE);
-       SendKeyEvent(rfb(viewer), XK_Control_L, FALSE);
+       SendKeyEvent(rfb(v), XK_Control_L, TRUE);
+       SendKeyEvent(rfb(v), vnc_key, TRUE);
+       SendKeyEvent(rfb(v), vnc_key, FALSE);
+       SendKeyEvent(rfb(v), XK_Control_L, FALSE);
 }
 
-void viewer_vnc_toggle_key(struct viewer *viewer, int vnc_key, bool key_down)
+void viewer_vnc_toggle_key(struct viewer *v, int vnc_key, bool key_down)
 {
-       SendKeyEvent(rfb(viewer), vnc_key, key_down ? TRUE : FALSE);
+       SendKeyEvent(rfb(v), vnc_key, key_down ? TRUE : FALSE);
 }
 
-void viewer_vnc_send_pointer(struct viewer *viewer)
+void viewer_vnc_send_pointer(struct viewer *v)
 {
        int mask = 0;
-       if (viewer->mouse_left) {
+       if (v->mouse_left) {
                mask |= rfbButton1Mask;
        }
-       if (viewer->mouse_middle) {
+       if (v->mouse_middle) {
                mask |= rfbButton2Mask;
        }
-       if (viewer->mouse_right) {
+       if (v->mouse_right) {
                mask |= rfbButton3Mask;
        }
-       SendPointerEvent(rfb(viewer), viewer->mouse_x, viewer->mouse_y, mask);
-}
-
-void viewer_move_mouse(struct viewer *viewer, int step_x, int step_y)
-{
-       int speed = viewer->mouse_speed[viewer->zoom_lvl];
-       viewer->mouse_x += step_x * speed;
-       viewer->mouse_y += step_y * speed;
-       if (viewer->mouse_x < 0) {
-               viewer->mouse_x = 0;
-       } else if (viewer->mouse_x > rfb(viewer)->width - 1) {
-               viewer->mouse_x = rfb(viewer)->width - 1;
-       }
-       if (viewer->mouse_y < 0) {
-               viewer->mouse_y = 0;
-       } else if (viewer->mouse_y > rfb(viewer)->height - 1) {
-               viewer->mouse_y = rfb(viewer)->height - 1;
-       }
-       viewer_vnc_send_pointer(viewer);
-}
-
-void viewer_zoom_to_cursor(struct viewer *viewer)
-{
-       /* Zoom in to approximately 67% of maximum zoom level */
-       viewer_zoom(viewer, VIEWER_ZOOM_MAX_LVL * 2 / 3 - viewer->zoom_lvl);
-       /* Calculate position of mouse cursor relative to the entire canvas */
-       viewer->view_x = (float)viewer->mouse_x / (float)rfb(viewer)->width;
-       viewer->view_y = (float)viewer->mouse_y / (float)rfb(viewer)->height;
-       /* Pan several steps to place the mouse cursor closer to view's centre 
*/
-       viewer_redraw(viewer);
+       SendPointerEvent(rfb(v), v->geo.mouse_x, v->geo.mouse_y, mask);
 }
 
-void viewer_input_to_vnc(struct viewer *viewer, int caca_key)
+void viewer_input_to_vnc(struct viewer *v, int caca_key)
 {
        /*
         * Escape key could mean two things, either it is a key press by 
itself, or it
@@ -372,7 +270,7 @@
         * dealt with later.
         */
        if (caca_key == CACA_KEY_ESCAPE) {
-               viewer->last_vnc_esc = get_time_usec();
+               v->last_vnc_esc = get_time_usec();
                return;
        }
        /*
@@ -404,24 +302,23 @@
                case 24:
                case 25:
                case 26:        /* (missing S) Ctrl+TUVWXYZ */
-                       viewer_vnc_click_ctrl_key_combo(viewer,
-                                                       caca_key - 1 + 'a');
+                       viewer_vnc_click_ctrl_key_combo(v, caca_key - 1 + 'a');
                        return;
                case 8: /* H and an extra backspace */
-                       viewer->void_backsp = true;
-                       viewer_vnc_click_ctrl_key_combo(viewer, 'h');
+                       v->void_backsp = true;
+                       viewer_vnc_click_ctrl_key_combo(v, 'h');
                        return;
                case 9: /* I and an extra tab */
-                       viewer->void_tab = true;
-                       viewer_vnc_click_ctrl_key_combo(viewer, 'i');
+                       v->void_tab = true;
+                       viewer_vnc_click_ctrl_key_combo(v, 'i');
                        return;
                case 13:        /* M and an extra enter */
-                       viewer->void_ret = true;
-                       viewer_vnc_click_ctrl_key_combo(viewer, 'm');
+                       v->void_ret = true;
+                       viewer_vnc_click_ctrl_key_combo(v, 'm');
                        return;
                case 19:        /* S and an extra pause */
-                       viewer->void_pause = true;
-                       viewer_vnc_click_ctrl_key_combo(viewer, 's');
+                       v->void_pause = true;
+                       viewer_vnc_click_ctrl_key_combo(v, 's');
                        return;
                }
        }
@@ -431,25 +328,25 @@
         */
        switch (caca_key) {
        case CACA_KEY_BACKSPACE:
-               if (viewer->void_backsp) {
-                       viewer->void_backsp = false;
+               if (v->void_backsp) {
+                       v->void_backsp = false;
                        return;
                }
                break;
        case CACA_KEY_TAB:
-               if (viewer->void_tab) {
-                       viewer->void_tab = false;
+               if (v->void_tab) {
+                       v->void_tab = false;
                        return;
                }
                break;
        case CACA_KEY_RETURN:
-               if (viewer->void_ret) {
-                       viewer->void_ret = false;
+               if (v->void_ret) {
+                       v->void_ret = false;
                        return;
                }
        case CACA_KEY_PAUSE:
-               if (viewer->void_pause) {
-                       viewer->void_pause = false;
+               if (v->void_pause) {
+                       v->void_pause = false;
                        return;
                }
        }
@@ -460,175 +357,196 @@
                return;
        }
        /*
-        * In case there was a banked escape key, the Alt combination key shall 
arrive
-        * in an instant within 2000 microseconds.
+        * In case there was a banked escape key, the Alt combination key shall
+        * arrive pretty soon, and most definitely before the next frame 
refresh.
+        * Occasionally it takes even longer to arrive but there is no way to
+        * work around it.
         */
-       if (get_time_usec() - viewer->last_vnc_esc < 2000) {
-               viewer_vnc_toggle_key(viewer, XK_Alt_L, true);
-               viewer_vnc_click_key(viewer, translated_ch);
-               viewer_vnc_toggle_key(viewer, XK_Alt_L, false);
-               viewer->last_vnc_esc = 0;
+       suseconds_t elapsed = get_time_usec() - v->last_vnc_esc;
+       if (elapsed < VIEWER_FRAME_INTVL_USEC
+           && elapsed < VIEWER_MAX_INPUT_INTVL_USEC) {
+               viewer_vnc_toggle_key(v, XK_Alt_L, true);
+               viewer_vnc_click_key(v, translated_ch);
+               viewer_vnc_toggle_key(v, XK_Alt_L, false);
+               v->last_vnc_esc = 0;
                return;
        }
        /* Finally the key code can be sent to VNC! */
-       viewer_vnc_click_key(viewer, translated_ch);
+       viewer_vnc_click_key(v, translated_ch);
 }
 
-void viewer_handle_control(struct viewer *viewer, int caca_key)
+bool viewer_handle_control(struct viewer * v, int caca_key)
 {
        /*
-        * Similar to the case with viewer_input_to_vnc, if user accidentally 
types an
-        * Alt key combination in the viewer, the first key event - an escape 
key will
-        * mislead viewer into quitting.
-        * Instead of quitting immediately, an escape key will be banked and 
the viewer
-        * awaits one more key event to arrive. If it indeed arrives and within 
2000
-        * microseconds, then the user must have mistakenly typed Alt key 
combination,
-        * in which case the viewer shall not quit.
-        * If the latter key event does not arrive, the escape key must have 
been an
-        * intention to quit the viewer and event loop will do that job.
+        * When the canvas is too busy panning/zooming, causing very low FPS,
+        * the terminal occasionally generates repeatitive key input seemingly
+        * out of nowhere, in very short successions. To work around it, this
+        * condition caps number of viewe controls to approximately 10 per 
second.
         */
-       if (get_time_usec() - viewer->last_viewer_esc < 2000) {
-               viewer->last_viewer_esc = 0;
+       suseconds_t elapsed = get_time_usec() - v->last_viewer_control;
+       if (v->last_viewer_control != 0
+           && elapsed < VIEWER_MAX_INPUT_INTVL_USEC) {
+               return true;
        }
+       /*
+        * In order to avoid redrawing too rapidly, only viewer zoom/pan
+        * actions redraw immediately.
+        * VNC input has to wait for main loop to redraw at an interval.
+        */
        switch (caca_key) {
        case 'h':
        case 'H':
-               viewer->disp_help = !viewer->disp_help;
-               break;
-       case CACA_KEY_ESCAPE:
-               /* Escape key is banked, its meaning will be determined soon 
later, */
-               viewer->last_viewer_esc = get_time_usec();
+               v->disp_help = !v->disp_help;
                break;
+       case CACA_KEY_F10:
+               return false;
                /* Left hand */
        case 'w':
        case 'W':
-               viewer_pan(viewer, 0, -1);
+               geo_pan(&v->geo, 0, -1);
+               viewer_redraw(v);
                break;
        case 'a':
        case 'A':
-               viewer_pan(viewer, -1, 0);
+               geo_pan(&v->geo, -1, 0);
+               viewer_redraw(v);
                break;
        case 's':
        case 'S':
-               viewer_pan(viewer, 0, 1);
+               geo_pan(&v->geo, 0, 1);
+               viewer_redraw(v);
                break;
        case 'd':
        case 'D':
-               viewer_pan(viewer, 1, 0);
+               geo_pan(&v->geo, 1, 0);
+               viewer_redraw(v);
                break;
        case 'q':
        case 'Q':
-               viewer_zoom(viewer, -1);
+               geo_zoom(&v->geo, viewer_geo(v), -1);
+               viewer_redraw(v);
                break;
        case 'e':
        case 'E':
-               viewer_zoom(viewer, 1);
+               geo_zoom(&v->geo, viewer_geo(v), 1);
+               viewer_redraw(v);
                break;
        case '`':
-               if (viewer->vnc->connected) {
-                       viewer->input2vnc = !viewer->input2vnc;
+               if (v->vnc->connected) {
+                       v->input2vnc = !v->input2vnc;
                }
                break;
        case '~':
-               viewer_vnc_click_key(viewer, 96);       /* click back-tick (96 
in ASCII) in VNC */
+               /* Click back-tick (96 in ASCII) in VNC */
+               viewer_vnc_click_key(v, 96);
                break;
                /* Right hand */
        case 'i':
        case 'I':
-               viewer_move_mouse(viewer, 0, -1);
+               geo_move_mouse(&v->geo, viewer_geo(v), 0, -1);
+               viewer_vnc_send_pointer(v);
                break;
        case 'j':
        case 'J':
-               viewer_move_mouse(viewer, -1, 0);
+               geo_move_mouse(&v->geo, viewer_geo(v), -1, 0);
+               viewer_vnc_send_pointer(v);
                break;
        case 'k':
        case 'K':
-               viewer_move_mouse(viewer, 0, 1);
+               geo_move_mouse(&v->geo, viewer_geo(v), 0, 1);
+               viewer_vnc_send_pointer(v);
                break;
        case 'l':
        case 'L':
-               viewer_move_mouse(viewer, 1, 0);
+               geo_move_mouse(&v->geo, viewer_geo(v), 1, 0);
+               viewer_vnc_send_pointer(v);
                break;
        case 'u':
        case 'U':
-               viewer->mouse_left = true;
-               viewer_vnc_send_pointer(viewer);
-               viewer->mouse_left = false;
-               viewer_vnc_send_pointer(viewer);
+               v->mouse_left = true;
+               viewer_vnc_send_pointer(v);
+               v->mouse_left = false;
+               viewer_vnc_send_pointer(v);
                break;
        case 'o':
        case 'O':
-               viewer->mouse_right = true;
-               viewer_vnc_send_pointer(viewer);
-               viewer->mouse_right = false;
-               viewer_vnc_send_pointer(viewer);
+               v->mouse_right = true;
+               viewer_vnc_send_pointer(v);
+               v->mouse_right = false;
+               viewer_vnc_send_pointer(v);
                break;
        case '7':
-               viewer->mouse_left = !viewer->mouse_left;
-               viewer_vnc_send_pointer(viewer);
+               v->mouse_left = !v->mouse_left;
+               viewer_vnc_send_pointer(v);
                break;
        case '8':
-               viewer->mouse_middle = !viewer->mouse_middle;
-               viewer_vnc_send_pointer(viewer);
+               v->mouse_middle = !v->mouse_middle;
+               viewer_vnc_send_pointer(v);
                break;
        case '9':
-               viewer->mouse_right = !viewer->mouse_right;
-               viewer_vnc_send_pointer(viewer);
+               v->mouse_right = !v->mouse_right;
+               viewer_vnc_send_pointer(v);
                break;
        case '0':
-               viewer->mouse_middle = true;
-               viewer_vnc_send_pointer(viewer);
-               viewer->mouse_middle = false;
-               viewer_vnc_send_pointer(viewer);
+               v->mouse_middle = true;
+               viewer_vnc_send_pointer(v);
+               v->mouse_middle = false;
+               viewer_vnc_send_pointer(v);
                break;
        case 'p':
        case 'P':
-               viewer_zoom_to_cursor(viewer);
+               geo_zoom_to_cursor(&v->geo, viewer_geo(v));
                break;
                /* Toggle keys */
        case 'z':
        case 'Z':
-               viewer->hold_lctrl = !viewer->hold_lctrl;
-               viewer_vnc_toggle_key(viewer, XK_Control_L, viewer->hold_lctrl);
+               v->hold_lctrl = !v->hold_lctrl;
+               viewer_vnc_toggle_key(v, XK_Control_L, v->hold_lctrl);
                break;
        case 'm':
        case 'M':
-               viewer->hold_rctrl = !viewer->hold_rctrl;
-               viewer_vnc_toggle_key(viewer, XK_Control_R, viewer->hold_rctrl);
+               v->hold_rctrl = !v->hold_rctrl;
+               viewer_vnc_toggle_key(v, XK_Control_R, v->hold_rctrl);
                break;
        case 'x':
        case 'X':
-               viewer->hold_lshift = !viewer->hold_lshift;
-               viewer_vnc_toggle_key(viewer, XK_Shift_L, viewer->hold_lshift);
+               v->hold_lshift = !v->hold_lshift;
+               viewer_vnc_toggle_key(v, XK_Shift_L, v->hold_lshift);
                break;
        case 'n':
        case 'N':
-               viewer->hold_rshift = !viewer->hold_rshift;
-               viewer_vnc_toggle_key(viewer, XK_Shift_R, viewer->hold_rshift);
+               v->hold_rshift = !v->hold_rshift;
+               viewer_vnc_toggle_key(v, XK_Shift_R, v->hold_rshift);
                break;
        case 'c':
        case 'C':
-               viewer->hold_lalt = !viewer->hold_lalt;
-               viewer_vnc_toggle_key(viewer, XK_Alt_L, viewer->hold_lalt);
+               v->hold_lalt = !v->hold_lalt;
+               viewer_vnc_toggle_key(v, XK_Alt_L, v->hold_lalt);
                break;
        case 'b':
        case 'B':
-               viewer->hold_ralt = !viewer->hold_ralt;
-               viewer_vnc_toggle_key(viewer, XK_Alt_R, viewer->hold_ralt);
+               v->hold_ralt = !v->hold_ralt;
+               viewer_vnc_toggle_key(v, XK_Alt_R, v->hold_ralt);
                break;
        case 'v':
        case 'V':
-               viewer->hold_lsuper = !viewer->hold_lsuper;
-               viewer_vnc_toggle_key(viewer, XK_Super_L, viewer->hold_lsuper);
+               v->hold_lsuper = !v->hold_lsuper;
+               viewer_vnc_toggle_key(v, XK_Super_L, v->hold_lsuper);
                break;
        }
+       v->last_viewer_control = get_time_usec();
+       return true;
 }
 
-void viewer_terminate(struct viewer *viewer)
+void viewer_terminate(struct viewer *v)
 {
-       caca_free_display(viewer->disp);
-       caca_free_canvas(viewer->view);
-       if (viewer->fb_dither != NULL) {
-               caca_free_dither(viewer->fb_dither);
+       if (v->fb_dither != NULL) {
+               caca_free_dither(v->fb_dither);
+       }
+       if (v->disp != NULL) {
+               caca_free_display(v->disp);
+       }
+       if (v->view != NULL) {
+               caca_free_canvas(v->view);
        }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/headmore-1.0/viewer.h new/headmore-1.1.1/viewer.h
--- old/headmore-1.0/viewer.h   2016-10-18 09:22:01.000000000 +0200
+++ new/headmore-1.1.1/viewer.h 2016-11-02 09:26:31.000000000 +0100
@@ -1,68 +1,64 @@
 #ifndef VIEWER_H
 #define VIEWER_H
 
+#include <caca.h>
 #include <stdbool.h>
 #include <sys/types.h>
-#include <caca.h>
+#include "geo.h"
 #include "vnc.h"
 
-#define VIEWER_PAN_STEP 0.20
-#define VIEWER_ZOOM_STEP 1.20f
-#define VIEWER_ZOOM_MAX_LVL 15
-#define VIEWER_FPS 10          /* too high FPS will make control sluggish, 
never go above 50. */
+/*
+ * The viewer will render frame-buffer content at roughly this many frames per 
second.
+ * It does not take rendering algorithm itself into account, hence the value 
is a rough estimate at best.
+ * The perceived FPS decreases as terminal gets larger.
+ * Do not raise the value too high or controls will become very sluggish.
+ */
+#define VIEWER_FPS 10
+#define VIEWER_FRAME_INTVL_USEC (1000000 / VIEWER_FPS)
+#define VIEWER_MAX_INPUT_INTVL_USEC (1000000 / VIEWER_FPS)
 
-/* Draw remote frame-buffer and handle key/mouse IO. */
+/* Render remote frame-buffer on terminal and handle key/mouse IO. */
 struct viewer {
        struct vnc *vnc;
-       caca_display_t *disp;
+       struct geo geo;
 
+       caca_display_t *disp;
        caca_canvas_t *view;
        struct caca_dither *fb_dither;
-       float view_x, zoom_x, view_y, zoom_y;
-       int zoom_lvl;
-       float zoom_lvls[VIEWER_ZOOM_MAX_LVL + 1];
 
-       int mouse_speed[VIEWER_ZOOM_MAX_LVL + 1];
-       int mouse_x, mouse_y;
-       bool mouse_left, mouse_middle, mouse_right;
-
-       suseconds_t last_vnc_esc, last_viewer_esc;
+       suseconds_t last_vnc_esc, last_viewer_control;
        bool void_backsp, void_tab, void_ret, void_pause, void_esc, void_del;
        bool disp_help, input2vnc;
        bool hold_lctrl, hold_lshift, hold_lalt, hold_lsuper, hold_ralt,
            hold_rshift, hold_rctrl;
+       bool mouse_left, mouse_middle, mouse_right;
 };
 
-/* Initialise canvas and its driver for the VNC connection. */
-bool viewer_init(struct viewer *viewer, struct vnc *vnc);
+/* Initialise viewer and its driver for the VNC connection. */
+bool viewer_init(struct viewer *v, struct vnc *vnc);
+/* Return geometry facts of the viewer. */
+struct geo_facts viewer_geo(struct viewer *v);
 /* Display a status row at 0,0. */
-void viewer_disp_status(struct viewer *viewer);
+void viewer_disp_status(struct viewer *v);
 /* Display a static help menu at 0,1. */
-void viewer_disp_help(struct viewer *viewer);
-/* Zoom in (+) or out (-) on the canvas. Remember to redraw! */
-void viewer_zoom(struct viewer *viewer, int offset);
-/* Pan the canvas several steps up (-y), down (+y), left (-x), or right (+x). 
Remember to redraw! */
-void viewer_pan(struct viewer *viewer, int pan_x, int pan_y);
+void viewer_disp_help(struct viewer *v);
 /* Redraw the content from the latest frame-buffer of VNC connection. */
-void viewer_redraw(struct viewer *viewer);
+void viewer_redraw(struct viewer *v);
 /* Handle keyboard input and canvas events. Block caller until quit key is 
pressed and handled. */
-void viewer_ev_loop(struct viewer *viewer);
+void viewer_ev_loop(struct viewer *v);
 /* Click (press and release) a keyboard key in VNC. */
-void viewer_vnc_click_key(struct viewer *viewer, int vnc_key);
+void viewer_vnc_click_key(struct viewer *v, int vnc_key);
 /* Hold control key and then click the specified key in VNC, then release 
control key. */
-void viewer_vnc_click_ctrl_key_combo(struct viewer *viewer, int vnc_key);
+void viewer_vnc_click_ctrl_key_combo(struct viewer *v, int vnc_key);
 /* Toggle (press or release) a keyboard key in VNC. */
-void viewer_vnc_toggle_key(struct viewer *viewer, int vnc_key, bool key_down);
+void viewer_vnc_toggle_key(struct viewer *v, int vnc_key, bool key_down);
 /* Send the latest pointer location and button mask to VNC. */
-void viewer_vnc_send_pointer(struct viewer *viewer);
-/* Move mouse pointer several steps up (-y) down (+y), left (-x) or right 
(+x).*/
-void viewer_move_mouse(struct viewer *viewer, int step_x, int step_y);
-/* Move view to the location of mouse cursor and zoom in there to a 
pre-defined level. */
-void viewer_zoom_to_cursor(struct viewer *viewer);
+void viewer_vnc_send_pointer(struct viewer *v);
 /* Translate caca key stroke to VNC key symbol and send it over VNC. */
-void viewer_input_to_vnc(struct viewer *viewer, int caca_key);
-/* Interpret and act on caca key stroke as a viewer control command. */
-void viewer_handle_control(struct viewer *viewer, int caca_key);
+void viewer_input_to_vnc(struct viewer *v, int caca_key);
+/* Interpret and act on caca key stroke as a viewer control command. Return 
false only if viewer should quit. */
+bool viewer_handle_control(struct viewer *v, int caca_key);
 /* Release all resources held by the viewer, but do not terminate the VNC 
connection. */
-void viewer_terminate(struct viewer *viewer);
+void viewer_terminate(struct viewer *v);
+
 #endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/headmore-1.0/vnc.c new/headmore-1.1.1/vnc.c
--- old/headmore-1.0/vnc.c      2016-10-18 09:22:01.000000000 +0200
+++ new/headmore-1.1.1/vnc.c    2016-11-02 09:26:31.000000000 +0100
@@ -1,8 +1,6 @@
-#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <time.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <rfb/keysym.h>
@@ -11,26 +9,6 @@
 #include <caca.h>
 #include "vnc.h"
 
-static FILE *vnc_dbg_log = NULL;       /* direct VNC library log to a tmp file 
*/
-
-/* Write a log line into log file. */
-static void vnc_log2file(const char *format, ...)
-{
-       if (!rfbEnableClientLogging || vnc_dbg_log == NULL) {
-               return;
-       }
-       time_t now;
-       time(&now);
-       char timestamp[100];
-       strftime(timestamp, 100 - 1, "%Y-%m-%d %X ", localtime(&now));
-       fprintf(vnc_dbg_log, "%s", timestamp);
-       va_list args;
-       va_start(args, format);
-       vfprintf(vnc_dbg_log, format, args);
-       va_end(args);
-       fflush(vnc_dbg_log);
-}
-
 /* Process RFB server messages, block caller. Return NULL. */
 static void *io_loop_fun(void *struct_vnc)
 {
@@ -50,60 +28,38 @@
        return NULL;
 }
 
-bool vnc_init(struct vnc * vnc, int argc, char **argv)
+bool vnc_init(struct vnc * v, int argc, char **argv)
 {
-       memset(vnc, 0, sizeof(struct vnc));
-       /* A debug log file is created under user's home directory */
-       char *home_dir_path = getenv("HOME");
-       if (home_dir_path != NULL) {
-               char *log_file_path =
-                   (char *)calloc(strlen(home_dir_path) + strlen(VNC_DBG_LOG) +
-                                  2, sizeof(char));
-               if (!log_file_path) {
-                       fprintf(stderr, "Out of memory");
-                       return false;
-               }
-               strcat(log_file_path, home_dir_path);
-               strcat(log_file_path, "/");
-               strcat(log_file_path, VNC_DBG_LOG);
-               printf("Log file is located at %s\n", log_file_path);
-               vnc_dbg_log = fopen(log_file_path, "a");
-               free(log_file_path);
-       }
-       /* libvncserver shares logging functions across all connections */
-       rfbClientLog = vnc_log2file;
-       rfbClientErr = vnc_log2file;
+       memset(v, 0, sizeof(struct vnc));
        /*
         * The connection asks server for 32-bit RGB colours.
         * Take note that VNC does not use alpha channel, hence the most 
significant 2 bytes are useless.
         * I think 256-colours should be more than sufficient for this VNC 
client, but somehow I could not
         * get a sufficiently good quality out of dithering process, perhaps I 
got the bit-masks wrong?
         */
-       vnc->conn = rfbGetClient(8, 3, 4);
-       vnc->conn->canHandleNewFBSize = FALSE;
-       printf("Establishing VNC connection...\n");
-       if (!rfbInitClient(vnc->conn, &argc, argv)) {
+       v->conn = rfbGetClient(8, 3, 4);
+       v->conn->canHandleNewFBSize = FALSE;
+       if (!rfbInitClient(v->conn, &argc, argv)) {
                return false;
        }
-       printf("Connection is successfully established!\n");
-       vnc->cont_io_loop = true;
-       vnc->connected = true;
-       if (pthread_create(&vnc->io_loop, NULL, io_loop_fun, (void *)vnc) != 0) 
{
+       v->cont_io_loop = true;
+       v->connected = true;
+       if (pthread_create(&v->io_loop, NULL, io_loop_fun, (void *)v) != 0) {
                fprintf(stderr, "Failed to create message loop thread\n");
                return false;
        }
        return true;
 }
 
-void vnc_destroy(struct vnc *vnc)
+void vnc_destroy(struct vnc *v)
 {
-       vnc->cont_io_loop = false;
-       vnc->connected = false;
-       if (pthread_join(vnc->io_loop, NULL) != 0) {
+       v->cont_io_loop = false;
+       v->connected = false;
+       if (pthread_join(v->io_loop, NULL) != 0) {
                fprintf(stderr, "Failed to join message loop thread\n");
        }
-       uint8_t *fb = vnc->conn->frameBuffer;
-       rfbClientCleanup(vnc->conn);
+       uint8_t *fb = v->conn->frameBuffer;
+       rfbClientCleanup(v->conn);
        free(fb);
        rfbClientLog("VNC connection has been terminated\n");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/headmore-1.0/vnc.h new/headmore-1.1.1/vnc.h
--- old/headmore-1.0/vnc.h      2016-10-18 09:22:01.000000000 +0200
+++ new/headmore-1.1.1/vnc.h    2016-11-02 09:26:31.000000000 +0100
@@ -4,9 +4,7 @@
 #include <stdbool.h>
 #include <rfb/rfbclient.h>
 
-#define VNC_POLL_TIMEOUT_USEC 100000   /* keep it under a second */
-#define VNC_DBG_LOG ".headmore.log"    /* name of log file under user's home 
directory */
-#define VNC_PASS_MAX 256       /* maximum length of input password */
+#define VNC_POLL_TIMEOUT_USEC 100000   /* A lower value enables faster 
termination of VNC IO loop */
 
 /* Connect to remote frame-buffer and handle control/image IO. */
 struct vnc {
@@ -16,9 +14,9 @@
 };
 
 /* Connect to server and immediately begin message loop in a separate thread. 
Return false only on failure. */
-bool vnc_init(struct vnc *vnc, int argc, char **argv);
+bool vnc_init(struct vnc *v, int argc, char **argv);
 /* Close VNC connection and free all resources, including the VNC client 
itself. */
-void vnc_destroy(struct vnc *vnc);
+void vnc_destroy(struct vnc *v);
 /* Translate a key code as read by libcaca to its corresponding VNC key code. 
Return -1 only if no translation. */
 int cacakey2vnc(int keych);
 


Reply via email to