/* * 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); }