From 29461fa910e86f9a1d6c10ecdca85200bcf217de Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 8 Oct 2018 16:33:04 +0200 Subject: [PATCH] demo: Add GTK+ test application --- demo/gtk-libfprint-test.c | 514 ++++++++++++++++++++++++++ demo/gtk-libfprint-test.gresource.xml | 6 + demo/gtk-libfprint-test.ui | 351 ++++++++++++++++++ demo/loop.c | 196 ++++++++++ demo/loop.h | 27 ++ demo/meson.build | 17 + meson.build | 12 + meson_options.txt | 4 + 8 files changed, 1127 insertions(+) create mode 100644 demo/gtk-libfprint-test.c create mode 100644 demo/gtk-libfprint-test.gresource.xml create mode 100644 demo/gtk-libfprint-test.ui create mode 100644 demo/loop.c create mode 100644 demo/loop.h create mode 100644 demo/meson.build diff --git a/demo/gtk-libfprint-test.c b/demo/gtk-libfprint-test.c new file mode 100644 index 0000000..b23e38d --- /dev/null +++ b/demo/gtk-libfprint-test.c @@ -0,0 +1,514 @@ +/* + * Example libfprint GTK+ image capture program + * Copyright (C) 2018 Bastien Nocera + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include +#include + +#include "loop.h" + +typedef GtkApplication LibfprintDemo; +typedef GtkApplicationClass LibfprintDemoClass; + +G_DEFINE_TYPE (LibfprintDemo, libfprint_demo, GTK_TYPE_APPLICATION) + +typedef enum { + IMAGE_DISPLAY_NONE = 0, + IMAGE_DISPLAY_MINUTIAE = 1 << 0, + IMAGE_DISPLAY_BINARY = 1 << 1 +} ImageDisplayFlags; + +typedef struct { + GtkApplicationWindow parent_instance; + + GtkWidget *header_bar; + GtkWidget *mode_stack; + GtkWidget *capture_button; + GtkWidget *capture_image; + GtkWidget *spinner; + GtkWidget *instructions; + + struct fp_dscv_dev *ddev; + struct fp_dev *dev; + + struct fp_img *img; + ImageDisplayFlags img_flags; +} LibfprintDemoWindow; + +typedef GtkApplicationWindowClass LibfprintDemoWindowClass; + +G_DEFINE_TYPE (LibfprintDemoWindow, libfprint_demo_window, GTK_TYPE_APPLICATION_WINDOW) + +typedef enum { + EMPTY_MODE, + NOIMAGING_MODE, + CAPTURE_MODE, + SPINNER_MODE, + ERROR_MODE +} LibfprintDemoMode; + +static void libfprint_demo_set_mode (LibfprintDemoWindow *win, + LibfprintDemoMode mode); + +static void +pixbuf_destroy (guchar *pixels, gpointer data) +{ + if (pixels == NULL) + return; + g_free (pixels); +} + +static unsigned char * +img_to_rgbdata (struct fp_img *img, + int width, + int height) +{ + int size = width * height; + unsigned char *imgdata = fp_img_get_data (img); + unsigned char *rgbdata = g_malloc (size * 3); + size_t i; + size_t rgb_offset = 0; + + for (i = 0; i < size; i++) { + unsigned char pixel = imgdata[i]; + + rgbdata[rgb_offset++] = pixel; + rgbdata[rgb_offset++] = pixel; + rgbdata[rgb_offset++] = pixel; + } + + return rgbdata; +} + +static void +plot_minutiae (unsigned char *rgbdata, + int width, + int height, + struct fp_minutia **minlist, + int nr_minutiae) +{ + int i; +#define write_pixel(num) do { \ + rgbdata[((num) * 3)] = 0xff; \ + rgbdata[((num) * 3) + 1] = 0; \ + rgbdata[((num) * 3) + 2] = 0; \ + } while(0) + + for (i = 0; i < nr_minutiae; i++) { + struct fp_minutia *min = minlist[i]; + int x, y; + size_t pixel_offset; + + fp_minutia_get_coords(min, &x, &y); + pixel_offset = (y * width) + x; + write_pixel(pixel_offset - 2); + write_pixel(pixel_offset - 1); + write_pixel(pixel_offset); + write_pixel(pixel_offset + 1); + write_pixel(pixel_offset + 2); + + write_pixel(pixel_offset - (width * 2)); + write_pixel(pixel_offset - (width * 1) - 1); + write_pixel(pixel_offset - (width * 1)); + write_pixel(pixel_offset - (width * 1) + 1); + write_pixel(pixel_offset + (width * 1) - 1); + write_pixel(pixel_offset + (width * 1)); + write_pixel(pixel_offset + (width * 1) + 1); + write_pixel(pixel_offset + (width * 2)); + } +} + +static GdkPixbuf * +img_to_pixbuf (struct fp_img *img, + ImageDisplayFlags flags) +{ + int width; + int height; + unsigned char *rgbdata; + + width = fp_img_get_width (img); + height = fp_img_get_height (img); + + if (flags & IMAGE_DISPLAY_BINARY) { + struct fp_img *binary; + binary = fp_img_binarize (img); + rgbdata = img_to_rgbdata (binary, width, height); + fp_img_free (binary); + } else { + rgbdata = img_to_rgbdata (img, width, height); + } + + if (flags & IMAGE_DISPLAY_MINUTIAE) { + struct fp_minutia **minlist; + int nr_minutiae; + + minlist = fp_img_get_minutiae (img, &nr_minutiae); + plot_minutiae (rgbdata, width, height, minlist, nr_minutiae); + } + + return gdk_pixbuf_new_from_data (rgbdata, GDK_COLORSPACE_RGB, + FALSE, 8, width, height, + width * 3, pixbuf_destroy, + NULL); +} + +static void +update_image (LibfprintDemoWindow *win) +{ + GdkPixbuf *pixbuf; + + if (win->img == NULL) { + gtk_image_clear (GTK_IMAGE (win->capture_image)); + return; + } + + g_debug ("Updating image, minutiae %s, binary mode %s", + win->img_flags & IMAGE_DISPLAY_MINUTIAE ? "shown" : "hidden", + win->img_flags & IMAGE_DISPLAY_BINARY ? "on" : "off"); + pixbuf = img_to_pixbuf (win->img, win->img_flags); + gtk_image_set_from_pixbuf (GTK_IMAGE (win->capture_image), pixbuf); + g_object_unref (pixbuf); +} + +static void +libfprint_demo_set_spinner_label (LibfprintDemoWindow *win, + const char *message) +{ + char *label; + + label = g_strdup_printf ("%s", message); + gtk_label_set_markup (GTK_LABEL (win->instructions), label); + g_free (label); +} + +static void +libfprint_demo_set_capture_label (LibfprintDemoWindow *win) +{ + struct fp_driver *drv; + enum fp_scan_type scan_type; + const char *message; + + drv = fp_dscv_dev_get_driver (win->ddev); + scan_type = fp_driver_get_scan_type(drv); + + switch (scan_type) { + case FP_SCAN_TYPE_PRESS: + message = "Place your finger on the fingerprint reader"; + break; + case FP_SCAN_TYPE_SWIPE: + message = "Swipe your finger across the fingerprint reader"; + break; + default: + g_assert_not_reached (); + } + + libfprint_demo_set_spinner_label (win, message); +} + +static void +dev_capture_start_cb (struct fp_dev *dev, + int result, + struct fp_img *img, + void *user_data) +{ + LibfprintDemoWindow *win = user_data; + + if (result < 0) { + libfprint_demo_set_mode (win, ERROR_MODE); + return; + } + + fp_async_capture_stop (dev, NULL, NULL); + + win->img = img; + update_image (win); + + libfprint_demo_set_mode (win, CAPTURE_MODE); +} + +static void +dev_open_cb (struct fp_dev *dev, int status, void *user_data) +{ + LibfprintDemoWindow *win = user_data; + int r; + + if (status < 0) { + libfprint_demo_set_mode (win, ERROR_MODE); + return; + } + + libfprint_demo_set_capture_label (win); + + win->dev = dev; + r = fp_async_capture_start (win->dev, FALSE, dev_capture_start_cb, user_data); + if (r < 0) { + g_warning ("fp_async_capture_start failed: %d", r); + libfprint_demo_set_mode (win, ERROR_MODE); + return; + } +} + +static void +activate_capture (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + LibfprintDemoWindow *win = user_data; + int r; + + libfprint_demo_set_mode (win, SPINNER_MODE); + g_clear_pointer (&win->img, fp_img_free); + + if (win->dev != NULL) { + dev_open_cb (win->dev, 0, user_data); + return; + } + + libfprint_demo_set_spinner_label (win, "Opening fingerprint reader"); + + r = fp_async_dev_open (win->ddev, dev_open_cb, user_data); + if (r < 0) { + g_warning ("fp_async_dev_open failed: %d", r); + libfprint_demo_set_mode (win, ERROR_MODE); + return; + } +} + +static void +activate_quit (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GtkApplication *app = user_data; + GtkWidget *win; + GList *list, *next; + + list = gtk_application_get_windows (app); + while (list) + { + win = list->data; + next = list->next; + + gtk_widget_destroy (GTK_WIDGET (win)); + + list = next; + } +} + +static void +activate_show_minutiae (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + LibfprintDemoWindow *win = user_data; + GVariant *state; + gboolean new_state; + + state = g_action_get_state (G_ACTION (action)); + new_state = !g_variant_get_boolean (state); + g_action_change_state (G_ACTION (action), g_variant_new_boolean (new_state)); + g_variant_unref (state); + + if (new_state) + win->img_flags |= IMAGE_DISPLAY_MINUTIAE; + else + win->img_flags &= ~IMAGE_DISPLAY_MINUTIAE; + + update_image (win); +} + +static void +activate_show_binary (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + LibfprintDemoWindow *win = user_data; + GVariant *state; + gboolean new_state; + + state = g_action_get_state (G_ACTION (action)); + new_state = !g_variant_get_boolean (state); + g_action_change_state (G_ACTION (action), g_variant_new_boolean (new_state)); + g_variant_unref (state); + + if (new_state) + win->img_flags |= IMAGE_DISPLAY_BINARY; + else + win->img_flags &= ~IMAGE_DISPLAY_BINARY; + + update_image (win); +} + +static void +change_show_minutiae_state (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_simple_action_set_state (action, state); +} + +static void +change_show_binary_state (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_simple_action_set_state (action, state); +} + +static GActionEntry app_entries[] = { + { "quit", activate_quit, NULL, NULL, NULL }, +}; + +static GActionEntry win_entries[] = { + { "show-minutiae", activate_show_minutiae, NULL, "false", change_show_minutiae_state }, + { "show-binary", activate_show_binary, NULL, "false", change_show_binary_state }, + { "capture", activate_capture, NULL, NULL, NULL } +}; + +static void +activate (GApplication *app) +{ + LibfprintDemoWindow *window; + + window = g_object_new (libfprint_demo_window_get_type (), + "application", app, + NULL); + gtk_widget_show (GTK_WIDGET (window)); +} + +static void +libfprint_demo_set_mode (LibfprintDemoWindow *win, + LibfprintDemoMode mode) +{ + struct fp_driver *drv; + char *title; + + switch (mode) { + case EMPTY_MODE: + gtk_stack_set_visible_child_name (GTK_STACK (win->mode_stack), "empty-mode"); + gtk_widget_set_sensitive (win->capture_button, FALSE); + gtk_spinner_stop (GTK_SPINNER (win->spinner)); + break; + case NOIMAGING_MODE: + gtk_stack_set_visible_child_name (GTK_STACK (win->mode_stack), "noimaging-mode"); + gtk_widget_set_sensitive (win->capture_button, FALSE); + gtk_spinner_stop (GTK_SPINNER (win->spinner)); + break; + case CAPTURE_MODE: + gtk_stack_set_visible_child_name (GTK_STACK (win->mode_stack), "capture-mode"); + gtk_widget_set_sensitive (win->capture_button, TRUE); + + drv = fp_dscv_dev_get_driver (win->ddev); + title = g_strdup_printf ("%s Test", fp_driver_get_full_name (drv)); + gtk_header_bar_set_title (GTK_HEADER_BAR (win->header_bar), title); + g_free (title); + + gtk_spinner_stop (GTK_SPINNER (win->spinner)); + break; + case SPINNER_MODE: + gtk_stack_set_visible_child_name (GTK_STACK (win->mode_stack), "spinner-mode"); + gtk_widget_set_sensitive (win->capture_button, FALSE); + gtk_spinner_start (GTK_SPINNER (win->spinner)); + break; + case ERROR_MODE: + gtk_stack_set_visible_child_name (GTK_STACK (win->mode_stack), "error-mode"); + gtk_widget_set_sensitive (win->capture_button, FALSE); + gtk_spinner_stop (GTK_SPINNER (win->spinner)); + break; + default: + g_assert_not_reached (); + } +} + +static void +libfprint_demo_init (LibfprintDemo *app) +{ + g_action_map_add_action_entries (G_ACTION_MAP (app), + app_entries, G_N_ELEMENTS (app_entries), + app); +} + +static void +libfprint_demo_class_init (LibfprintDemoClass *class) +{ + GApplicationClass *app_class = G_APPLICATION_CLASS (class); + + app_class->activate = activate; +} + +static void +libfprint_demo_window_init (LibfprintDemoWindow *window) +{ + struct fp_dscv_dev **discovered_devs; + + gtk_widget_init_template (GTK_WIDGET (window)); + gtk_window_set_default_size (GTK_WINDOW (window), 700, 500); + + g_action_map_add_action_entries (G_ACTION_MAP (window), + win_entries, G_N_ELEMENTS (win_entries), + window); + + if (fp_init () < 0) { + libfprint_demo_set_mode (window, ERROR_MODE); + return; + } + + setup_pollfds (); + + discovered_devs = fp_discover_devs(); + if (!discovered_devs) + return; + + if (!fp_driver_supports_imaging(fp_dscv_dev_get_driver(discovered_devs[0]))) { + libfprint_demo_set_mode (window, NOIMAGING_MODE); + return; + } + + window->ddev = discovered_devs[0]; + libfprint_demo_set_mode (window, CAPTURE_MODE); +} + +static void +libfprint_demo_window_class_init (LibfprintDemoWindowClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + gtk_widget_class_set_template_from_resource (widget_class, "/libfprint_demo/gtk-libfprint-test.ui"); + gtk_widget_class_bind_template_child (widget_class, LibfprintDemoWindow, header_bar); + gtk_widget_class_bind_template_child (widget_class, LibfprintDemoWindow, mode_stack); + gtk_widget_class_bind_template_child (widget_class, LibfprintDemoWindow, capture_button); + gtk_widget_class_bind_template_child (widget_class, LibfprintDemoWindow, capture_image); + gtk_widget_class_bind_template_child (widget_class, LibfprintDemoWindow, spinner); + gtk_widget_class_bind_template_child (widget_class, LibfprintDemoWindow, instructions); + + //FIXME setup dispose +} + +int main (int argc, char **argv) +{ + GtkApplication *app; + + app = GTK_APPLICATION (g_object_new (libfprint_demo_get_type (), + "application-id", "org.freedesktop.libfprint.Demo", + "flags", G_APPLICATION_FLAGS_NONE, + NULL)); + + return g_application_run (G_APPLICATION (app), 0, NULL); +} diff --git a/demo/gtk-libfprint-test.gresource.xml b/demo/gtk-libfprint-test.gresource.xml new file mode 100644 index 0000000..1540837 --- /dev/null +++ b/demo/gtk-libfprint-test.gresource.xml @@ -0,0 +1,6 @@ + + + + gtk-libfprint-test.ui + + diff --git a/demo/gtk-libfprint-test.ui b/demo/gtk-libfprint-test.ui new file mode 100644 index 0000000..0691620 --- /dev/null +++ b/demo/gtk-libfprint-test.ui @@ -0,0 +1,351 @@ + + + + + + + +
+ + Show Minutiae + win.show-minutiae + + + Show Binary + win.show-binary + +
+
+
diff --git a/demo/loop.c b/demo/loop.c new file mode 100644 index 0000000..81dd62e --- /dev/null +++ b/demo/loop.c @@ -0,0 +1,196 @@ +/* + * fprint D-Bus daemon + * Copyright (C) 2008 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "loop.h" + +struct fdsource { + GSource source; + GSList *pollfds; +}; + +static gboolean source_prepare(GSource *source, gint *timeout) +{ + int r; + struct timeval tv; + + r = fp_get_next_timeout(&tv); + if (r == 0) { + *timeout = -1; + return FALSE; + } + + if (!timerisset(&tv)) + return TRUE; + + *timeout = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + return FALSE; +} + +static gboolean source_check(GSource *source) +{ + struct fdsource *_fdsource = (struct fdsource *) source; + GSList *l; + struct timeval tv; + int r; + + if (!_fdsource->pollfds) + return FALSE; + + for (l = _fdsource->pollfds; l != NULL; l = l->next) { + GPollFD *pollfd = l->data; + + if (pollfd->revents) + return TRUE; + } + + r = fp_get_next_timeout(&tv); + if (r == 1 && !timerisset(&tv)) + return TRUE; + + return FALSE; +} + +static gboolean source_dispatch(GSource *source, GSourceFunc callback, + gpointer data) +{ + struct timeval zerotimeout = { + .tv_sec = 0, + .tv_usec = 0, + }; + + /* FIXME error handling */ + fp_handle_events_timeout(&zerotimeout); + + /* FIXME whats the return value used for? */ + return TRUE; +} + +static void source_finalize(GSource *source) +{ + struct fdsource *_fdsource = (struct fdsource *) source; + GSList *l; + + if (!_fdsource->pollfds) + return; + + for (l = _fdsource->pollfds; l != NULL; l = l->next) { + GPollFD *pollfd = l->data; + + g_source_remove_poll((GSource *) _fdsource, pollfd); + g_slice_free(GPollFD, pollfd); + _fdsource->pollfds = g_slist_delete_link(_fdsource->pollfds, l); + } + + g_slist_free(_fdsource->pollfds); +} + +static GSourceFuncs sourcefuncs = { + .prepare = source_prepare, + .check = source_check, + .dispatch = source_dispatch, + .finalize = source_finalize, +}; + +static struct fdsource *fdsource = NULL; + +static void pollfd_add(int fd, short events) +{ + GPollFD *pollfd; + + pollfd = g_slice_new(GPollFD); + pollfd->fd = fd; + pollfd->events = 0; + pollfd->revents = 0; + if (events & POLLIN) + pollfd->events |= G_IO_IN; + if (events & POLLOUT) + pollfd->events |= G_IO_OUT; + + fdsource->pollfds = g_slist_prepend(fdsource->pollfds, pollfd); + g_source_add_poll((GSource *) fdsource, pollfd); +} + +static void pollfd_added_cb(int fd, short events) +{ + g_debug("now monitoring fd %d", fd); + pollfd_add(fd, events); +} + +static void pollfd_removed_cb(int fd) +{ + GSList *l; + + g_debug("no longer monitoring fd %d", fd); + + if (!fdsource->pollfds) { + g_debug("cannot remove from list as list is empty?"); + return; + } + + for (l = fdsource->pollfds; l != NULL; l = l->next) { + GPollFD *pollfd = l->data; + + if (pollfd->fd != fd) + continue; + + g_source_remove_poll((GSource *) fdsource, pollfd); + g_slice_free(GPollFD, pollfd); + fdsource->pollfds = g_slist_delete_link(fdsource->pollfds, l); + return; + } + + g_error("couldn't find fd %d in list\n", fd); +} + +int setup_pollfds(void) +{ + ssize_t numfds; + size_t i; + struct fp_pollfd *fpfds; + GSource *gsource; + + gsource = g_source_new(&sourcefuncs, sizeof(struct fdsource)); + fdsource = (struct fdsource *) gsource; + fdsource->pollfds = NULL; + + numfds = fp_get_pollfds(&fpfds); + if (numfds < 0) { + if (fpfds) + free(fpfds); + return (int) numfds; + } else if (numfds > 0) { + for (i = 0; i < numfds; i++) { + struct fp_pollfd *fpfd = &fpfds[i]; + pollfd_add(fpfd->fd, fpfd->events); + } + } + + free(fpfds); + fp_set_pollfd_notifiers(pollfd_added_cb, pollfd_removed_cb); + g_source_attach(gsource, NULL); + return 0; +} diff --git a/demo/loop.h b/demo/loop.h new file mode 100644 index 0000000..0266bfb --- /dev/null +++ b/demo/loop.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2008 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef POLL_H + +#define POLL_H + +int setup_pollfds(void); + +#endif + diff --git a/demo/meson.build b/demo/meson.build new file mode 100644 index 0000000..4b6278a --- /dev/null +++ b/demo/meson.build @@ -0,0 +1,17 @@ +gtk_test_resources = gnome.compile_resources('gtk-test-resources', 'gtk-libfprint-test.gresource.xml', + source_dir : '.', + c_name : 'gtk_test') + +prefix = get_option('prefix') +bindir = join_paths(prefix, get_option('bindir')) + +executable('gtk-libfprint-test', + [ 'gtk-libfprint-test.c', 'loop.c', 'loop.h', gtk_test_resources ], + dependencies: [ libfprint_dep, gtk_dep ], + include_directories: [ + root_inc, + ], + c_args: [ common_cflags, + '-DPACKAGE_VERSION="' + meson.project_version() + '"' ], + install: true, + install_dir: bindir) diff --git a/meson.build b/meson.build index 87d7adf..5629119 100644 --- a/meson.build +++ b/meson.build @@ -111,6 +111,15 @@ if get_option('x11-examples') endif endif +if get_option('gtk-examples') + gnome = import('gnome') + + gtk_dep = dependency('gtk+-3.0', required: false) + if not gtk_dep.found() + error('GTK+ 3.x is required for GTK+ examples') + endif +endif + libfprint_conf.set('API_EXPORTED', '__attribute__((visibility("default")))') configure_file(output: 'config.h', configuration: libfprint_conf) @@ -120,6 +129,9 @@ if get_option('doc') gnome = import('gnome') subdir('doc') endif +if get_option('gtk-examples') + subdir('demo') +endif pkgconfig = import('pkgconfig') pkgconfig.generate( diff --git a/meson_options.txt b/meson_options.txt index f847bff..3e63304 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -14,6 +14,10 @@ option('x11-examples', description: 'Whether to build X11 example applications', type: 'boolean', value: true) +option('gtk-examples', + description: 'Whether to build GTK+ example applications', + type: 'boolean', + value: true) option('doc', description: 'Whether to build the API documentation', type: 'boolean',