/* SPDX-License-Identifier: LGPL-2.1-or-later */ #define FP_COMPONENT "gxfp5130" #include "drivers_api.h" #include "gxfp/session.h" #include #include #include #include #include #define GXFP_DEVICE_PATH_DEFAULT "/dev/gxfp" #define GXFP_ACTIVATE_TIMEOUT_MS 10000 #define GXFP_FINGER_TIMEOUT_MS INT_MAX #define GXFP_CAPTURE_TIMEOUT_MS 20000 #define GXFP_IMAGE_WIDTH 64 #define GXFP_IMAGE_HEIGHT 80 #define GXFP_IMAGE_PPMM (500.0 / 25.4) typedef enum { GXFP_JOB_OPEN, GXFP_JOB_ACTIVATE, GXFP_JOB_WAIT_FINGER_DOWN, GXFP_JOB_CAPTURE, GXFP_JOB_WAIT_FINGER_UP, GXFP_JOB_DEACTIVATE, GXFP_JOB_CLOSE, } GxfpJob; typedef struct { GxfpJob job; gint result; guint8 *image_data; gsize image_size; } GxfpJobResult; struct _FpiDeviceGxfp5130 { FpImageDevice parent; struct gxfp_session *session; gchar *device_path; gchar *debug_image_dir; guint64 capture_sequence; gboolean worker_running; gboolean deactivating; }; G_DECLARE_FINAL_TYPE (FpiDeviceGxfp5130, fpi_device_gxfp5130, FPI, DEVICE_GXFP5130, FpImageDevice) G_DEFINE_TYPE (FpiDeviceGxfp5130, fpi_device_gxfp5130, FP_TYPE_IMAGE_DEVICE) static void gxfp_save_debug_image (FpiDeviceGxfp5130 *self, const guint8 *data, gsize size) { g_autofree gchar *filename = NULL; g_autofree gchar *contents = NULL; g_autoptr(GError) error = NULL; gsize header_size; if (!self->debug_image_dir || !self->debug_image_dir[0]) return; if (size != GXFP_IMAGE_WIDTH * GXFP_IMAGE_HEIGHT) { fp_warn ("Refusing to save debug image with invalid size %" G_GSIZE_FORMAT, size); return; } if (g_mkdir_with_parents (self->debug_image_dir, 0700) < 0) { fp_warn ("Failed to create debug image directory %s: %s", self->debug_image_dir, g_strerror (errno)); return; } self->capture_sequence++; filename = g_strdup_printf ("%s/capture-%u-%06" G_GUINT64_FORMAT ".pgm", self->debug_image_dir, (guint) getpid (), self->capture_sequence); contents = g_malloc (32 + size); header_size = g_snprintf (contents, 32, "P5\n%d %d\n255\n", GXFP_IMAGE_WIDTH, GXFP_IMAGE_HEIGHT); memcpy (contents + header_size, data, size); if (!g_file_set_contents (filename, contents, header_size + size, &error)) fp_warn ("Failed to save debug image %s: %s", filename, error->message); else fp_dbg ("Saved raw debug image to %s", filename); } static void gxfp_start_job (FpiDeviceGxfp5130 *self, GxfpJob job); static GError * gxfp_error_new (gint result) { gint error_number = result < 0 ? -result : EIO; return g_error_new (G_IO_ERROR, g_io_error_from_errno (error_number), "GXFP session failed: %s (%d)", g_strerror (error_number), result); } static void gxfp_job_result_free (GxfpJobResult *result) { if (!result) return; g_free (result->image_data); g_free (result); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (GxfpJobResult, gxfp_job_result_free) static void gxfp_job_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (source_object); GxfpJob job = GPOINTER_TO_INT (task_data); GxfpJobResult *result = g_new0 (GxfpJobResult, 1); (void) cancellable; result->job = job; switch (job) { case GXFP_JOB_OPEN: result->result = gxfp_session_open (&self->session, self->device_path); break; case GXFP_JOB_ACTIVATE: result->result = gxfp_session_activate (self->session, GXFP_ACTIVATE_TIMEOUT_MS); break; case GXFP_JOB_WAIT_FINGER_DOWN: result->result = gxfp_session_arm_finger_down (self->session); if (!result->result) result->result = gxfp_session_wait_finger_down ( self->session, GXFP_FINGER_TIMEOUT_MS); break; case GXFP_JOB_CAPTURE: { struct gxfp_image12 image = { 0 }; result->result = gxfp_session_capture (self->session, &image, GXFP_CAPTURE_TIMEOUT_MS); if (!result->result) { result->image_size = image.width * image.height; result->image_data = g_malloc (result->image_size); result->result = gxfp_image12_to_u8 (&image, result->image_data, result->image_size); } gxfp_image12_clear (&image); } break; case GXFP_JOB_WAIT_FINGER_UP: result->result = gxfp_session_arm_finger_up (self->session); if (!result->result) result->result = gxfp_session_wait_finger_up ( self->session, GXFP_FINGER_TIMEOUT_MS); break; case GXFP_JOB_DEACTIVATE: result->result = gxfp_session_deactivate (self->session); break; case GXFP_JOB_CLOSE: gxfp_session_close (self->session); self->session = NULL; result->result = 0; break; default: result->result = -EINVAL; break; } g_task_return_pointer (task, result, (GDestroyNotify) gxfp_job_result_free); } static void gxfp_finish_deactivate (FpiDeviceGxfp5130 *self, gint result) { self->deactivating = FALSE; fpi_image_device_deactivate_complete ( FP_IMAGE_DEVICE (self), result ? gxfp_error_new (result) : NULL); } static void gxfp_job_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (source_object); g_autoptr(GxfpJobResult) result = g_task_propagate_pointer (G_TASK (res), NULL); FpImageDevice *image_device = FP_IMAGE_DEVICE (self); (void) user_data; self->worker_running = FALSE; if (self->deactivating && result->job != GXFP_JOB_DEACTIVATE) { gxfp_start_job (self, GXFP_JOB_DEACTIVATE); return; } if (result->result) { GError *error = gxfp_error_new (result->result); switch (result->job) { case GXFP_JOB_OPEN: fpi_image_device_open_complete (image_device, error); break; case GXFP_JOB_ACTIVATE: fpi_image_device_activate_complete (image_device, error); break; case GXFP_JOB_DEACTIVATE: gxfp_finish_deactivate (self, result->result); g_error_free (error); break; case GXFP_JOB_CLOSE: fpi_image_device_close_complete (image_device, error); break; case GXFP_JOB_WAIT_FINGER_DOWN: case GXFP_JOB_CAPTURE: case GXFP_JOB_WAIT_FINGER_UP: default: fpi_image_device_session_error (image_device, error); break; } return; } switch (result->job) { case GXFP_JOB_OPEN: fpi_image_device_open_complete (image_device, NULL); break; case GXFP_JOB_ACTIVATE: fpi_image_device_activate_complete (image_device, NULL); break; case GXFP_JOB_WAIT_FINGER_DOWN: fpi_image_device_report_finger_status (image_device, TRUE); break; case GXFP_JOB_CAPTURE: { FpImage *image; g_assert (result->image_size == GXFP_IMAGE_WIDTH * GXFP_IMAGE_HEIGHT); image = fp_image_new (GXFP_IMAGE_WIDTH, GXFP_IMAGE_HEIGHT); memcpy (image->data, result->image_data, result->image_size); image->ppmm = GXFP_IMAGE_PPMM; image->flags = FPI_IMAGE_NONE; gxfp_save_debug_image (self, result->image_data, result->image_size); fpi_image_device_image_captured (image_device, image); } break; case GXFP_JOB_WAIT_FINGER_UP: fpi_image_device_report_finger_status (image_device, FALSE); break; case GXFP_JOB_DEACTIVATE: gxfp_finish_deactivate (self, 0); break; case GXFP_JOB_CLOSE: g_clear_pointer (&self->device_path, g_free); g_clear_pointer (&self->debug_image_dir, g_free); fpi_image_device_close_complete (image_device, NULL); break; } } static void gxfp_start_job (FpiDeviceGxfp5130 *self, GxfpJob job) { g_autoptr(GTask) task = NULL; g_assert (!self->worker_running); self->worker_running = TRUE; task = g_task_new (self, NULL, gxfp_job_done, NULL); g_task_set_task_data (task, GINT_TO_POINTER (job), NULL); g_task_run_in_thread (task, gxfp_job_thread); } static void gxfp_dev_open (FpImageDevice *device) { FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device); const gchar *path = fpi_device_get_virtual_env (FP_DEVICE (device)); self->device_path = g_strdup (path && path[0] ? path : GXFP_DEVICE_PATH_DEFAULT); self->debug_image_dir = g_strdup (g_getenv ("GXFP_DEBUG_IMAGE_DIR")); self->capture_sequence = 0; gxfp_start_job (self, GXFP_JOB_OPEN); } static void gxfp_dev_close (FpImageDevice *device) { FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device); g_assert (!self->worker_running); gxfp_start_job (self, GXFP_JOB_CLOSE); } static void gxfp_dev_activate (FpImageDevice *device) { FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device); self->deactivating = FALSE; gxfp_start_job (self, GXFP_JOB_ACTIVATE); } static void gxfp_dev_deactivate (FpImageDevice *device) { FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device); self->deactivating = TRUE; if (self->worker_running) { gxfp_session_cancel (self->session); return; } gxfp_start_job (self, GXFP_JOB_DEACTIVATE); } static void gxfp_dev_change_state (FpImageDevice *device, FpiImageDeviceState state) { FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device); if (self->deactivating || self->worker_running) return; switch (state) { case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON: gxfp_start_job (self, GXFP_JOB_WAIT_FINGER_DOWN); break; case FPI_IMAGE_DEVICE_STATE_CAPTURE: gxfp_start_job (self, GXFP_JOB_CAPTURE); break; case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF: gxfp_start_job (self, GXFP_JOB_WAIT_FINGER_UP); break; case FPI_IMAGE_DEVICE_STATE_INACTIVE: case FPI_IMAGE_DEVICE_STATE_ACTIVATING: case FPI_IMAGE_DEVICE_STATE_DEACTIVATING: case FPI_IMAGE_DEVICE_STATE_IDLE: default: break; } } static const FpIdEntry id_table[] = { { .virtual_envvar = "FP_GXFP5130" }, { .virtual_envvar = NULL }, }; static void fpi_device_gxfp5130_init (FpiDeviceGxfp5130 *self) { self->session = NULL; } static void fpi_device_gxfp5130_class_init (FpiDeviceGxfp5130Class *klass) { FpDeviceClass *device_class = FP_DEVICE_CLASS (klass); FpImageDeviceClass *image_class = FP_IMAGE_DEVICE_CLASS (klass); device_class->id = FP_COMPONENT; device_class->full_name = "Goodix GXFP5130 (ChicagoHU 0x2504)"; device_class->type = FP_DEVICE_TYPE_VIRTUAL; device_class->id_table = id_table; device_class->scan_type = FP_SCAN_TYPE_PRESS; image_class->img_open = gxfp_dev_open; image_class->img_close = gxfp_dev_close; image_class->activate = gxfp_dev_activate; image_class->deactivate = gxfp_dev_deactivate; image_class->change_state = gxfp_dev_change_state; image_class->img_width = GXFP_IMAGE_WIDTH; image_class->img_height = GXFP_IMAGE_HEIGHT; }