Don't initialise GTK in --screenshot mode.

I had this idea today and immediately wondered why I'd never had it
before!

To generate the puzzle screenshots used on the website and as program
icons, we run the GTK front end with the --screenshot option, which
sets up GTK, insists on connecting to an X server (or other display),
draws the state of a puzzle on a Cairo surface, writes that surface
out to a .png file, and exits.

But there's no reason we actually need the GTK setup during that
process, especially because the surface we do the drawing on is our
_own_ surface, not even one provided to us by GTK. We could just set
up a Cairo surface by itself, draw on it, and save it to a file.
Calling gtk_init is not only pointless, but actively inconvenient,
because it means the build script depends on having an X server
available for the sole purpose of making gtk_init not complain.

So now I've simplified things, by adding a 'headless' flag in
new_window and the frontend structure, which suppresses all uses of
actual GTK, leaving only the Cairo surface setup and enough supporting
stuff (like colours) to generate the puzzle image. One awkward build
dependency removed.

This means that --screenshot no longer works in GTK 2, which I don't
care about, because it only needs to run on _one_ platform.
This commit is contained in:
Simon Tatham
2018-11-23 23:44:17 +00:00
parent db3b531e2c
commit d9e03f50da
2 changed files with 42 additions and 11 deletions

View File

@ -34,7 +34,7 @@ ifneq "$(NOICONS)" yes then
in puzzles do make -j$(nproc) in puzzles do make -j$(nproc)
# Now build the screenshots and icons. # Now build the screenshots and icons.
in puzzles/icons do xvfb-run -s "-screen 0 1024x768x24" make web winicons gtkicons -j$(nproc) in puzzles/icons do make web winicons gtkicons -j$(nproc)
# Destroy the local binaries and autoconf detritus, mostly to avoid # Destroy the local binaries and autoconf detritus, mostly to avoid
# wasting network bandwidth by transferring them to the delegate # wasting network bandwidth by transferring them to the delegate

49
gtk.c
View File

@ -139,6 +139,8 @@ struct font {
* particularly good reason not to. * particularly good reason not to.
*/ */
struct frontend { struct frontend {
bool headless; /* true if we're running without GTK, for --screenshot */
GtkWidget *window; GtkWidget *window;
GtkAccelGroup *dummy_accelgroup; GtkAccelGroup *dummy_accelgroup;
GtkWidget *area; GtkWidget *area;
@ -270,6 +272,9 @@ void gtk_status_bar(void *handle, const char *text)
{ {
frontend *fe = (frontend *)handle; frontend *fe = (frontend *)handle;
if (fe->headless)
return;
assert(fe->statusbar); assert(fe->statusbar);
gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx); gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx);
@ -297,7 +302,7 @@ static void teardown_drawing(frontend *fe)
fe->cr = NULL; fe->cr = NULL;
#ifndef USE_CAIRO_WITHOUT_PIXMAP #ifndef USE_CAIRO_WITHOUT_PIXMAP
{ if (!fe->headless) {
cairo_t *cr = gdk_cairo_create(fe->pixmap); cairo_t *cr = gdk_cairo_create(fe->pixmap);
cairo_set_source_surface(cr, fe->image, 0, 0); cairo_set_source_surface(cr, fe->image, 0, 0);
cairo_rectangle(cr, cairo_rectangle(cr,
@ -507,6 +512,11 @@ static void wipe_and_maybe_destroy_cairo(frontend *fe, cairo_t *cr,
static void setup_backing_store(frontend *fe) static void setup_backing_store(frontend *fe)
{ {
#ifndef USE_CAIRO_WITHOUT_PIXMAP #ifndef USE_CAIRO_WITHOUT_PIXMAP
if (fe->headless) {
fprintf(stderr, "headless mode does not work with GDK pixmaps\n");
exit(1);
}
fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area), fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area),
fe->pw, fe->ph, -1); fe->pw, fe->ph, -1);
#endif #endif
@ -518,7 +528,7 @@ static void setup_backing_store(frontend *fe)
wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true); wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true);
#endif #endif
#if GTK_CHECK_VERSION(3,22,0) #if GTK_CHECK_VERSION(3,22,0)
{ if (!fe->headless) {
GdkWindow *gdkwin; GdkWindow *gdkwin;
cairo_region_t *region; cairo_region_t *region;
GdkDrawingContext *drawctx; GdkDrawingContext *drawctx;
@ -780,6 +790,11 @@ static void setup_backing_store(frontend *fe)
{ {
GdkGC *gc; GdkGC *gc;
if (fe->headless) {
fprintf(stderr, "headless mode does not work with GDK drawing\n");
exit(1);
}
fe->pixmap = gdk_pixmap_new(fe->area->window, fe->pw, fe->ph, -1); fe->pixmap = gdk_pixmap_new(fe->area->window, fe->pw, fe->ph, -1);
gc = gdk_gc_new(fe->area->window); gc = gdk_gc_new(fe->area->window);
@ -1118,7 +1133,7 @@ void gtk_end_draw(void *handle)
teardown_drawing(fe); teardown_drawing(fe);
if (fe->bbox_l < fe->bbox_r && fe->bbox_u < fe->bbox_d) { if (fe->bbox_l < fe->bbox_r && fe->bbox_u < fe->bbox_d && !fe->headless) {
#ifdef USE_CAIRO_WITHOUT_PIXMAP #ifdef USE_CAIRO_WITHOUT_PIXMAP
gtk_widget_queue_draw_area(fe->area, gtk_widget_queue_draw_area(fe->area,
fe->bbox_l - 1 + fe->ox, fe->bbox_l - 1 + fe->ox,
@ -2470,7 +2485,8 @@ static void populate_gtk_preset_menu(frontend *fe, struct preset_menu *menu,
enum { ARG_EITHER, ARG_SAVE, ARG_ID }; /* for argtype */ enum { ARG_EITHER, ARG_SAVE, ARG_ID }; /* for argtype */
static frontend *new_window(char *arg, int argtype, char **error) static frontend *new_window(
char *arg, int argtype, char **error, bool headless)
{ {
frontend *fe; frontend *fe;
GtkBox *vbox, *hbox; GtkBox *vbox, *hbox;
@ -2483,8 +2499,15 @@ static frontend *new_window(char *arg, int argtype, char **error)
struct preset_menu *preset_menu; struct preset_menu *preset_menu;
fe = snew(frontend); fe = snew(frontend);
#if GTK_CHECK_VERSION(3,20,0) memset(fe, 0, sizeof(frontend));
fe->css_provider = NULL;
#if !GTK_CHECK_VERSION(3,0,0)
if (headless) {
fprintf(stderr, "headless mode not supported below GTK 3\n");
exit(1);
}
#else
fe->headless = headless;
#endif #endif
fe->timer_active = false; fe->timer_active = false;
@ -2552,6 +2575,14 @@ static frontend *new_window(char *arg, int argtype, char **error)
midend_new_game(fe->me); midend_new_game(fe->me);
} }
snaffle_colours(fe);
if (headless) {
get_size(fe, &fe->pw, &fe->ph);
setup_backing_store(fe);
return fe;
}
#if !GTK_CHECK_VERSION(3,0,0) #if !GTK_CHECK_VERSION(3,0,0)
{ {
/* /*
@ -2759,8 +2790,6 @@ static frontend *new_window(char *arg, int argtype, char **error)
changed_preset(fe); changed_preset(fe);
snaffle_colours(fe);
if (midend_wants_statusbar(fe->me)) { if (midend_wants_statusbar(fe->me)) {
GtkWidget *viewport; GtkWidget *viewport;
GtkRequisition req; GtkRequisition req;
@ -3319,10 +3348,12 @@ int main(int argc, char **argv)
return 0; return 0;
} else { } else {
frontend *fe; frontend *fe;
bool headless = screenshot_file != NULL;
if (!headless)
gtk_init(&argc, &argv); gtk_init(&argc, &argv);
fe = new_window(arg, argtype, &error); fe = new_window(arg, argtype, &error, headless);
if (!fe) { if (!fe) {
fprintf(stderr, "%s: %s\n", pname, error); fprintf(stderr, "%s: %s\n", pname, error);