shithub: puzzles

Download patch

ref: 78bc9ea7f79f379634f822d5f95242900f5716b9
parent: 9aa7b7cdfb2bcd200f45941a58d6ae698882a2d4
author: Franklin Wei <franklin@rockbox.org>
date: Mon Jul 6 18:06:30 EDT 2020

Add method for frontends to query the backend's cursor location.

The Rockbox frontend allows games to be displayed in a "zoomed-in"
state targets with small displays. Currently we use a modal interface
-- a "viewing" mode in which the cursor keys are used to pan around
the rendered bitmap; and an "interaction" mode that actually sends
keys to the game.

This commit adds a midend_get_cursor_location() function to allow the
frontend to retrieve the backend's cursor location or other "region of
interest" -- such as the player location in Cube or Inertia.

With this information, the Rockbox frontend can now intelligently
follow the cursor around in the zoomed-in state, eliminating the need
for a modal interface.

--- a/blackbox.c
+++ b/blackbox.c
@@ -1098,6 +1098,20 @@
     return NULL;
 }
 
+
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = TODRAW(ui->cur_x);
+        *y = TODRAW(ui->cur_y);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 /* ----------------------------------------------------------------------
  * Drawing routines.
  */
@@ -1542,6 +1556,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/bridges.c
+++ b/bridges.c
@@ -2146,6 +2146,20 @@
     bool started, dragging;
 };
 
+
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 /*
  * The contents of ds->grid are complicated, because of the circular
  * islands which overlap their own grid square into neighbouring
@@ -3267,6 +3281,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/cube.c
+++ b/cube.c
@@ -1535,6 +1535,27 @@
     sfree(ds);
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    struct bbox bb;
+
+    bb.l = 2.0F * (params->d1 + params->d2);
+    bb.r = -2.0F * (params->d1 + params->d2);
+    bb.u = 2.0F * (params->d1 + params->d2);
+    bb.d = -2.0F * (params->d1 + params->d2);
+
+    find_bbox_callback(&bb, state->grid->squares + state->current);
+
+    *x = ((int)(bb.l * GRID_SCALE) + ds->ox);
+    *y = ((int)(bb.u * GRID_SCALE) + ds->oy);
+    *w = (bb.r - bb.l) * GRID_SCALE;
+    *h = (bb.d - bb.u) * GRID_SCALE;
+}
+
 static void game_redraw(drawing *dr, game_drawstate *ds,
                         const game_state *oldstate, const game_state *state,
                         int dir, const game_ui *ui,
@@ -1762,6 +1783,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/devel.but
+++ b/devel.but
@@ -1289,6 +1289,51 @@
 to achieve this, its \cw{flash_length()} function has to store a
 flag in the \c{game_ui} to indicate which flash type is required.)
 
+\S{backend-get-cursor-location} \cw{get_cursor_location()}
+
+\c void (*get_cursor_location)(const game_ui *ui,
+\c                             const game_drawstate *ds,
+\c                             const game_state *state,
+\c                             const game_params *params,
+\c                             int *x, int *y,
+\c                             int *w, int *h);
+
+This function queries the backend for the rectangular region
+containing the cursor (in games that have one), or other region of
+interest.
+
+This function is called by only
+\cw{midend_get_cursor_location()}(\k{midend-get-cursor-location}). Its
+purpose is to allow front ends to query the location of the backend's
+cursor. With knowledge of this location, a front end can, for example,
+ensure that the region of interest remains visible if the puzzle is
+too big to fit on the screen at once.
+
+On returning, \cw{*x}, \cw{*y} should be set to the X and Y
+coordinates of the upper-left corner of the rectangular region of
+interest, and \cw{*w} and \cw{*h} should be the width and height of
+that region, respectively. In the event that a cursor is not visible
+on screen, this function should return and leave the return parameters
+untouched \dash the midend will notice this. The backend need not
+bother checking that \cw{x}, \cw{y}, \cw{w} and \cw{h} are
+non-\cw{NULL} \dash the midend guarantees that they will not be.
+
+Defining what constitutes a \q{region of interest} is left up to the
+backend. If a game provides a conventional cursor \dash such as Mines,
+Solo, or any of the other grid-based games \dash the most logical
+choice is of course the location of the cursor itself. However, in
+other cases such as Cube or Inertia, there is no \q{cursor} in the
+conventional sense \dash the player instead controls an object moving
+around the screen. In these cases, it makes sense to define the region
+of interest as the bounding box of the player object or another
+sensible region \dash such as the grid square the player is sitting on
+in Cube.
+
+If a backend does not provide a cursor mechanism at all, the backend
+is free to provide an empty implementation of this function, or a
+\cw{NULL} pointer in the \cw{game} structure \dash the midend will
+notice either of these cases and behave appropriately.
+
 \S{backend-status} \cw{status()}
 
 \c int (*status)(const game_state *state);
@@ -3306,6 +3351,34 @@
 \cw{activate_timer()} to be called from within a call to this
 function.  Some back ends require that \cw{midend_size()}
 (\k{midend-size}) is called before \cw{midend_solve()}.
+
+\H{midend-get-cursor-location} \cw{midend_get_cursor_location()}
+
+\c bool midend_get_cursor_location(midend *me,
+\c                                 int *x, int *y,
+\c                                 int *w, int *h);
+
+This function requests the location of the back end's on-screen cursor
+or other region of interest.
+
+What exactly this region contains is up to the backend, but in general
+the region will be an area that the player is controlling with the
+cursor keys \dash such as the player location in Cube and Inertia, or
+the cursor in any of the conventional grid-based games. With knowledge
+of this location, a front end can, for example, ensure that the region
+of interest remains visible even if the entire puzzle is too big to
+fit on the screen.
+
+On success, this function returns \cw{true}, and the locations pointed
+to by \cw{x}, \cw{y}, \cw{w} and \cw{h} are updated to describe the
+cursor region, which has an upper-left corner located at \cw{(*x,*y)}
+and a size of \cw{*w} pixels wide by \cw{*h} pixels tall. The caller
+may pass \cw{NULL} for any number of these pointers, which will be
+ignored.
+
+On failure, this function returns \cw{false}. Failure can occur if
+there is currently no active cursor region, or if the back end lacks
+cursor support.
 
 \H{midend-status} \cw{midend_status()}
 
--- a/dominosa.c
+++ b/dominosa.c
@@ -3328,6 +3328,20 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible)
+    {
+        *x = BORDER + ((2 * ui->cur_x + 1) * TILESIZE) / 4;
+        *y = BORDER + ((2 * ui->cur_y + 1) * TILESIZE) / 4;
+        *w = *h = TILESIZE / 2 + 2;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -3422,6 +3436,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/fifteen.c
+++ b/fifteen.c
@@ -1061,6 +1061,17 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    *x = COORD(X(state, state->gap_pos));
+    *y = COORD(Y(state, state->gap_pos));
+    *w = *h = TILE_SIZE;
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1115,6 +1126,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/filling.c
+++ b/filling.c
@@ -2060,6 +2060,20 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible)
+    {
+	*x = BORDER + ui->cur_x * TILE_SIZE;
+	*y = BORDER + ui->cur_y * TILE_SIZE;
+	*w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2165,6 +2179,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,				   /* wants_statusbar */
--- a/flip.c
+++ b/flip.c
@@ -1290,6 +1290,20 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cdraw)
+    {
+        *x = COORD(ui->cx);
+        *y = COORD(ui->cy);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1344,6 +1358,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/flood.c
+++ b/flood.c
@@ -1277,6 +1277,20 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cursor_visible)
+    {
+        *x = COORD(ui->cx);
+        *y = COORD(ui->cy);
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     if (state->complete && state->moves <= state->movelimit) {
@@ -1359,6 +1373,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/galaxies.c
+++ b/galaxies.c
@@ -3469,6 +3469,37 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        space *sp = &SPACE(state, ui->cur_x, ui->cur_y);
+
+        if(sp->flags & F_DOT) {
+            *x = SCOORD(ui->cur_x) - DOT_SIZE;
+            *y = SCOORD(ui->cur_y) - DOT_SIZE;
+            *w = *h = 2 * DOT_SIZE + 1;
+        } else if(sp->type != s_tile) {
+            int dx = (ui->cur_x % 2) ? CURSOR_SIZE : CURSOR_SIZE/3;
+            int dy = (ui->cur_y % 2) ? CURSOR_SIZE : CURSOR_SIZE/3;
+            int x1 = SCOORD(ui->cur_x)-dx, y1 = SCOORD(ui->cur_y)-dy;
+            int xs = dx*2+1, ys = dy*2+1;
+
+            *x = x1;
+            *y = y1;
+            *w = xs;
+            *h = ys;
+        } else {
+            *x = SCOORD(ui->cur_x) - CURSOR_SIZE;
+            *y = SCOORD(ui->cur_y) - CURSOR_SIZE;
+            *w = *h = 2 * CURSOR_SIZE + 1;
+        }
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -3695,6 +3726,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
 #ifdef EDITOR
     false, false, NULL, NULL,
--- a/guess.c
+++ b/guess.c
@@ -1448,6 +1448,20 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->display_cur && !state->solved) {
+        *x = GUESS_X(state->next_go, ui->peg_cur) - CGAP;
+        *y = GUESS_Y(state->next_go, ui->peg_cur) - CGAP;
+
+        *w = *h = 2 * (PEGRAD + CGAP) + 1;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     /*
@@ -1508,6 +1522,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/inertia.c
+++ b/inertia.c
@@ -2181,6 +2181,17 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    *x = ds->pbgx;
+    *y = ds->pbgy;
+    *w = *h = TILESIZE;
+}
+
 static int game_status(const game_state *state)
 {
     /*
@@ -2240,6 +2251,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/keen.c
+++ b/keen.c
@@ -2198,6 +2198,20 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->hshow) {
+        *x = BORDER + ui->hx * TILESIZE + 1 + GRIDEXTRA;
+        *y = BORDER + ui->hy * TILESIZE + 1 + GRIDEXTRA;
+
+        *w = *h = TILESIZE-1-2*GRIDEXTRA;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2480,6 +2494,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/lightup.c
+++ b/lightup.c
@@ -2217,6 +2217,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2325,6 +2338,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/loopy.c
+++ b/loopy.c
@@ -3537,6 +3537,14 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+}
+
 static int game_status(const game_state *state)
 {
     return state->solved ? +1 : 0;
@@ -3675,6 +3683,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false /* wants_statusbar */,
--- a/magnets.c
+++ b/magnets.c
@@ -2291,6 +2291,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2432,6 +2445,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/map.c
+++ b/map.c
@@ -3061,6 +3061,19 @@
 	return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -3260,6 +3273,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, true, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/midend.c
+++ b/midend.c
@@ -1464,6 +1464,35 @@
     me->game_id_change_notify_ctx = ctx;
 }
 
+bool midend_get_cursor_location(midend *me,
+                                int *x_out, int *y_out,
+                                int *w_out, int *h_out)
+{
+    int x, y, w, h;
+    x = y = -1;
+    w = h = 1;
+
+    if(me->ourgame->get_cursor_location)
+        me->ourgame->get_cursor_location(me->ui,
+                                         me->drawstate,
+                                         me->states[me->statepos-1].state,
+                                         me->params,
+                                         &x, &y, &w, &h);
+
+    if(x == -1 && y == -1)
+        return false;
+
+    if(x_out)
+        *x_out = x;
+    if(y_out)
+        *y_out = y;
+    if(w_out)
+        *w_out = w;
+    if(h_out)
+        *h_out = h;
+    return true;
+}
+
 void midend_supersede_game_desc(midend *me, const char *desc,
                                 const char *privdesc)
 {
--- a/mines.c
+++ b/mines.c
@@ -3150,6 +3150,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     /*
@@ -3211,6 +3224,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/net.c
+++ b/net.c
@@ -3087,6 +3087,20 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = WINDOW_OFFSET + TILE_SIZE * ui->cur_x;
+        *y = WINDOW_OFFSET + TILE_SIZE * ui->cur_y;
+
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -3268,6 +3282,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/netslide.c
+++ b/netslide.c
@@ -1825,6 +1825,20 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = BORDER + WINDOW_OFFSET + TILE_SIZE * ui->cur_x;
+        *y = BORDER + WINDOW_OFFSET + TILE_SIZE * ui->cur_y;
+
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1879,6 +1893,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/nullgame.c
+++ b/nullgame.c
@@ -242,6 +242,14 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+}
+
 static int game_status(const game_state *state)
 {
     return 0;
@@ -296,6 +304,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/palisade.c
+++ b/palisade.c
@@ -1274,6 +1274,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->show) {
+        *x = MARGIN + TILESIZE * ui->x;
+        *y = MARGIN + TILESIZE * ui->y;
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1387,6 +1400,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     true,                                     /* wants_statusbar */
--- a/pattern.c
+++ b/pattern.c
@@ -1916,6 +1916,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = TOCOORD(ds->w, ui->cur_x);
+        *y = TOCOORD(ds->h, ui->cur_y);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2027,6 +2040,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/pearl.c
+++ b/pearl.c
@@ -2542,6 +2542,19 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cursor_active) {
+        *x = COORD(ui->curx);
+        *y = COORD(ui->cury);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2635,6 +2648,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/pegs.c
+++ b/pegs.c
@@ -1280,6 +1280,19 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     /*
@@ -1338,6 +1351,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/puzzles.h
+++ b/puzzles.h
@@ -347,6 +347,8 @@
                           bool (*read)(void *ctx, void *buf, int len),
                           void *rctx);
 void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx);
+bool midend_get_cursor_location(midend *me, int *x, int *y, int *w, int *h);
+
 /* Printing functions supplied by the mid-end */
 const char *midend_print_puzzle(midend *me, document *doc, bool with_soln);
 int midend_tilesize(midend *me);
@@ -680,6 +682,11 @@
                          const game_state *newstate, int dir, game_ui *ui);
     float (*flash_length)(const game_state *oldstate,
                           const game_state *newstate, int dir, game_ui *ui);
+    void (*get_cursor_location)(const game_ui *ui,
+                                const game_drawstate *ds,
+                                const game_state *state,
+                                const game_params *params,
+                                int *x, int *y, int *w, int *h);
     int (*status)(const game_state *state);
     bool can_print, can_print_in_colour;
     void (*print_size)(const game_params *params, float *x, float *y);
--- a/range.c
+++ b/range.c
@@ -1572,6 +1572,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cursor_show) {
+        *x = BORDER + TILESIZE * ui->c;
+        *y = BORDER + TILESIZE * ui->r;
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->was_solved ? +1 : 0;
@@ -1823,6 +1836,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false, /* wants_statusbar */
--- a/rect.c
+++ b/rect.c
@@ -2880,6 +2880,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2991,6 +3004,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/samegame.c
+++ b/samegame.c
@@ -1615,6 +1615,19 @@
 	return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->displaysel) {
+        *x = COORD(ui->xsel);
+        *y = COORD(ui->ysel);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     /*
@@ -1673,6 +1686,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/signpost.c
+++ b/signpost.c
@@ -2187,6 +2187,19 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cshow) {
+        *x = COORD(ui->cx);
+        *y = COORD(ui->cy);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2274,6 +2287,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/singles.c
+++ b/singles.c
@@ -1758,6 +1758,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cshow) {
+        *x = COORD(ui->cx);
+        *y = COORD(ui->cy);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1850,6 +1863,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/sixteen.c
+++ b/sixteen.c
@@ -1146,6 +1146,19 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1200,6 +1213,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/slant.c
+++ b/slant.c
@@ -2064,6 +2064,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2181,6 +2194,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/solo.c
+++ b/solo.c
@@ -5297,6 +5297,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->hshow) {
+        *x = BORDER + ui->hx * TILE_SIZE + 1 + GRIDEXTRA;
+        *y = BORDER + ui->hy * TILE_SIZE + 1 + GRIDEXTRA;
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -5622,6 +5635,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/tents.c
+++ b/tents.c
@@ -2554,6 +2554,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cdisp) {
+        *x = COORD(ui->cx);
+        *y = COORD(ui->cy);
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2630,6 +2643,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/towers.c
+++ b/towers.c
@@ -1935,6 +1935,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->hshow) {
+        *x = COORD(ui->hx);
+        *y = COORD(ui->hy);
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2060,6 +2073,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/tracks.c
+++ b/tracks.c
@@ -2854,6 +2854,37 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cursor_active) {
+        int off = HALFSZ / 4;
+        int cx = COORD(ui->curx / 2) + off;
+        int cy = COORD(ui->cury / 2) + off;
+        int cw, ch;
+        cw = ch = TILE_SIZE - (2*off) + 1;
+
+        if(ui->curx % 2 == 0) {
+            /* left border */
+            cx -= off;
+            cw = 2 * off + 1;
+        }
+        if(ui->cury % 2 == 0) {
+            /* upper border */
+            cy -= off;
+            ch = 2 * off + 1;
+        }
+
+        *x = cx;
+        *y = cy;
+        *w = cw;
+        *h = ch;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2948,6 +2979,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/twiddle.c
+++ b/twiddle.c
@@ -1090,6 +1090,19 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cur_visible) {
+        *x = COORD(ui->cur_x);
+        *y = COORD(ui->cur_y);
+        *w = *h = state->n * TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1316,6 +1329,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/undead.c
+++ b/undead.c
@@ -2727,6 +2727,19 @@
             !newstate->cheated) ? FLASH_TIME : 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->hshow) {
+        *x = BORDER + (ui->hx) * TILESIZE;
+        *y = BORDER + (ui->hy + 1) * TILESIZE;
+        *w = *h = TILESIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->solved;
@@ -2781,6 +2794,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     false,                 /* wants_statusbar */
--- a/unequal.c
+++ b/unequal.c
@@ -2041,6 +2041,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->hshow) {
+        *x = COORD(ui->hx);
+        *y = COORD(ui->hy);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2135,6 +2148,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/unfinished/group.c
+++ b/unfinished/group.c
@@ -2196,6 +2196,14 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2320,6 +2328,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/unfinished/separate.c
+++ b/unfinished/separate.c
@@ -799,6 +799,14 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+}
+
 static int game_status(const game_state *state)
 {
     return 0;
@@ -853,6 +861,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/unfinished/slide.c
+++ b/unfinished/slide.c
@@ -2297,6 +2297,14 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -2351,6 +2359,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     true,			       /* wants_statusbar */
--- a/unfinished/sokoban.c
+++ b/unfinished/sokoban.c
@@ -1415,6 +1415,14 @@
         return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1469,6 +1477,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     false,			       /* wants_statusbar */
--- a/unruly.c
+++ b/unruly.c
@@ -1860,6 +1860,19 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+    if(ui->cursor) {
+        *x = COORD(ui->cx);
+        *y = COORD(ui->cy);
+        *w = *h = TILE_SIZE;
+    }
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1948,6 +1961,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     true, false, game_print_size, game_print,
     false,                      /* wants_statusbar */
--- a/untangle.c
+++ b/untangle.c
@@ -1428,6 +1428,14 @@
     return 0.0F;
 }
 
+static void game_get_cursor_location(const game_ui *ui,
+                                     const game_drawstate *ds,
+                                     const game_state *state,
+                                     const game_params *params,
+                                     int *x, int *y, int *w, int *h)
+{
+}
+
 static int game_status(const game_state *state)
 {
     return state->completed ? +1 : 0;
@@ -1482,6 +1490,7 @@
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_get_cursor_location,
     game_status,
     false, false, game_print_size, game_print,
     false,			       /* wants_statusbar */