From dede35fade505b4656186ef411297a2d598db503 Mon Sep 17 00:00:00 2001 From: lignah Date: Tue, 2 Dec 2025 08:51:59 +0900 Subject: [PATCH 1/4] Add experimental Samsung 730B driver skeleton --- .gitignore | 2 + libfprint/drivers/samsung730b.c | 84 +++++++++++++++++++++++++++++++++ libfprint/meson.build | 2 + meson.build | 1 + 4 files changed, 89 insertions(+) create mode 100644 libfprint/drivers/samsung730b.c diff --git a/.gitignore b/.gitignore index 07d73995..9e2641e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.o *.swp _build +.cache/ +tests/__pycache__/ diff --git a/libfprint/drivers/samsung730b.c b/libfprint/drivers/samsung730b.c new file mode 100644 index 00000000..ce90f90e --- /dev/null +++ b/libfprint/drivers/samsung730b.c @@ -0,0 +1,84 @@ +#define FP_COMPONENT "samsung730b" + +#include "drivers_api.h" + +#define SAMSUNG730B_VID 0x04e8 +#define SAMSUNG730B_PID 0x730b + +struct _FpiDeviceSamsung730b +{ + FpImageDevice parent; +}; + +G_DECLARE_FINAL_TYPE (FpiDeviceSamsung730b, + fpi_device_samsung730b, + FPI, DEVICE_SAMSUNG730B, + FpImageDevice); + +G_DEFINE_TYPE (FpiDeviceSamsung730b, + fpi_device_samsung730b, + FP_TYPE_IMAGE_DEVICE); + +static void +samsung730b_dev_init (FpImageDevice *dev) +{ + fpi_image_device_open_complete (dev, NULL); +} + +static void +samsung730b_dev_deinit (FpImageDevice *dev) +{ + fpi_image_device_close_complete (dev, NULL); +} + +static void +samsung730b_dev_activate (FpImageDevice *dev) +{ + fpi_image_device_activate_complete (dev, NULL); +} + +static void +samsung730b_dev_deactivate (FpImageDevice *dev) +{ + fpi_image_device_deactivate_complete (dev, NULL); +} + +static const FpIdEntry samsung730b_id_table[] = { + { + .pid = SAMSUNG730B_PID, + .vid = SAMSUNG730B_VID, + .driver_data = 0, + }, + { + .pid = 0, + .vid = 0, + .driver_data = 0, + }, +}; + +static void +fpi_device_samsung730b_init (FpiDeviceSamsung730b *self) +{ +} + +static void +fpi_device_samsung730b_class_init (FpiDeviceSamsung730bClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass); + + dev_class->id = "samsung730b"; + dev_class->full_name = "Samsung 730B (experimental)"; + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->id_table = samsung730b_id_table; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + + img_class->img_open = samsung730b_dev_init; + img_class->img_close = samsung730b_dev_deinit; + img_class->activate = samsung730b_dev_activate; + img_class->deactivate = samsung730b_dev_deactivate; + + img_class->bz3_threshold = 20; + img_class->img_width = -1; + img_class->img_height = -1; +} \ No newline at end of file diff --git a/libfprint/meson.build b/libfprint/meson.build index 34494813..10ed9b9e 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -153,6 +153,8 @@ driver_sources = { [ 'drivers/realtek/realtek.c' ], 'focaltech_moc' : [ 'drivers/focaltech_moc/focaltech_moc.c' ], + 'samsung730b' : + [ 'drivers/samsung730b.c' ], } helper_sources = { diff --git a/meson.build b/meson.build index baafa19c..3dc548da 100644 --- a/meson.build +++ b/meson.build @@ -144,6 +144,7 @@ default_drivers = [ 'fpcmoc', 'realtek', 'focaltech_moc', + 'samsung730b', ] spi_drivers = [ From 063830777f017bec4572ed75522ccc0134fbeeec Mon Sep 17 00:00:00 2001 From: lignah Date: Thu, 4 Dec 2025 03:39:52 +0900 Subject: [PATCH 2/4] just capture --- libfprint/drivers/samsung730b.c | 485 ++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) diff --git a/libfprint/drivers/samsung730b.c b/libfprint/drivers/samsung730b.c index ce90f90e..460a70c7 100644 --- a/libfprint/drivers/samsung730b.c +++ b/libfprint/drivers/samsung730b.c @@ -5,6 +5,15 @@ #define SAMSUNG730B_VID 0x04e8 #define SAMSUNG730B_PID 0x730b +#define SAMSUNG730B_BULK_EP_IN (0x82 | FPI_USB_ENDPOINT_IN) +#define SAMSUNG730B_BULK_EP_OUT (0x01 | FPI_USB_ENDPOINT_OUT) + +#define SAMSUNG730B_IMG_WIDTH 112 +#define SAMSUNG730B_IMG_HEIGHT 96 +#define SAMSUNG730B_DATA_OFFSET 180 + +#define BULK_PACKET_SIZE 256 + struct _FpiDeviceSamsung730b { FpImageDevice parent; @@ -19,22 +28,498 @@ G_DEFINE_TYPE (FpiDeviceSamsung730b, fpi_device_samsung730b, FP_TYPE_IMAGE_DEVICE); + + + + +struct Samsung730bCmd +{ + const guint8 *data; + gsize len; +}; + +static const guint8 cmd0[] = { 0x4f, 0x80 }; +static const guint8 cmd1[] = { 0xa9, 0x4f, 0x80 }; +static const guint8 cmd2[] = { 0xa9, 0xb9, 0x00 }; +static const guint8 cmd3[] = { 0xa9, 0x60, 0x1b, 0x00 }; +static const guint8 cmd4[] = { 0xa9, 0x50, 0x21, 0x00 }; +static const guint8 cmd5[] = { 0xa9, 0x61, 0x00, 0x00 }; +static const guint8 cmd6[] = { 0xa9, 0x62, 0x00, 0x1a }; +static const guint8 cmd7[] = { 0xa9, 0x63, 0x00, 0x1a }; +static const guint8 cmd8[] = { 0xa9, 0x64, 0x04, 0x0a }; +static const guint8 cmd9[] = { 0xa9, 0x66, 0x0f, 0x80 }; +static const guint8 cmd10[] = { 0xa9, 0x67, 0x1b, 0x00 }; +static const guint8 cmd11[] = { 0xa9, 0x68, 0x00, 0x0f }; +static const guint8 cmd12[] = { 0xa9, 0x69, 0x00, 0x14 }; +static const guint8 cmd13[] = { 0xa9, 0x6a, 0x00, 0x19 }; +static const guint8 cmd14[] = { 0xa9, 0x6c, 0x00, 0x19 }; +static const guint8 cmd15[] = { 0xa9, 0x40, 0x43, 0x00 }; +static const guint8 cmd16[] = { 0xa9, 0x41, 0x6f, 0x00 }; +static const guint8 cmd17[] = { 0xa9, 0x55, 0x20, 0x00 }; +static const guint8 cmd18[] = { 0xa9, 0x5f, 0x00, 0x00 }; +static const guint8 cmd19[] = { 0xa9, 0x52, 0x27, 0x00 }; +static const guint8 cmd20[] = { 0xa9, 0x09, 0x00, 0x00 }; +static const guint8 cmd21[] = { 0xa9, 0x5d, 0x4d, 0x00 }; +static const guint8 cmd22[] = { 0xa9, 0x51, 0xa8, 0x25 }; +static const guint8 cmd23[] = { 0xa9, 0x03, 0x00 }; +static const guint8 cmd24[] = { 0xa9, 0x38, 0x01, 0x00 }; +static const guint8 cmd25[] = { 0xa9, 0x3d, 0xff, 0x0f }; +static const guint8 cmd26[] = { 0xa9, 0x10, 0x60, 0x00 }; +static const guint8 cmd27[] = { 0xa9, 0x3b, 0x14, 0x00 }; +static const guint8 cmd28[] = { 0xa9, 0x2f, 0xf6, 0xff }; +static const guint8 cmd29[] = { 0xa9, 0x09, 0x00, 0x00 }; +static const guint8 cmd30[] = { 0xa9, 0x0c, 0x00 }; +static const guint8 cmd31[] = { 0xa8, 0x20, 0x00, 0x00 }; +static const guint8 cmd32[] = { 0xa9, 0x04, 0x00 }; +static const guint8 cmd33[] = { 0xa8, 0x08, 0x00 }; +static const guint8 cmd34[] = { 0xa9, 0x09, 0x00, 0x00 }; +static const guint8 cmd35[] = { 0xa8, 0x3e, 0x00, 0x00 }; +static const guint8 cmd36[] = { 0xa9, 0x03, 0x00, 0x00 }; +static const guint8 cmd37[] = { 0xa8, 0x20, 0x00, 0x00 }; +static const guint8 cmd38[] = { 0xa9, 0x10, 0x00, 0x01 }; +static const guint8 cmd39[] = { 0xa9, 0x2f, 0xef, 0x00 }; +static const guint8 cmd40[] = { 0xa9, 0x09, 0x00, 0x00 }; +static const guint8 cmd41[] = { 0xa9, 0x5d, 0x4d, 0x00 }; +static const guint8 cmd42[] = { 0xa9, 0x51, 0x3a, 0x25 }; +static const guint8 cmd43[] = { 0xa9, 0x0c, 0x00 }; +static const guint8 cmd44[] = { 0xa8, 0x20, 0x00, 0x00 }; +static const guint8 cmd45[] = { 0xa9, 0x04, 0x00, 0x00 }; +static const guint8 cmd46[] = { 0xa9, 0x09, 0x00, 0x00 }; + +static const struct Samsung730bCmd samsung730b_init_cmds[] = { + { cmd0, sizeof (cmd0) }, + { cmd1, sizeof (cmd1) }, + { cmd2, sizeof (cmd2) }, + { cmd3, sizeof (cmd3) }, + { cmd4, sizeof (cmd4) }, + { cmd5, sizeof (cmd5) }, + { cmd6, sizeof (cmd6) }, + { cmd7, sizeof (cmd7) }, + { cmd8, sizeof (cmd8) }, + { cmd9, sizeof (cmd9) }, + { cmd10, sizeof (cmd10) }, + { cmd11, sizeof (cmd11) }, + { cmd12, sizeof (cmd12) }, + { cmd13, sizeof (cmd13) }, + { cmd14, sizeof (cmd14) }, + { cmd15, sizeof (cmd15) }, + { cmd16, sizeof (cmd16) }, + { cmd17, sizeof (cmd17) }, + { cmd18, sizeof (cmd18) }, + { cmd19, sizeof (cmd19) }, + { cmd20, sizeof (cmd20) }, + { cmd21, sizeof (cmd21) }, + { cmd22, sizeof (cmd22) }, + { cmd23, sizeof (cmd23) }, + { cmd24, sizeof (cmd24) }, + { cmd25, sizeof (cmd25) }, + { cmd26, sizeof (cmd26) }, + { cmd27, sizeof (cmd27) }, + { cmd28, sizeof (cmd28) }, + { cmd29, sizeof (cmd29) }, + { cmd30, sizeof (cmd30) }, + { cmd31, sizeof (cmd31) }, + { cmd32, sizeof (cmd32) }, + { cmd33, sizeof (cmd33) }, + { cmd34, sizeof (cmd34) }, + { cmd35, sizeof (cmd35) }, + { cmd36, sizeof (cmd36) }, + { cmd37, sizeof (cmd37) }, + { cmd38, sizeof (cmd38) }, + { cmd39, sizeof (cmd39) }, + { cmd40, sizeof (cmd40) }, + { cmd41, sizeof (cmd41) }, + { cmd42, sizeof (cmd42) }, + { cmd43, sizeof (cmd43) }, + { cmd44, sizeof (cmd44) }, + { cmd45, sizeof (cmd45) }, + { cmd46, sizeof (cmd46) }, +}; + +static const gsize samsung730b_init_cmds_len = + G_N_ELEMENTS (samsung730b_init_cmds); + + +static const guint16 capture_indices[] = { + 0x032a, 0x042a, 0x052a, 0x062a, + 0x072a, 0x082a, 0x092a, 0x0a2a, + 0x0b2a, 0x0c2a, 0x0d2a, 0x0e2a, + 0x0f2a, 0x102a, 0x112a, 0x122a, + 0x132a, 0x142a, 0x152a, 0x162a, + 0x172a, 0x182a, 0x192a, 0x1a2a, + 0x1b2a, 0x1c2a, 0x1d2a, 0x1e2a, + 0x1f2a, 0x202a, 0x212a, 0x222a, + 0x232a, 0x242a, 0x252a, 0x262a, + 0x272a, 0x282a, 0x292a, 0x2a2a, + 0x2b2a, 0x2c2a, 0x2d2a, 0x2e2a, + 0x2f2a, 0x302a, 0x312a, 0x322a, + 0x332a, 0x342a, 0x352a, 0x362a, + 0x372a, 0x382a, 0x392a, 0x3a2a, + 0x3b2a, 0x3c2a, 0x3d2a, 0x3e2a, + 0x3f2a, 0x402a, 0x412a, 0x422a, + 0x432a, 0x442a, 0x452a, 0x462a, + 0x472a, 0x482a, 0x492a, 0x4a2a, + 0x4b2a, 0x4c2a, 0x4d2a, 0x4e2a, + 0x4f2a, 0x502a, 0x512a, 0x522a, + 0x532a, 0x542a, 0x552a, 0x562a, + 0x572a, +}; +static const size_t CAPTURE_NUM_CHUNKS = sizeof(capture_indices) / sizeof(capture_indices[0]); + + + +static gboolean +samsung730b_capture_frame (FpImageDevice *dev, + guint8 **out_buf, + gsize *out_len, + GError **error) +{ + GUsbDevice *usb_dev; + guint8 *buf; + gsize total_len = 0; + gsize capacity = (gsize) CAPTURE_NUM_CHUNKS * BULK_PACKET_SIZE + 1024; + gsize actual_length; + gboolean ok; + gsize i; + + guint16 windex0; + guint16 windex; + + usb_dev = fpi_device_get_usb_device (FP_DEVICE (dev)); + + buf = g_malloc (capacity); + if (!buf) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + "samsung730b: failed to allocate capture buffer"); + return FALSE; + } + + /* ---------- 1) 첫 chunk: 상태 응답만 처리 (데이터 누적 X) ---------- */ + windex0 = capture_indices[0]; + + ok = g_usb_device_control_transfer (usb_dev, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0xCA, /* request */ + 0x0003, /* value */ + windex0, /* idx */ + NULL, /* data */ + 0, /* length */ + &actual_length, + 500, + NULL, + error); + if (!ok) + goto out_fail; + + /* 캡처 시작 명령 (a8 06 00 00...) */ + { + guint8 start_cmd[256] = { 0 }; + + start_cmd[0] = 0xa8; + start_cmd[1] = 0x06; + start_cmd[2] = 0x00; + start_cmd[3] = 0x00; + + ok = g_usb_device_bulk_transfer (usb_dev, + SAMSUNG730B_BULK_EP_OUT, + start_cmd, + sizeof (start_cmd), + &actual_length, + 500, + NULL, + error); + if (!ok) + goto out_fail; + } + + /* 첫 IN: 짧은 상태 응답만 읽고 버림 */ + { + guint8 tmp0[256]; + + ok = g_usb_device_bulk_transfer (usb_dev, + SAMSUNG730B_BULK_EP_IN, + tmp0, + sizeof (tmp0), + &actual_length, + 500, + NULL, + error); + if (!ok) + goto out_fail; + } + + /* ---------- 2) 나머지 chunk: 실제 데이터 + ACK ---------- */ + for (i = 1; i < CAPTURE_NUM_CHUNKS; i++) + { + guint8 tmp[256]; + guint8 ack[256] = { 0 }; + + windex = capture_indices[i]; + + /* CONTROL 0xCA: 청크 설정 */ + ok = g_usb_device_control_transfer (usb_dev, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0xCA, + 0x0003, + windex, + NULL, + 0, + &actual_length, + 500, + NULL, + error); + if (!ok) + goto out_fail; + + /* 데이터 IN (256 bytes 기대) */ + ok = g_usb_device_bulk_transfer (usb_dev, + SAMSUNG730B_BULK_EP_IN, + tmp, + sizeof (tmp), + &actual_length, + 1000, + NULL, + error); + if (!ok) + goto out_fail; + + if (actual_length == 0) + break; + + if (total_len + actual_length > capacity) + { + capacity *= 2; + buf = g_realloc (buf, capacity); + if (!buf) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + "samsung730b: realloc failed"); + return FALSE; + } + } + + memcpy (buf + total_len, tmp, actual_length); + total_len += actual_length; + + /* ACK (256 zeros) */ + ok = g_usb_device_bulk_transfer (usb_dev, + SAMSUNG730B_BULK_EP_OUT, + ack, + sizeof (ack), + &actual_length, + 500, + NULL, + error); + if (!ok) + goto out_fail; + } + + *out_buf = buf; + *out_len = total_len; + return TRUE; + +out_fail: + g_free (buf); + *out_buf = NULL; + *out_len = 0; + return FALSE; +} + +#include +static void +samsung730b_do_capture_and_report (FpImageDevice *dev) +{ + guint8 *raw = NULL; + gsize raw_len = 0; + GError *error = NULL; + FpImage *image = NULL; + gsize needed; + + needed = SAMSUNG730B_DATA_OFFSET + + (gsize) SAMSUNG730B_IMG_WIDTH * SAMSUNG730B_IMG_HEIGHT; + + if (!samsung730b_capture_frame (dev, &raw, &raw_len, &error)) + goto out_error; + + if (raw_len < needed) + { + g_clear_error (&error); + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "samsung730b: captured data too short (%"G_GSIZE_FORMAT")", raw_len); + goto out_error; + } + + image = fp_image_new (SAMSUNG730B_IMG_WIDTH, SAMSUNG730B_IMG_HEIGHT); + + { + gsize img_size; + guchar *data = (guchar *) fp_image_get_data (image, &img_size); + + if (img_size < SAMSUNG730B_IMG_WIDTH * SAMSUNG730B_IMG_HEIGHT) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "samsung730b: image buffer too small (%"G_GSIZE_FORMAT")", img_size); + goto out_error; + } + + memcpy (data, + raw + SAMSUNG730B_DATA_OFFSET, + SAMSUNG730B_IMG_WIDTH * SAMSUNG730B_IMG_HEIGHT); + } + + g_free (raw); + raw = NULL; + + /* 디버그용 덤프 (원하면 유지) */ + g_message ("samsung730b: dumping captured image to /tmp/s730b.pgm"); + + { + FILE *f = fopen ("/tmp/s730b.pgm", "wb"); + if (f) + { + const guchar *data = fp_image_get_data (image, NULL); + + fprintf (f, "P5\n%d %d\n255\n", + SAMSUNG730B_IMG_WIDTH, SAMSUNG730B_IMG_HEIGHT); + fwrite (data, 1, + SAMSUNG730B_IMG_WIDTH * SAMSUNG730B_IMG_HEIGHT, + f); + fclose (f); + } + else + { + g_message ("samsung730b: failed to open /tmp/s730b.pgm for writing"); + } + } +g_object_unref (image); + +out_error: + if (raw) + g_free (raw); + if (image) + g_object_unref (image); + + if (error) + { + g_warning ("samsung730b: capture error: %s", error->message); + g_clear_error (&error); + } +} + + + static void samsung730b_dev_init (FpImageDevice *dev) { + GUsbDevice *usb_dev; + GError *error = NULL; + guint8 c3_data[16] = { + 0x80, 0x84, 0x1e, 0x00, + 0x08, 0x00, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00 + }; + guint8 iface = 0; /* 테스트 코드에서 claim한 인터페이스 번호 (보통 0) */ + gboolean ok; + gsize actual_length; + gsize i; + + usb_dev = fpi_device_get_usb_device (FP_DEVICE (dev)); + + /* 1. USB 인터페이스 claim */ + if (!g_usb_device_claim_interface (usb_dev, iface, 0, &error)) + { + fpi_image_device_open_complete (dev, error); + return; + } + + /* 2. control 0xC3 전송 (libusb_control_transfer 대체) */ + ok = g_usb_device_control_transfer (usb_dev, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0xC3, /* request */ + 0x0000, /* value */ + 0x0000, /* idx */ + c3_data, /* data */ + sizeof (c3_data), /* length */ + &actual_length, /* actual_length */ + 500, /* timeout ms */ + NULL, /* cancellable */ + &error); /* error */ + if (!ok || actual_length != sizeof (c3_data)) + { + if (!error) + error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, + "samsung730b: control 0xC3 transfer failed " + "(actual_length=%"G_GSIZE_FORMAT")", + actual_length); + fpi_image_device_open_complete (dev, error); + return; + } + + /* 3. init 명령들 bulk OUT 전송 (libusb_bulk_transfer 대체) */ + for (i = 0; i < samsung730b_init_cmds_len; i++) + { + const struct Samsung730bCmd *cmd = &samsung730b_init_cmds[i]; + + ok = g_usb_device_bulk_transfer (usb_dev, + SAMSUNG730B_BULK_EP_OUT, + (guint8 *) cmd->data, + cmd->len, + &actual_length, + 500, + NULL, /* cancellable */ + &error); /* error */ + if (!ok || actual_length != cmd->len) + { + if (!error) + error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, + "samsung730b: init bulk transfer failed " + "idx=%"G_GSIZE_FORMAT" actual_length=%"G_GSIZE_FORMAT"/%"G_GSIZE_FORMAT, + i, actual_length, cmd->len); + fpi_image_device_open_complete (dev, error); + return; + } + } + + /* 4. 성공 */ fpi_image_device_open_complete (dev, NULL); } static void samsung730b_dev_deinit (FpImageDevice *dev) { + GUsbDevice *usb_dev; + GError *error = NULL; + guint8 iface = 0; + + usb_dev = fpi_device_get_usb_device (FP_DEVICE (dev)); + + if (!g_usb_device_release_interface (usb_dev, iface, 0, &error)) + { + fpi_image_device_close_complete (dev, error); + return; + } + fpi_image_device_close_complete (dev, NULL); } static void samsung730b_dev_activate (FpImageDevice *dev) { + /* 장치 준비 완료 (CAPTURE state 진입) */ fpi_image_device_activate_complete (dev, NULL); + + /* 한 장 캡처해서 image_captured로 보고 */ + samsung730b_do_capture_and_report (dev); + + /* 단발 캡처 기기라서, 추가로 루프 안 돌고 바로 반환함 */ } static void From fbaa97eaeb61d4ec4678d731db598ddc3aed3d53 Mon Sep 17 00:00:00 2001 From: lignah Date: Sun, 7 Dec 2025 15:59:11 +0900 Subject: [PATCH 3/4] drivers: add initial Samsung 730B image driver Add an experimental driver for the Samsung 730B USB fingerprint sensor (04e8:730b). The implementation is based on reverse engineering of the Windows traffic and a Python prototype: - repo: https://github.com/lignah/samsung-730b-reverse - script: scripts/samsung_730b_v1_3.py Implemented pieces: - device init sequence (0xC3 control + A9/A8 bulk writes) - image capture using 0xCA control and 256-byte bulk chunks - image layout: 112x96 at offset 180 in the raw buffer - basic image-based finger detection (short probe + ff ratio heuristic) The driver currently exposes the device as a press-type image sensor and can capture usable fingerprints via libfprint. Known limitations: - Finger detection and capture work, but fprintd enrollment UX is still rough (minutiae detection sometimes fails, and higher-level behavior needs tuning). - The driver is marked experimental on purpose; further iteration with libfprint/fprintd maintainers is expected. --- libfprint/drivers/samsung730b.c | 1023 +++++++++++++++++-------------- 1 file changed, 561 insertions(+), 462 deletions(-) diff --git a/libfprint/drivers/samsung730b.c b/libfprint/drivers/samsung730b.c index 460a70c7..319a7d74 100644 --- a/libfprint/drivers/samsung730b.c +++ b/libfprint/drivers/samsung730b.c @@ -5,521 +5,624 @@ #define SAMSUNG730B_VID 0x04e8 #define SAMSUNG730B_PID 0x730b -#define SAMSUNG730B_BULK_EP_IN (0x82 | FPI_USB_ENDPOINT_IN) -#define SAMSUNG730B_BULK_EP_OUT (0x01 | FPI_USB_ENDPOINT_OUT) +#define SAMSUNG730B_EP_IN (0x82 | FPI_USB_ENDPOINT_IN) +#define SAMSUNG730B_EP_OUT (0x01 | FPI_USB_ENDPOINT_OUT) +#define BULK_PACKET_SIZE 256 -#define SAMSUNG730B_IMG_WIDTH 112 -#define SAMSUNG730B_IMG_HEIGHT 96 -#define SAMSUNG730B_DATA_OFFSET 180 +#define IMG_W 112 +#define IMG_H 96 +#define IMG_OFFSET 180 -#define BULK_PACKET_SIZE 256 +/* detect 파라미터 (파이썬 v1.3 기준) */ +#define DETECT_MAX_ROUNDS 5 +#define DETECT_PROBES_PER_ROUND 5 +#define DETECT_PROBE_CHUNKS 6 +#define DETECT_INTERVAL_MS 400 +#define DETECT_INIT_TIMEOUT_MS 500 +#define DETECT_BULK_TIMEOUT_MS 700 -struct _FpiDeviceSamsung730b +/* capture 파라미터 */ +#define CAPTURE_NUM_CHUNKS 85 +#define CAPTURE_START_IDX 0x032a +#define CAPTURE_CTRL_REQ 0xCA +#define CAPTURE_CTRL_VAL 0x0003 + +typedef struct _FpiDeviceSamsung730b { FpImageDevice parent; -}; +} FpiDeviceSamsung730b; G_DECLARE_FINAL_TYPE (FpiDeviceSamsung730b, fpi_device_samsung730b, FPI, DEVICE_SAMSUNG730B, FpImageDevice); - G_DEFINE_TYPE (FpiDeviceSamsung730b, fpi_device_samsung730b, FP_TYPE_IMAGE_DEVICE); +/* ---------- init 시퀀스 ---------- */ - - - -struct Samsung730bCmd +typedef struct { const guint8 *data; gsize len; +} SamsungCmd; + +#define CMD(...) (const guint8[]){ __VA_ARGS__ }, sizeof((const guint8[]){ __VA_ARGS__ }) + +static const SamsungCmd init_cmds[] = { + { CMD(0x4f, 0x80) }, + { CMD(0xa9, 0x4f, 0x80) }, + { CMD(0xa9, 0xb9, 0x00) }, + { CMD(0xa9, 0x60, 0x1b, 0x00) }, + { CMD(0xa9, 0x50, 0x21, 0x00) }, + { CMD(0xa9, 0x61, 0x00, 0x00) }, + { CMD(0xa9, 0x62, 0x00, 0x1a) }, + { CMD(0xa9, 0x63, 0x00, 0x1a) }, + { CMD(0xa9, 0x64, 0x04, 0x0a) }, + { CMD(0xa9, 0x66, 0x0f, 0x80) }, + { CMD(0xa9, 0x67, 0x1b, 0x00) }, + { CMD(0xa9, 0x68, 0x00, 0x0f) }, + { CMD(0xa9, 0x69, 0x00, 0x14) }, + { CMD(0xa9, 0x6a, 0x00, 0x19) }, + { CMD(0xa9, 0x6c, 0x00, 0x19) }, + { CMD(0xa9, 0x40, 0x43, 0x00) }, + { CMD(0xa9, 0x41, 0x6f, 0x00) }, + { CMD(0xa9, 0x55, 0x20, 0x00) }, + { CMD(0xa9, 0x5f, 0x00, 0x00) }, + { CMD(0xa9, 0x52, 0x27, 0x00) }, + { CMD(0xa9, 0x09, 0x00, 0x00) }, + { CMD(0xa9, 0x5d, 0x4d, 0x00) }, + { CMD(0xa9, 0x51, 0xa8, 0x25) }, + { CMD(0xa9, 0x03, 0x00) }, + { CMD(0xa9, 0x38, 0x01, 0x00) }, + { CMD(0xa9, 0x3d, 0xff, 0x0f) }, + { CMD(0xa9, 0x10, 0x60, 0x00) }, + { CMD(0xa9, 0x3b, 0x14, 0x00) }, + { CMD(0xa9, 0x2f, 0xf6, 0xff) }, + { CMD(0xa9, 0x09, 0x00, 0x00) }, + { CMD(0xa9, 0x0c, 0x00) }, + { CMD(0xa8, 0x20, 0x00, 0x00) }, + { CMD(0xa9, 0x04, 0x00) }, + { CMD(0xa8, 0x08, 0x00) }, + { CMD(0xa9, 0x09, 0x00, 0x00) }, + { CMD(0xa8, 0x3e, 0x00, 0x00) }, + { CMD(0xa9, 0x03, 0x00, 0x00) }, + { CMD(0xa8, 0x20, 0x00, 0x00) }, + { CMD(0xa9, 0x10, 0x00, 0x01) }, + { CMD(0xa9, 0x2f, 0xef, 0x00) }, + { CMD(0xa9, 0x09, 0x00, 0x00) }, + { CMD(0xa9, 0x5d, 0x4d, 0x00) }, + { CMD(0xa9, 0x51, 0x3a, 0x25) }, + { CMD(0xa9, 0x0c, 0x00) }, + { CMD(0xa8, 0x20, 0x00, 0x00) }, + { CMD(0xa9, 0x04, 0x00, 0x00) }, + { CMD(0xa9, 0x09, 0x00, 0x00) }, }; -static const guint8 cmd0[] = { 0x4f, 0x80 }; -static const guint8 cmd1[] = { 0xa9, 0x4f, 0x80 }; -static const guint8 cmd2[] = { 0xa9, 0xb9, 0x00 }; -static const guint8 cmd3[] = { 0xa9, 0x60, 0x1b, 0x00 }; -static const guint8 cmd4[] = { 0xa9, 0x50, 0x21, 0x00 }; -static const guint8 cmd5[] = { 0xa9, 0x61, 0x00, 0x00 }; -static const guint8 cmd6[] = { 0xa9, 0x62, 0x00, 0x1a }; -static const guint8 cmd7[] = { 0xa9, 0x63, 0x00, 0x1a }; -static const guint8 cmd8[] = { 0xa9, 0x64, 0x04, 0x0a }; -static const guint8 cmd9[] = { 0xa9, 0x66, 0x0f, 0x80 }; -static const guint8 cmd10[] = { 0xa9, 0x67, 0x1b, 0x00 }; -static const guint8 cmd11[] = { 0xa9, 0x68, 0x00, 0x0f }; -static const guint8 cmd12[] = { 0xa9, 0x69, 0x00, 0x14 }; -static const guint8 cmd13[] = { 0xa9, 0x6a, 0x00, 0x19 }; -static const guint8 cmd14[] = { 0xa9, 0x6c, 0x00, 0x19 }; -static const guint8 cmd15[] = { 0xa9, 0x40, 0x43, 0x00 }; -static const guint8 cmd16[] = { 0xa9, 0x41, 0x6f, 0x00 }; -static const guint8 cmd17[] = { 0xa9, 0x55, 0x20, 0x00 }; -static const guint8 cmd18[] = { 0xa9, 0x5f, 0x00, 0x00 }; -static const guint8 cmd19[] = { 0xa9, 0x52, 0x27, 0x00 }; -static const guint8 cmd20[] = { 0xa9, 0x09, 0x00, 0x00 }; -static const guint8 cmd21[] = { 0xa9, 0x5d, 0x4d, 0x00 }; -static const guint8 cmd22[] = { 0xa9, 0x51, 0xa8, 0x25 }; -static const guint8 cmd23[] = { 0xa9, 0x03, 0x00 }; -static const guint8 cmd24[] = { 0xa9, 0x38, 0x01, 0x00 }; -static const guint8 cmd25[] = { 0xa9, 0x3d, 0xff, 0x0f }; -static const guint8 cmd26[] = { 0xa9, 0x10, 0x60, 0x00 }; -static const guint8 cmd27[] = { 0xa9, 0x3b, 0x14, 0x00 }; -static const guint8 cmd28[] = { 0xa9, 0x2f, 0xf6, 0xff }; -static const guint8 cmd29[] = { 0xa9, 0x09, 0x00, 0x00 }; -static const guint8 cmd30[] = { 0xa9, 0x0c, 0x00 }; -static const guint8 cmd31[] = { 0xa8, 0x20, 0x00, 0x00 }; -static const guint8 cmd32[] = { 0xa9, 0x04, 0x00 }; -static const guint8 cmd33[] = { 0xa8, 0x08, 0x00 }; -static const guint8 cmd34[] = { 0xa9, 0x09, 0x00, 0x00 }; -static const guint8 cmd35[] = { 0xa8, 0x3e, 0x00, 0x00 }; -static const guint8 cmd36[] = { 0xa9, 0x03, 0x00, 0x00 }; -static const guint8 cmd37[] = { 0xa8, 0x20, 0x00, 0x00 }; -static const guint8 cmd38[] = { 0xa9, 0x10, 0x00, 0x01 }; -static const guint8 cmd39[] = { 0xa9, 0x2f, 0xef, 0x00 }; -static const guint8 cmd40[] = { 0xa9, 0x09, 0x00, 0x00 }; -static const guint8 cmd41[] = { 0xa9, 0x5d, 0x4d, 0x00 }; -static const guint8 cmd42[] = { 0xa9, 0x51, 0x3a, 0x25 }; -static const guint8 cmd43[] = { 0xa9, 0x0c, 0x00 }; -static const guint8 cmd44[] = { 0xa8, 0x20, 0x00, 0x00 }; -static const guint8 cmd45[] = { 0xa9, 0x04, 0x00, 0x00 }; -static const guint8 cmd46[] = { 0xa9, 0x09, 0x00, 0x00 }; - -static const struct Samsung730bCmd samsung730b_init_cmds[] = { - { cmd0, sizeof (cmd0) }, - { cmd1, sizeof (cmd1) }, - { cmd2, sizeof (cmd2) }, - { cmd3, sizeof (cmd3) }, - { cmd4, sizeof (cmd4) }, - { cmd5, sizeof (cmd5) }, - { cmd6, sizeof (cmd6) }, - { cmd7, sizeof (cmd7) }, - { cmd8, sizeof (cmd8) }, - { cmd9, sizeof (cmd9) }, - { cmd10, sizeof (cmd10) }, - { cmd11, sizeof (cmd11) }, - { cmd12, sizeof (cmd12) }, - { cmd13, sizeof (cmd13) }, - { cmd14, sizeof (cmd14) }, - { cmd15, sizeof (cmd15) }, - { cmd16, sizeof (cmd16) }, - { cmd17, sizeof (cmd17) }, - { cmd18, sizeof (cmd18) }, - { cmd19, sizeof (cmd19) }, - { cmd20, sizeof (cmd20) }, - { cmd21, sizeof (cmd21) }, - { cmd22, sizeof (cmd22) }, - { cmd23, sizeof (cmd23) }, - { cmd24, sizeof (cmd24) }, - { cmd25, sizeof (cmd25) }, - { cmd26, sizeof (cmd26) }, - { cmd27, sizeof (cmd27) }, - { cmd28, sizeof (cmd28) }, - { cmd29, sizeof (cmd29) }, - { cmd30, sizeof (cmd30) }, - { cmd31, sizeof (cmd31) }, - { cmd32, sizeof (cmd32) }, - { cmd33, sizeof (cmd33) }, - { cmd34, sizeof (cmd34) }, - { cmd35, sizeof (cmd35) }, - { cmd36, sizeof (cmd36) }, - { cmd37, sizeof (cmd37) }, - { cmd38, sizeof (cmd38) }, - { cmd39, sizeof (cmd39) }, - { cmd40, sizeof (cmd40) }, - { cmd41, sizeof (cmd41) }, - { cmd42, sizeof (cmd42) }, - { cmd43, sizeof (cmd43) }, - { cmd44, sizeof (cmd44) }, - { cmd45, sizeof (cmd45) }, - { cmd46, sizeof (cmd46) }, +/* capture 인덱스: 파이썬 CAPTURE_START_INDEX/NUM_CHUNKS 기반 */ +static const guint16 capture_indices[CAPTURE_NUM_CHUNKS] = { + 0x032a, 0x042a, 0x052a, 0x062a, 0x072a, 0x082a, 0x092a, 0x0a2a, + 0x0b2a, 0x0c2a, 0x0d2a, 0x0e2a, 0x0f2a, 0x102a, 0x112a, 0x122a, + 0x132a, 0x142a, 0x152a, 0x162a, 0x172a, 0x182a, 0x192a, 0x1a2a, + 0x1b2a, 0x1c2a, 0x1d2a, 0x1e2a, 0x1f2a, 0x202a, 0x212a, 0x222a, + 0x232a, 0x242a, 0x252a, 0x262a, 0x272a, 0x282a, 0x292a, 0x2a2a, + 0x2b2a, 0x2c2a, 0x2d2a, 0x2e2a, 0x2f2a, 0x302a, 0x312a, 0x322a, + 0x332a, 0x342a, 0x352a, 0x362a, 0x372a, 0x382a, 0x392a, 0x3a2a, + 0x3b2a, 0x3c2a, 0x3d2a, 0x3e2a, 0x3f2a, 0x402a, 0x412a, 0x422a, + 0x432a, 0x442a, 0x452a, 0x462a, 0x472a, 0x482a, 0x492a, 0x4a2a, + 0x4b2a, 0x4c2a, 0x4d2a, 0x4e2a, 0x4f2a, 0x502a, 0x512a, 0x522a, + 0x532a, }; -static const gsize samsung730b_init_cmds_len = - G_N_ELEMENTS (samsung730b_init_cmds); - - -static const guint16 capture_indices[] = { - 0x032a, 0x042a, 0x052a, 0x062a, - 0x072a, 0x082a, 0x092a, 0x0a2a, - 0x0b2a, 0x0c2a, 0x0d2a, 0x0e2a, - 0x0f2a, 0x102a, 0x112a, 0x122a, - 0x132a, 0x142a, 0x152a, 0x162a, - 0x172a, 0x182a, 0x192a, 0x1a2a, - 0x1b2a, 0x1c2a, 0x1d2a, 0x1e2a, - 0x1f2a, 0x202a, 0x212a, 0x222a, - 0x232a, 0x242a, 0x252a, 0x262a, - 0x272a, 0x282a, 0x292a, 0x2a2a, - 0x2b2a, 0x2c2a, 0x2d2a, 0x2e2a, - 0x2f2a, 0x302a, 0x312a, 0x322a, - 0x332a, 0x342a, 0x352a, 0x362a, - 0x372a, 0x382a, 0x392a, 0x3a2a, - 0x3b2a, 0x3c2a, 0x3d2a, 0x3e2a, - 0x3f2a, 0x402a, 0x412a, 0x422a, - 0x432a, 0x442a, 0x452a, 0x462a, - 0x472a, 0x482a, 0x492a, 0x4a2a, - 0x4b2a, 0x4c2a, 0x4d2a, 0x4e2a, - 0x4f2a, 0x502a, 0x512a, 0x522a, - 0x532a, 0x542a, 0x552a, 0x562a, - 0x572a, -}; -static const size_t CAPTURE_NUM_CHUNKS = sizeof(capture_indices) / sizeof(capture_indices[0]); - - +/* ---------- 공용 USB 헬퍼 ---------- */ static gboolean -samsung730b_capture_frame (FpImageDevice *dev, - guint8 **out_buf, - gsize *out_len, - GError **error) +usb_ctrl_out (GUsbDevice *usb_dev, + guint8 req, + guint16 val, + guint16 idx, + const guint8 *data, + gsize len, + guint timeout_ms, + GError **error) { - GUsbDevice *usb_dev; - guint8 *buf; - gsize total_len = 0; - gsize capacity = (gsize) CAPTURE_NUM_CHUNKS * BULK_PACKET_SIZE + 1024; - gsize actual_length; - gboolean ok; - gsize i; + gsize actual = 0; - guint16 windex0; - guint16 windex; - - usb_dev = fpi_device_get_usb_device (FP_DEVICE (dev)); - - buf = g_malloc (capacity); - if (!buf) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, - "samsung730b: failed to allocate capture buffer"); - return FALSE; - } - - /* ---------- 1) 첫 chunk: 상태 응답만 처리 (데이터 누적 X) ---------- */ - windex0 = capture_indices[0]; - - ok = g_usb_device_control_transfer (usb_dev, - G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, - G_USB_DEVICE_REQUEST_TYPE_VENDOR, - G_USB_DEVICE_RECIPIENT_DEVICE, - 0xCA, /* request */ - 0x0003, /* value */ - windex0, /* idx */ - NULL, /* data */ - 0, /* length */ - &actual_length, - 500, - NULL, - error); - if (!ok) - goto out_fail; - - /* 캡처 시작 명령 (a8 06 00 00...) */ - { - guint8 start_cmd[256] = { 0 }; - - start_cmd[0] = 0xa8; - start_cmd[1] = 0x06; - start_cmd[2] = 0x00; - start_cmd[3] = 0x00; - - ok = g_usb_device_bulk_transfer (usb_dev, - SAMSUNG730B_BULK_EP_OUT, - start_cmd, - sizeof (start_cmd), - &actual_length, - 500, - NULL, - error); - if (!ok) - goto out_fail; - } - - /* 첫 IN: 짧은 상태 응답만 읽고 버림 */ - { - guint8 tmp0[256]; - - ok = g_usb_device_bulk_transfer (usb_dev, - SAMSUNG730B_BULK_EP_IN, - tmp0, - sizeof (tmp0), - &actual_length, - 500, - NULL, - error); - if (!ok) - goto out_fail; - } - - /* ---------- 2) 나머지 chunk: 실제 데이터 + ACK ---------- */ - for (i = 1; i < CAPTURE_NUM_CHUNKS; i++) - { - guint8 tmp[256]; - guint8 ack[256] = { 0 }; - - windex = capture_indices[i]; - - /* CONTROL 0xCA: 청크 설정 */ - ok = g_usb_device_control_transfer (usb_dev, - G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, - G_USB_DEVICE_REQUEST_TYPE_VENDOR, - G_USB_DEVICE_RECIPIENT_DEVICE, - 0xCA, - 0x0003, - windex, - NULL, - 0, - &actual_length, - 500, - NULL, - error); - if (!ok) - goto out_fail; - - /* 데이터 IN (256 bytes 기대) */ - ok = g_usb_device_bulk_transfer (usb_dev, - SAMSUNG730B_BULK_EP_IN, - tmp, - sizeof (tmp), - &actual_length, - 1000, - NULL, - error); - if (!ok) - goto out_fail; - - if (actual_length == 0) - break; - - if (total_len + actual_length > capacity) - { - capacity *= 2; - buf = g_realloc (buf, capacity); - if (!buf) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, - "samsung730b: realloc failed"); - return FALSE; - } - } - - memcpy (buf + total_len, tmp, actual_length); - total_len += actual_length; - - /* ACK (256 zeros) */ - ok = g_usb_device_bulk_transfer (usb_dev, - SAMSUNG730B_BULK_EP_OUT, - ack, - sizeof (ack), - &actual_length, - 500, - NULL, - error); - if (!ok) - goto out_fail; - } - - *out_buf = buf; - *out_len = total_len; - return TRUE; - -out_fail: - g_free (buf); - *out_buf = NULL; - *out_len = 0; - return FALSE; + return g_usb_device_control_transfer (usb_dev, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + req, val, idx, + (guchar *) data, len, + &actual, + timeout_ms, + NULL, + error); } -#include -static void -samsung730b_do_capture_and_report (FpImageDevice *dev) +static gboolean +usb_bulk_out (GUsbDevice *usb_dev, + guint8 ep, + const guint8 *data, + gsize len, + guint timeout_ms, + GError **error) { - guint8 *raw = NULL; - gsize raw_len = 0; - GError *error = NULL; - FpImage *image = NULL; - gsize needed; + gsize actual = 0; - needed = SAMSUNG730B_DATA_OFFSET + - (gsize) SAMSUNG730B_IMG_WIDTH * SAMSUNG730B_IMG_HEIGHT; - - if (!samsung730b_capture_frame (dev, &raw, &raw_len, &error)) - goto out_error; - - if (raw_len < needed) - { - g_clear_error (&error); - g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, - "samsung730b: captured data too short (%"G_GSIZE_FORMAT")", raw_len); - goto out_error; - } - - image = fp_image_new (SAMSUNG730B_IMG_WIDTH, SAMSUNG730B_IMG_HEIGHT); - - { - gsize img_size; - guchar *data = (guchar *) fp_image_get_data (image, &img_size); - - if (img_size < SAMSUNG730B_IMG_WIDTH * SAMSUNG730B_IMG_HEIGHT) - { - g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, - "samsung730b: image buffer too small (%"G_GSIZE_FORMAT")", img_size); - goto out_error; - } - - memcpy (data, - raw + SAMSUNG730B_DATA_OFFSET, - SAMSUNG730B_IMG_WIDTH * SAMSUNG730B_IMG_HEIGHT); - } - - g_free (raw); - raw = NULL; - - /* 디버그용 덤프 (원하면 유지) */ - g_message ("samsung730b: dumping captured image to /tmp/s730b.pgm"); - - { - FILE *f = fopen ("/tmp/s730b.pgm", "wb"); - if (f) - { - const guchar *data = fp_image_get_data (image, NULL); - - fprintf (f, "P5\n%d %d\n255\n", - SAMSUNG730B_IMG_WIDTH, SAMSUNG730B_IMG_HEIGHT); - fwrite (data, 1, - SAMSUNG730B_IMG_WIDTH * SAMSUNG730B_IMG_HEIGHT, - f); - fclose (f); - } - else - { - g_message ("samsung730b: failed to open /tmp/s730b.pgm for writing"); - } - } -g_object_unref (image); - -out_error: - if (raw) - g_free (raw); - if (image) - g_object_unref (image); - - if (error) - { - g_warning ("samsung730b: capture error: %s", error->message); - g_clear_error (&error); - } + return g_usb_device_bulk_transfer (usb_dev, + ep, + (guchar *) data, + len, + &actual, + timeout_ms, + NULL, + error); } - - -static void -samsung730b_dev_init (FpImageDevice *dev) +static gboolean +usb_bulk_in (GUsbDevice *usb_dev, + guint8 ep, + guint8 *buf, + gsize len, + guint timeout_ms, + gsize *actual_out, + GError **error) +{ + return g_usb_device_bulk_transfer (usb_dev, + ep, + buf, + len, + actual_out, + timeout_ms, + NULL, + error); +} + +/* ---------- 센서 init ---------- */ + +static gboolean +s730b_run_init (GUsbDevice *usb_dev, GError **error) { - GUsbDevice *usb_dev; - GError *error = NULL; guint8 c3_data[16] = { 0x80, 0x84, 0x1e, 0x00, 0x08, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; - guint8 iface = 0; /* 테스트 코드에서 claim한 인터페이스 번호 (보통 0) */ - gboolean ok; - gsize actual_length; gsize i; - usb_dev = fpi_device_get_usb_device (FP_DEVICE (dev)); + if (!usb_ctrl_out (usb_dev, 0xC3, 0x0000, 0x0000, + c3_data, sizeof (c3_data), + 500, error)) + return FALSE; - /* 1. USB 인터페이스 claim */ - if (!g_usb_device_claim_interface (usb_dev, iface, 0, &error)) + for (i = 0; i < G_N_ELEMENTS (init_cmds); i++) { - fpi_image_device_open_complete (dev, error); - return; + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, + init_cmds[i].data, init_cmds[i].len, + 500, error)) + return FALSE; } - /* 2. control 0xC3 전송 (libusb_control_transfer 대체) */ - ok = g_usb_device_control_transfer (usb_dev, - G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, - G_USB_DEVICE_REQUEST_TYPE_VENDOR, - G_USB_DEVICE_RECIPIENT_DEVICE, - 0xC3, /* request */ - 0x0000, /* value */ - 0x0000, /* idx */ - c3_data, /* data */ - sizeof (c3_data), /* length */ - &actual_length, /* actual_length */ - 500, /* timeout ms */ - NULL, /* cancellable */ - &error); /* error */ - if (!ok || actual_length != sizeof (c3_data)) + return TRUE; +} + +/* ---------- finger detect용 probe 캡처 ---------- */ + +static guint8 * +s730b_detect_probe (GUsbDevice *usb_dev, gsize *out_len, GError **error) +{ + guint8 *buf = NULL; + gsize capacity = DETECT_PROBE_CHUNKS * BULK_PACKET_SIZE + 64; + gsize total = 0; + guint8 start_cmd[BULK_PACKET_SIZE] = { 0 }; + guint i; + + buf = g_malloc0 (capacity); + if (!buf) { - if (!error) - error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, - "samsung730b: control 0xC3 transfer failed " - "(actual_length=%"G_GSIZE_FORMAT")", - actual_length); - fpi_image_device_open_complete (dev, error); - return; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + "s730b: detect probe alloc fail"); + return NULL; } - /* 3. init 명령들 bulk OUT 전송 (libusb_bulk_transfer 대체) */ - for (i = 0; i < samsung730b_init_cmds_len; i++) - { - const struct Samsung730bCmd *cmd = &samsung730b_init_cmds[i]; + g_message ("s730b: detect_probe: start"); + /* chunk 0: control 0xCA + start_cmd */ + if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, + CAPTURE_START_IDX, NULL, 0, + DETECT_INIT_TIMEOUT_MS, error)) + { + g_message ("s730b: detect_probe: ctrl_out failed"); + goto fail; + } - ok = g_usb_device_bulk_transfer (usb_dev, - SAMSUNG730B_BULK_EP_OUT, - (guint8 *) cmd->data, - cmd->len, - &actual_length, - 500, - NULL, /* cancellable */ - &error); /* error */ - if (!ok || actual_length != cmd->len) + start_cmd[0] = 0xa8; + start_cmd[1] = 0x06; + + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, + start_cmd, sizeof (start_cmd), + DETECT_INIT_TIMEOUT_MS, error)) + { + g_message ("s730b: detect_probe: bulk_out(start_cmd) failed"); + goto fail; + } + + /* 초기 상태 응답 */ + { + guint8 tmp[BULK_PACKET_SIZE]; + gsize got = 0; + + if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, + tmp, sizeof (tmp), + DETECT_INIT_TIMEOUT_MS, &got, error)) + { + g_message ("s730b: detect_probe: first bulk_in failed"); + goto fail; + } + g_message ("s730b: detect_probe: first bulk_in got=%"G_GSIZE_FORMAT, got); + + if (got > 0) + { + memcpy (buf + total, tmp, got); + total += got; + } + } + + /* chunk 1..N: 일부만 읽기 */ + for (i = 1; i < DETECT_PROBE_CHUNKS; i++) + { + guint8 tmp[BULK_PACKET_SIZE]; + guint8 ack[BULK_PACKET_SIZE] = { 0 }; + gsize got = 0; + guint16 widx = CAPTURE_START_IDX + i * BULK_PACKET_SIZE; + + if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, + widx, NULL, 0, + DETECT_INIT_TIMEOUT_MS, error)) + { + g_message ("s730b: detect_probe: ctrl_out chunk %u failed", i); + goto fail; + } + + if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, + tmp, sizeof (tmp), + DETECT_BULK_TIMEOUT_MS, &got, error)) + { + g_message ("s730b: detect_probe: bulk_in chunk %u failed", i); + goto fail; + } + g_message ("s730b: detect_probe: bulk_in chunk %u got=%"G_GSIZE_FORMAT, i, got); + + if (got > 0) { - if (!error) - error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, - "samsung730b: init bulk transfer failed " - "idx=%"G_GSIZE_FORMAT" actual_length=%"G_GSIZE_FORMAT"/%"G_GSIZE_FORMAT, - i, actual_length, cmd->len); - fpi_image_device_open_complete (dev, error); - return; + memcpy (buf + total, tmp, got); + total += got; + } + + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, + ack, sizeof (ack), + DETECT_INIT_TIMEOUT_MS, error)) + { + g_message ("s730b: detect_probe: ack out chunk %u failed", i); + goto fail; + } + } + g_message ("s730b: detect_probe: done total=%"G_GSIZE_FORMAT, total); + + *out_len = total; + return buf; + +fail: + g_message ("s730b: detect_probe: fail"); + g_free (buf); + *out_len = 0; + return NULL; +} + +/* ---------- ff 비율 기반 finger 존재 여부 ---------- */ + +static gboolean +s730b_has_finger (const guint8 *data, gsize len) +{ + if (!data || len < 512) + return FALSE; + + gsize total = MIN (len, (gsize) 4096); + gsize zeros = 0, ff = 0; + + for (gsize i = 0; i < total; i++) + { + guint8 v = data[i]; + if (v == 0x00) + zeros++; + else if (v == 0xFF) + ff++; + } + + double ff_ratio = (double) ff / (double) total; + + /* 파이썬 `_has_finger_in_detect`와 동일한 조건: + * - ff_ratio > 0.3 + * - zeros가 전체의 95% 미만 */ + if (ff_ratio > 0.30 && zeros < total * 0.95) + return TRUE; + + return FALSE; +} + +/* ---------- finger detect 루프 ---------- */ + +static gboolean +s730b_wait_finger (GUsbDevice *usb_dev) +{ + GError *local_error = NULL; + + for (guint r = 0; r < DETECT_MAX_ROUNDS; r++) + { + g_message ("s730b: detect round %u/%u", r + 1, DETECT_MAX_ROUNDS); + + for (guint i = 0; i < DETECT_PROBES_PER_ROUND; i++) + { + gsize probe_len = 0; + guint8 *probe; + + local_error = NULL; + probe = s730b_detect_probe (usb_dev, &probe_len, &local_error); + + if (!probe) + { + /* USB 에러/타임아웃은 soft-fail로 처리 */ + if (local_error) + { + g_message ("s730b: detect probe error (round=%u, probe=%u): %s", + r, i, local_error->message); + g_clear_error (&local_error); + } + /* 이 probe는 실패, 다음 probe로 넘어감 */ + g_usleep ((gulong) DETECT_INTERVAL_MS * 1000); + continue; + } + + gboolean finger = s730b_has_finger (probe, probe_len); + g_free (probe); + + g_message ("s730b: probe %u: finger=%d", i, finger); + if (finger) + return TRUE; + + g_usleep ((gulong) DETECT_INTERVAL_MS * 1000); + } + + /* 라운드 실패 시 센서 재초기화도 soft하게 처리 */ + g_message ("s730b: re-init after failed detect round"); + local_error = NULL; + if (!s730b_run_init (usb_dev, &local_error)) + { + if (local_error) + { + g_message ("s730b: re-init failed after detect round: %s", + local_error->message); + g_clear_error (&local_error); + } + /* init 실패해도 여기선 그냥 detect 실패로 간주 */ + break; } } - /* 4. 성공 */ + g_message ("s730b: finger detect timeout (no finger)"); + return FALSE; +} + +/* ---------- full frame 캡처 ---------- */ + +static gboolean +s730b_capture_frame (GUsbDevice *usb_dev, + guint8 **out_buf, + gsize *out_len, + GError **error) +{ + guint8 *buf = NULL; + gsize capacity = (gsize) CAPTURE_NUM_CHUNKS * BULK_PACKET_SIZE + 1024; + gsize total = 0; + + buf = g_malloc (capacity); + if (!buf) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + "s730b: capture alloc fail"); + return FALSE; + } + + /* chunk 0: control 0xCA + start_cmd */ + { + guint8 start_cmd[BULK_PACKET_SIZE] = { 0 }; + start_cmd[0] = 0xa8; + start_cmd[1] = 0x06; + + if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, + capture_indices[0], NULL, 0, + 500, error)) + goto fail; + + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, + start_cmd, sizeof (start_cmd), + 500, error)) + goto fail; + + guint8 tmp[BULK_PACKET_SIZE]; + gsize got = 0; + + if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, + tmp, sizeof (tmp), + 500, &got, error)) + goto fail; + + /* 파이썬처럼 chunk0 데이터는 버림 (상태 응답) */ + } + + /* chunk 1..N : 실제 이미지 데이터 + ACK */ + for (gsize i = 1; i < CAPTURE_NUM_CHUNKS; i++) + { + guint8 tmp[BULK_PACKET_SIZE]; + guint8 ack[BULK_PACKET_SIZE] = { 0 }; + gsize got = 0; + guint16 widx = capture_indices[i]; + + if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, + widx, NULL, 0, + 500, error)) + goto fail; + + if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, + tmp, sizeof (tmp), + 1000, &got, error)) + goto fail; + + if (got == 0) + break; + + if (total + got > capacity) + { + capacity *= 2; + guint8 *tmp_buf = g_realloc (buf, capacity); + if (!tmp_buf) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + "s730b: capture realloc fail"); + goto fail; + } + buf = tmp_buf; + } + + memcpy (buf + total, tmp, got); + total += got; + + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, + ack, sizeof (ack), + 500, error)) + goto fail; + } + + *out_buf = buf; + *out_len = total; + return TRUE; + +fail: + g_free (buf); + *out_buf = NULL; + *out_len = 0; + return FALSE; +} + +/* ---------- 이미지로 변환 + report ---------- */ + +static void +s730b_emit_image (FpImageDevice *dev, guint8 *raw, gsize raw_len) +{ + gsize needed = IMG_OFFSET + (gsize) IMG_W * IMG_H; + + if (raw_len < needed) + { + g_warning ("s730b: captured too short (%"G_GSIZE_FORMAT")", raw_len); + g_free (raw); + + fpi_image_device_report_finger_status (dev, FALSE); + fpi_image_device_image_captured (dev, NULL); + return; + } + + FpImage *img = fp_image_new (IMG_W, IMG_H); + gsize img_size = 0; + guchar *dst = (guchar *) fp_image_get_data (img, &img_size); + + if (img_size < (gsize) IMG_W * IMG_H) + { + g_warning ("s730b: image buffer too small (%"G_GSIZE_FORMAT")", img_size); + g_free (raw); + g_object_unref (img); + + fpi_image_device_report_finger_status (dev, FALSE); + fpi_image_device_image_captured (dev, NULL); + return; + } + + memcpy (dst, raw + IMG_OFFSET, IMG_W * IMG_H); + g_free (raw); + + fpi_image_device_report_finger_status (dev, TRUE); + fpi_image_device_image_captured (dev, img); + g_object_unref (img); +} + +/* ---------- libfprint 콜백들 ---------- */ + +static void +samsung730b_dev_init (FpImageDevice *dev) +{ + GUsbDevice *usb = fpi_device_get_usb_device (FP_DEVICE (dev)); + GError *error = NULL; + + if (!g_usb_device_claim_interface (usb, 0, 0, &error)) + { + fpi_image_device_open_complete (dev, error); + return; + } + + if (!s730b_run_init (usb, &error)) + { + fpi_image_device_open_complete (dev, error); + return; + } + fpi_image_device_open_complete (dev, NULL); } static void samsung730b_dev_deinit (FpImageDevice *dev) { - GUsbDevice *usb_dev; + GUsbDevice *usb = fpi_device_get_usb_device (FP_DEVICE (dev)); GError *error = NULL; - guint8 iface = 0; - usb_dev = fpi_device_get_usb_device (FP_DEVICE (dev)); - - if (!g_usb_device_release_interface (usb_dev, iface, 0, &error)) - { - fpi_image_device_close_complete (dev, error); - return; - } - - fpi_image_device_close_complete (dev, NULL); + g_usb_device_release_interface (usb, 0, 0, &error); + fpi_image_device_close_complete (dev, error); } static void samsung730b_dev_activate (FpImageDevice *dev) { - /* 장치 준비 완료 (CAPTURE state 진입) */ + GUsbDevice *usb = fpi_device_get_usb_device (FP_DEVICE (dev)); + GError *error = NULL; + guint8 *raw = NULL; + gsize raw_len = 0; + + /* 1) finger detect (이미지 기반) */ + if (!s730b_wait_finger (usb)) + { + /* 손가락 없음: 에러로 치지 않고 조용히 끝냄 */ + fpi_image_device_report_finger_status (dev, FALSE); + fpi_image_device_activate_complete (dev, NULL); + return; + } + + /* 2) detect 동안 센서 상태가 바뀌었을 수 있으니 init 재실행 */ + if (!s730b_run_init (usb, &error)) + { + fpi_image_device_activate_complete (dev, error); + return; + } + + /* 3) full capture */ + if (!s730b_capture_frame (usb, &raw, &raw_len, &error)) + { + fpi_image_device_activate_complete (dev, error); + return; + } + + /* activate 완료 알리고 이미지 전달 */ fpi_image_device_activate_complete (dev, NULL); - - /* 한 장 캡처해서 image_captured로 보고 */ - samsung730b_do_capture_and_report (dev); - - /* 단발 캡처 기기라서, 추가로 루프 안 돌고 바로 반환함 */ + s730b_emit_image (dev, raw, raw_len); } static void @@ -528,19 +631,15 @@ samsung730b_dev_deactivate (FpImageDevice *dev) fpi_image_device_deactivate_complete (dev, NULL); } -static const FpIdEntry samsung730b_id_table[] = { - { - .pid = SAMSUNG730B_PID, - .vid = SAMSUNG730B_VID, - .driver_data = 0, - }, - { - .pid = 0, - .vid = 0, - .driver_data = 0, - }, +/* ---------- ID 테이블 ---------- */ + +static const FpIdEntry samsung730b_ids[] = { + { .vid = SAMSUNG730B_VID, .pid = SAMSUNG730B_PID, .driver_data = 0 }, + { .vid = 0, .pid = 0, .driver_data = 0 }, }; +/* ---------- GObject boilerplate ---------- */ + static void fpi_device_samsung730b_init (FpiDeviceSamsung730b *self) { @@ -554,8 +653,8 @@ fpi_device_samsung730b_class_init (FpiDeviceSamsung730bClass *klass) dev_class->id = "samsung730b"; dev_class->full_name = "Samsung 730B (experimental)"; + dev_class->id_table = samsung730b_ids; dev_class->type = FP_DEVICE_TYPE_USB; - dev_class->id_table = samsung730b_id_table; dev_class->scan_type = FP_SCAN_TYPE_PRESS; img_class->img_open = samsung730b_dev_init; @@ -563,7 +662,7 @@ fpi_device_samsung730b_class_init (FpiDeviceSamsung730bClass *klass) img_class->activate = samsung730b_dev_activate; img_class->deactivate = samsung730b_dev_deactivate; + img_class->img_width = IMG_W; + img_class->img_height = IMG_H; img_class->bz3_threshold = 20; - img_class->img_width = -1; - img_class->img_height = -1; } \ No newline at end of file From 4668fbc591d52dc73a932df33e103790aa129ec4 Mon Sep 17 00:00:00 2001 From: lignah Date: Fri, 12 Dec 2025 04:16:40 +0900 Subject: [PATCH 4/4] samsung730b: translate comments to English and code update --- libfprint/drivers/samsung730b.c | 644 +++++++++++++++++--------------- 1 file changed, 352 insertions(+), 292 deletions(-) diff --git a/libfprint/drivers/samsung730b.c b/libfprint/drivers/samsung730b.c index 319a7d74..39f7a216 100644 --- a/libfprint/drivers/samsung730b.c +++ b/libfprint/drivers/samsung730b.c @@ -1,3 +1,24 @@ +/* + * Samsung 730B driver for libfprint + * 730B is a press-typed sensor, which captures image in 112x96 + * + * Copyright (C) 2025 Jang Han-gil + * + * 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 + */ + #define FP_COMPONENT "samsung730b" #include "drivers_api.h" @@ -13,34 +34,35 @@ #define IMG_H 96 #define IMG_OFFSET 180 -/* detect 파라미터 (파이썬 v1.3 기준) */ -#define DETECT_MAX_ROUNDS 5 -#define DETECT_PROBES_PER_ROUND 5 -#define DETECT_PROBE_CHUNKS 6 +/* Detection parameters */ +#define DETECT_MAX_LOOPS 5 +#define DETECT_PER_LOOP 5 +#define DETECT_PACKETS 6 #define DETECT_INTERVAL_MS 400 #define DETECT_INIT_TIMEOUT_MS 500 #define DETECT_BULK_TIMEOUT_MS 700 -/* capture 파라미터 */ -#define CAPTURE_NUM_CHUNKS 85 +/* Capture parameters (Vendor Protocol) */ #define CAPTURE_START_IDX 0x032a #define CAPTURE_CTRL_REQ 0xCA #define CAPTURE_CTRL_VAL 0x0003 -typedef struct _FpiDeviceSamsung730b -{ - FpImageDevice parent; -} FpiDeviceSamsung730b; - G_DECLARE_FINAL_TYPE (FpiDeviceSamsung730b, fpi_device_samsung730b, FPI, DEVICE_SAMSUNG730B, FpImageDevice); + +struct _FpiDeviceSamsung730b +{ + FpImageDevice parent; + GCancellable *cancellable; +}; + G_DEFINE_TYPE (FpiDeviceSamsung730b, fpi_device_samsung730b, FP_TYPE_IMAGE_DEVICE); -/* ---------- init 시퀀스 ---------- */ +/* ---------- Initialization Sequence ---------- */ typedef struct { @@ -50,10 +72,11 @@ typedef struct #define CMD(...) (const guint8[]){ __VA_ARGS__ }, sizeof((const guint8[]){ __VA_ARGS__ }) +/* initialization sequence */ static const SamsungCmd init_cmds[] = { { CMD(0x4f, 0x80) }, { CMD(0xa9, 0x4f, 0x80) }, - { CMD(0xa9, 0xb9, 0x00) }, + { CMD(0xa8, 0xb9, 0x00) }, { CMD(0xa9, 0x60, 0x1b, 0x00) }, { CMD(0xa9, 0x50, 0x21, 0x00) }, { CMD(0xa9, 0x61, 0x00, 0x00) }, @@ -100,8 +123,12 @@ static const SamsungCmd init_cmds[] = { { CMD(0xa9, 0x09, 0x00, 0x00) }, }; -/* capture 인덱스: 파이썬 CAPTURE_START_INDEX/NUM_CHUNKS 기반 */ -static const guint16 capture_indices[CAPTURE_NUM_CHUNKS] = { +/* + * Sequence of wIndex values used to request image chunks from the sensor. + * The driver iterates through these indices to read the full image frame + * in 256-byte blocks. The values were derived from USB traffic analysis. + */ +static const guint16 capture_indices[] = { 0x032a, 0x042a, 0x052a, 0x062a, 0x072a, 0x082a, 0x092a, 0x0a2a, 0x0b2a, 0x0c2a, 0x0d2a, 0x0e2a, 0x0f2a, 0x102a, 0x112a, 0x122a, 0x132a, 0x142a, 0x152a, 0x162a, 0x172a, 0x182a, 0x192a, 0x1a2a, @@ -112,110 +139,73 @@ static const guint16 capture_indices[CAPTURE_NUM_CHUNKS] = { 0x3b2a, 0x3c2a, 0x3d2a, 0x3e2a, 0x3f2a, 0x402a, 0x412a, 0x422a, 0x432a, 0x442a, 0x452a, 0x462a, 0x472a, 0x482a, 0x492a, 0x4a2a, 0x4b2a, 0x4c2a, 0x4d2a, 0x4e2a, 0x4f2a, 0x502a, 0x512a, 0x522a, - 0x532a, + 0x532a, 0x542a, 0x552a, 0x562a, 0x572a, }; +#define CAPTURE_NUM_PACKETS (G_N_ELEMENTS (capture_indices)) -/* ---------- 공용 USB 헬퍼 ---------- */ +/* ---------- USB Helpers ---------- */ static gboolean -usb_ctrl_out (GUsbDevice *usb_dev, - guint8 req, - guint16 val, - guint16 idx, - const guint8 *data, - gsize len, - guint timeout_ms, - GError **error) +usb_ctrl_out (GUsbDevice *usb_dev, guint8 req, guint16 val, guint16 idx, + const guint8 *data, gsize len, guint timeout_ms, GError **error) { gsize actual = 0; - return g_usb_device_control_transfer (usb_dev, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, - req, val, idx, - (guchar *) data, len, - &actual, - timeout_ms, - NULL, - error); + req, val, idx, (guchar *) data, len, + &actual, timeout_ms, NULL, error); } static gboolean -usb_bulk_out (GUsbDevice *usb_dev, - guint8 ep, - const guint8 *data, - gsize len, - guint timeout_ms, - GError **error) +usb_bulk_out (GUsbDevice *usb_dev, guint8 ep, const guint8 *data, gsize len, + guint timeout_ms, GError **error) { gsize actual = 0; - - return g_usb_device_bulk_transfer (usb_dev, - ep, - (guchar *) data, - len, - &actual, - timeout_ms, - NULL, - error); + return g_usb_device_bulk_transfer (usb_dev, ep, (guchar *) data, len, + &actual, timeout_ms, NULL, error); } static gboolean -usb_bulk_in (GUsbDevice *usb_dev, - guint8 ep, - guint8 *buf, - gsize len, - guint timeout_ms, - gsize *actual_out, - GError **error) +usb_bulk_in (GUsbDevice *usb_dev, guint8 ep, guint8 *buf, gsize len, + guint timeout_ms, gsize *actual_out, GError **error) { - return g_usb_device_bulk_transfer (usb_dev, - ep, - buf, - len, - actual_out, - timeout_ms, - NULL, - error); + return g_usb_device_bulk_transfer (usb_dev, ep, buf, len, + actual_out, timeout_ms, NULL, error); } -/* ---------- 센서 init ---------- */ +/* ---------- Device Logic ---------- */ +/* + * Resets and initializes the sensor. + * This sequence seems to be required before every capture or detection loop. + */ static gboolean -s730b_run_init (GUsbDevice *usb_dev, GError **error) +s730b_init (GUsbDevice *usb_dev, GError **error) { guint8 c3_data[16] = { - 0x80, 0x84, 0x1e, 0x00, - 0x08, 0x00, 0x00, 0x01, - 0x01, 0x01, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00 + 0x80, 0x84, 0x1e, 0x00, 0x08, 0x00, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; gsize i; - if (!usb_ctrl_out (usb_dev, 0xC3, 0x0000, 0x0000, - c3_data, sizeof (c3_data), - 500, error)) + if (!usb_ctrl_out (usb_dev, 0xC3, 0x0000, 0x0000, c3_data, sizeof (c3_data), 500, error)) return FALSE; for (i = 0; i < G_N_ELEMENTS (init_cmds); i++) { - if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, - init_cmds[i].data, init_cmds[i].len, - 500, error)) + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, init_cmds[i].data, init_cmds[i].len, 500, error)) return FALSE; } - return TRUE; } -/* ---------- finger detect용 probe 캡처 ---------- */ - static guint8 * -s730b_detect_probe (GUsbDevice *usb_dev, gsize *out_len, GError **error) +s730b_detect_finger (GUsbDevice *usb_dev, gsize *out_len, GError **error) { guint8 *buf = NULL; - gsize capacity = DETECT_PROBE_CHUNKS * BULK_PACKET_SIZE + 64; + gsize capacity = DETECT_PACKETS * BULK_PACKET_SIZE + 64; gsize total = 0; guint8 start_cmd[BULK_PACKET_SIZE] = { 0 }; guint i; @@ -223,46 +213,24 @@ s730b_detect_probe (GUsbDevice *usb_dev, gsize *out_len, GError **error) buf = g_malloc0 (capacity); if (!buf) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, - "s730b: detect probe alloc fail"); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, "alloc fail"); return NULL; } - g_message ("s730b: detect_probe: start"); - /* chunk 0: control 0xCA + start_cmd */ - if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, - CAPTURE_START_IDX, NULL, 0, - DETECT_INIT_TIMEOUT_MS, error)) - { - g_message ("s730b: detect_probe: ctrl_out failed"); + /* packet 0 */ + if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, CAPTURE_START_IDX, NULL, 0, DETECT_INIT_TIMEOUT_MS, error)) goto fail; - } start_cmd[0] = 0xa8; start_cmd[1] = 0x06; - - if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, - start_cmd, sizeof (start_cmd), - DETECT_INIT_TIMEOUT_MS, error)) - { - g_message ("s730b: detect_probe: bulk_out(start_cmd) failed"); + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, start_cmd, sizeof (start_cmd), DETECT_INIT_TIMEOUT_MS, error)) goto fail; - } - /* 초기 상태 응답 */ { guint8 tmp[BULK_PACKET_SIZE]; gsize got = 0; - - if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, - tmp, sizeof (tmp), - DETECT_INIT_TIMEOUT_MS, &got, error)) - { - g_message ("s730b: detect_probe: first bulk_in failed"); + if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, tmp, sizeof (tmp), DETECT_INIT_TIMEOUT_MS, &got, error)) goto fail; - } - g_message ("s730b: detect_probe: first bulk_in got=%"G_GSIZE_FORMAT, got); - if (got > 0) { memcpy (buf + total, tmp, got); @@ -270,61 +238,38 @@ s730b_detect_probe (GUsbDevice *usb_dev, gsize *out_len, GError **error) } } - /* chunk 1..N: 일부만 읽기 */ - for (i = 1; i < DETECT_PROBE_CHUNKS; i++) + /* packet 1..N: Read remaining chunks */ + for (i = 1; i < DETECT_PACKETS; i++) { guint8 tmp[BULK_PACKET_SIZE]; guint8 ack[BULK_PACKET_SIZE] = { 0 }; gsize got = 0; guint16 widx = CAPTURE_START_IDX + i * BULK_PACKET_SIZE; - if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, - widx, NULL, 0, - DETECT_INIT_TIMEOUT_MS, error)) - { - g_message ("s730b: detect_probe: ctrl_out chunk %u failed", i); + if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, widx, NULL, 0, DETECT_INIT_TIMEOUT_MS, error)) goto fail; - } - - if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, - tmp, sizeof (tmp), - DETECT_BULK_TIMEOUT_MS, &got, error)) - { - g_message ("s730b: detect_probe: bulk_in chunk %u failed", i); + if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, tmp, sizeof (tmp), DETECT_BULK_TIMEOUT_MS, &got, error)) goto fail; - } - g_message ("s730b: detect_probe: bulk_in chunk %u got=%"G_GSIZE_FORMAT, i, got); - if (got > 0) { memcpy (buf + total, tmp, got); total += got; } - - if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, - ack, sizeof (ack), - DETECT_INIT_TIMEOUT_MS, error)) - { - g_message ("s730b: detect_probe: ack out chunk %u failed", i); + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, ack, sizeof (ack), DETECT_INIT_TIMEOUT_MS, error)) goto fail; - } } - g_message ("s730b: detect_probe: done total=%"G_GSIZE_FORMAT, total); *out_len = total; return buf; fail: - g_message ("s730b: detect_probe: fail"); g_free (buf); *out_len = 0; return NULL; } -/* ---------- ff 비율 기반 finger 존재 여부 ---------- */ - static gboolean -s730b_has_finger (const guint8 *data, gsize len) +s730b_has_fingerprint (const guint8 *data, gsize len) { if (!data || len < 512) return FALSE; @@ -335,174 +280,160 @@ s730b_has_finger (const guint8 *data, gsize len) for (gsize i = 0; i < total; i++) { guint8 v = data[i]; - if (v == 0x00) - zeros++; - else if (v == 0xFF) - ff++; + if (v == 0x00) zeros++; + else if (v == 0xFF) ff++; } double ff_ratio = (double) ff / (double) total; - - /* 파이썬 `_has_finger_in_detect`와 동일한 조건: - * - ff_ratio > 0.3 - * - zeros가 전체의 95% 미만 */ if (ff_ratio > 0.30 && zeros < total * 0.95) return TRUE; return FALSE; } -/* ---------- finger detect 루프 ---------- */ - +/* Wait for finger ON */ static gboolean -s730b_wait_finger (GUsbDevice *usb_dev) +s730b_wait_finger (GUsbDevice *usb_dev, GCancellable *cancellable) { GError *local_error = NULL; - for (guint r = 0; r < DETECT_MAX_ROUNDS; r++) + for (guint r = 0; r < DETECT_MAX_LOOPS; r++) { - g_message ("s730b: detect round %u/%u", r + 1, DETECT_MAX_ROUNDS); + if (g_cancellable_is_cancelled (cancellable)) return FALSE; + g_message ("s730b: wait_finger loop %u/%u", r + 1, DETECT_MAX_LOOPS); - for (guint i = 0; i < DETECT_PROBES_PER_ROUND; i++) + for (guint i = 0; i < DETECT_PER_LOOP; i++) { - gsize probe_len = 0; - guint8 *probe; + if (g_cancellable_is_cancelled (cancellable)) return FALSE; local_error = NULL; - probe = s730b_detect_probe (usb_dev, &probe_len, &local_error); - - if (!probe) + if (!s730b_init (usb_dev, &local_error)) { - /* USB 에러/타임아웃은 soft-fail로 처리 */ - if (local_error) - { - g_message ("s730b: detect probe error (round=%u, probe=%u): %s", - r, i, local_error->message); - g_clear_error (&local_error); - } - /* 이 probe는 실패, 다음 probe로 넘어감 */ + g_clear_error (&local_error); g_usleep ((gulong) DETECT_INTERVAL_MS * 1000); continue; } - gboolean finger = s730b_has_finger (probe, probe_len); - g_free (probe); + gsize figner_len = 0; + guint8 *finger = s730b_detect_finger (usb_dev, &figner_len, &local_error); + if (!finger) + { + g_clear_error (&local_error); + g_usleep ((gulong) DETECT_INTERVAL_MS * 1000); + continue; + } - g_message ("s730b: probe %u: finger=%d", i, finger); - if (finger) - return TRUE; + gboolean fingerprint = s730b_has_fingerprint (finger, figner_len); + g_free (finger); + if (fingerprint) + { + g_message ("s730b: fingerprint detected!"); + /* Re-init required before full capture */ + if (!s730b_init (usb_dev, &local_error)) + { + g_clear_error (&local_error); + g_usleep ((gulong) DETECT_INTERVAL_MS * 1000); + continue; + } + return TRUE; + } g_usleep ((gulong) DETECT_INTERVAL_MS * 1000); } - - /* 라운드 실패 시 센서 재초기화도 soft하게 처리 */ - g_message ("s730b: re-init after failed detect round"); - local_error = NULL; - if (!s730b_run_init (usb_dev, &local_error)) - { - if (local_error) - { - g_message ("s730b: re-init failed after detect round: %s", - local_error->message); - g_clear_error (&local_error); - } - /* init 실패해도 여기선 그냥 detect 실패로 간주 */ - break; - } + if (!s730b_init (usb_dev, &local_error)) g_clear_error (&local_error); } - - g_message ("s730b: finger detect timeout (no finger)"); return FALSE; } -/* ---------- full frame 캡처 ---------- */ - +/* Wait for finger OFF */ static gboolean -s730b_capture_frame (GUsbDevice *usb_dev, - guint8 **out_buf, - gsize *out_len, - GError **error) +s730b_wait_finger_lost (GUsbDevice *usb_dev, GCancellable *cancellable) +{ + GError *local_error = NULL; + + g_message ("s730b: waiting for fingerprint removal (indefinite)..."); + + while (!g_cancellable_is_cancelled (cancellable)) + { + local_error = NULL; + if (!s730b_init (usb_dev, &local_error)) + { + g_clear_error (&local_error); + g_usleep (200 * 1000); + continue; + } + + gsize figner_len = 0; + guint8 *finger = s730b_detect_finger (usb_dev, &figner_len, &local_error); + if (!finger) + { + g_clear_error (&local_error); + g_usleep (200 * 1000); + continue; + } + + gboolean fingerprint = s730b_has_fingerprint (finger, figner_len); + g_free (finger); + + if (!fingerprint) + return TRUE; + + g_usleep (200 * 1000); + } + + return FALSE; +} + +/* Capture fingerprint image */ +static gboolean +s730b_capture (GUsbDevice *usb_dev, guint8 **out_buf, gsize *out_len, GError **error) { guint8 *buf = NULL; - gsize capacity = (gsize) CAPTURE_NUM_CHUNKS * BULK_PACKET_SIZE + 1024; + gsize capacity = (gsize) CAPTURE_NUM_PACKETS * BULK_PACKET_SIZE + 1024; gsize total = 0; buf = g_malloc (capacity); if (!buf) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, - "s730b: capture alloc fail"); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, "alloc fail"); return FALSE; } - /* chunk 0: control 0xCA + start_cmd */ + /* packet 0 */ { guint8 start_cmd[BULK_PACKET_SIZE] = { 0 }; start_cmd[0] = 0xa8; start_cmd[1] = 0x06; - if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, - capture_indices[0], NULL, 0, - 500, error)) - goto fail; - - if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, - start_cmd, sizeof (start_cmd), - 500, error)) - goto fail; - + if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, capture_indices[0], NULL, 0, 500, error)) goto fail; + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, start_cmd, sizeof (start_cmd), 500, error)) goto fail; guint8 tmp[BULK_PACKET_SIZE]; gsize got = 0; - - if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, - tmp, sizeof (tmp), - 500, &got, error)) - goto fail; - - /* 파이썬처럼 chunk0 데이터는 버림 (상태 응답) */ + if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, tmp, sizeof (tmp), 500, &got, error)) goto fail; } - /* chunk 1..N : 실제 이미지 데이터 + ACK */ - for (gsize i = 1; i < CAPTURE_NUM_CHUNKS; i++) + /* packet 1..N */ + for (gsize i = 1; i < CAPTURE_NUM_PACKETS; i++) { guint8 tmp[BULK_PACKET_SIZE]; guint8 ack[BULK_PACKET_SIZE] = { 0 }; gsize got = 0; guint16 widx = capture_indices[i]; - if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, - widx, NULL, 0, - 500, error)) - goto fail; - - if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, - tmp, sizeof (tmp), - 1000, &got, error)) - goto fail; - - if (got == 0) - break; - + if (!usb_ctrl_out (usb_dev, CAPTURE_CTRL_REQ, CAPTURE_CTRL_VAL, widx, NULL, 0, 500, error)) goto fail; + if (!usb_bulk_in (usb_dev, SAMSUNG730B_EP_IN, tmp, sizeof (tmp), 1000, &got, error)) goto fail; + + if (got == 0) break; if (total + got > capacity) { capacity *= 2; guint8 *tmp_buf = g_realloc (buf, capacity); - if (!tmp_buf) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, - "s730b: capture realloc fail"); - goto fail; - } + if (!tmp_buf) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, "realloc fail"); goto fail; } buf = tmp_buf; } - memcpy (buf + total, tmp, got); total += got; - - if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, - ack, sizeof (ack), - 500, error)) - goto fail; + if (!usb_bulk_out (usb_dev, SAMSUNG730B_EP_OUT, ack, sizeof (ack), 500, error)) goto fail; } *out_buf = buf; @@ -516,18 +447,15 @@ fail: return FALSE; } -/* ---------- 이미지로 변환 + report ---------- */ - +/* Process and submit the image to libfprint */ static void -s730b_emit_image (FpImageDevice *dev, guint8 *raw, gsize raw_len) +s730b_submit_image (FpImageDevice *dev, guint8 *raw, gsize raw_len) { gsize needed = IMG_OFFSET + (gsize) IMG_W * IMG_H; - if (raw_len < needed) { - g_warning ("s730b: captured too short (%"G_GSIZE_FORMAT")", raw_len); + g_warning ("s730b: captured too short"); g_free (raw); - fpi_image_device_report_finger_status (dev, FALSE); fpi_image_device_image_captured (dev, NULL); return; @@ -537,26 +465,159 @@ s730b_emit_image (FpImageDevice *dev, guint8 *raw, gsize raw_len) gsize img_size = 0; guchar *dst = (guchar *) fp_image_get_data (img, &img_size); - if (img_size < (gsize) IMG_W * IMG_H) - { - g_warning ("s730b: image buffer too small (%"G_GSIZE_FORMAT")", img_size); - g_free (raw); - g_object_unref (img); - - fpi_image_device_report_finger_status (dev, FALSE); - fpi_image_device_image_captured (dev, NULL); - return; - } - memcpy (dst, raw + IMG_OFFSET, IMG_W * IMG_H); g_free (raw); fpi_image_device_report_finger_status (dev, TRUE); fpi_image_device_image_captured (dev, img); - g_object_unref (img); } -/* ---------- libfprint 콜백들 ---------- */ +/* ---------- Asynchronous Task Management ---------- */ + +typedef enum { + TASK_MODE_ENROLL, + TASK_MODE_WAIT_OFF +} TaskMode; + +typedef struct { + GUsbDevice *usb_dev; + guint8 *raw_data; + gsize raw_len; + TaskMode mode; +} S730bTaskData; + +static void +s730b_task_data_free (gpointer user_data) +{ + S730bTaskData *data = user_data; + if (data->raw_data) g_free (data->raw_data); + g_free (data); +} + +/* Forward declarations to Asynchronous Task for Compiler */ +static void s730b_enroll_done (GObject *source_object, GAsyncResult *res, gpointer user_data); +static void s730b_enroll_worker (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable); + +static void +s730b_run_async_task (FpImageDevice *dev, TaskMode mode) +{ + FpiDeviceSamsung730b *self = FPI_DEVICE_SAMSUNG730B (dev); + GUsbDevice *usb = fpi_device_get_usb_device (FP_DEVICE (dev)); + GTask *task; + S730bTaskData *data; + + task = g_task_new (dev, self->cancellable, s730b_enroll_done, NULL); + data = g_new0 (S730bTaskData, 1); + data->usb_dev = usb; + data->mode = mode; + + g_task_set_task_data (task, data, s730b_task_data_free); + g_task_run_in_thread (task, s730b_enroll_worker); + g_object_unref (task); +} + +/* + * Worker thread for blocking USB operations (s730b_wait_finger(), s730b_capture()). + * Prevent the main GLib loop from stopping by using the thread + */ +static void +s730b_enroll_worker (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +{ + S730bTaskData *data = task_data; + GError *error = NULL; + + if (g_task_return_error_if_cancelled (task)) return; + + if (data->mode == TASK_MODE_WAIT_OFF) + { + /* Unlimited waiting until you take off finger */ + if (s730b_wait_finger_lost (data->usb_dev, cancellable)) + { + g_task_return_boolean (task, TRUE); + } + else + { + /* Check if cancelled or failed */ + if (g_task_return_error_if_cancelled (task)) + return; + + g_task_return_new_error (task, FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO, "Wait lost failed"); + } + return; + } + + /* TASK_MODE_ENROLL */ + if (!s730b_wait_finger (data->usb_dev, cancellable)) + { + if (g_task_return_error_if_cancelled (task)) + return; + + g_task_return_new_error (task, FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO, "No fingerprint detected"); + return; + } + + if (g_task_return_error_if_cancelled (task)) return; + + /* Short delay for stabilization */ + g_usleep (50 * 1000); + + if (!s730b_capture (data->usb_dev, &data->raw_data, &data->raw_len, &error)) + { + g_task_return_error (task, error); + return; + } + + g_task_return_boolean (task, TRUE); +} + +static void +s730b_enroll_done (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + FpImageDevice *dev = FP_IMAGE_DEVICE (source_object); + GTask *task = G_TASK (res); + S730bTaskData *data = g_task_get_task_data (task); + GError *error = NULL; + + if (g_task_propagate_boolean (task, &error)) + { + if (data->mode == TASK_MODE_WAIT_OFF) + { + fpi_image_device_report_finger_status (dev, FALSE); + + /* Restart enroll loop */ + s730b_run_async_task (dev, TASK_MODE_ENROLL); + } + else + { + /* Capture done, submit image */ + guint8 *buf = data->raw_data; + gsize len = data->raw_len; + data->raw_data = NULL; + s730b_submit_image (dev, buf, len); + + /* Chain next task: Wait for finger removal */ + s730b_run_async_task (dev, TASK_MODE_WAIT_OFF); + } + } + else + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_warning ("s730b: task failed: %s", error->message); + if (data->mode == TASK_MODE_ENROLL) + { + fpi_image_device_report_finger_status (dev, FALSE); + fpi_image_device_image_captured (dev, NULL); + + /* Retry detection on error */ + s730b_run_async_task (dev, TASK_MODE_ENROLL); + } + } + g_error_free (error); + } +} + +/* ---------- libfprint callbacks ---------- */ static void samsung730b_dev_init (FpImageDevice *dev) @@ -569,13 +630,11 @@ samsung730b_dev_init (FpImageDevice *dev) fpi_image_device_open_complete (dev, error); return; } - - if (!s730b_run_init (usb, &error)) + if (!s730b_init (usb, &error)) { fpi_image_device_open_complete (dev, error); return; } - fpi_image_device_open_complete (dev, NULL); } @@ -592,57 +651,55 @@ samsung730b_dev_deinit (FpImageDevice *dev) static void samsung730b_dev_activate (FpImageDevice *dev) { - GUsbDevice *usb = fpi_device_get_usb_device (FP_DEVICE (dev)); - GError *error = NULL; - guint8 *raw = NULL; - gsize raw_len = 0; + FpiDeviceSamsung730b *self = FPI_DEVICE_SAMSUNG730B (dev); + FpiImageDeviceState state; - /* 1) finger detect (이미지 기반) */ - if (!s730b_wait_finger (usb)) - { - /* 손가락 없음: 에러로 치지 않고 조용히 끝냄 */ - fpi_image_device_report_finger_status (dev, FALSE); - fpi_image_device_activate_complete (dev, NULL); - return; - } + if (self->cancellable) g_object_unref (self->cancellable); + self->cancellable = g_cancellable_new (); - /* 2) detect 동안 센서 상태가 바뀌었을 수 있으니 init 재실행 */ - if (!s730b_run_init (usb, &error)) - { - fpi_image_device_activate_complete (dev, error); - return; - } - - /* 3) full capture */ - if (!s730b_capture_frame (usb, &raw, &raw_len, &error)) - { - fpi_image_device_activate_complete (dev, error); - return; - } - - /* activate 완료 알리고 이미지 전달 */ + g_object_get (dev, "fpi-image-device-state", &state, NULL); fpi_image_device_activate_complete (dev, NULL); - s730b_emit_image (dev, raw, raw_len); + + if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF) + s730b_run_async_task (dev, TASK_MODE_WAIT_OFF); + else + s730b_run_async_task (dev, TASK_MODE_ENROLL); } static void samsung730b_dev_deactivate (FpImageDevice *dev) { + FpiDeviceSamsung730b *self = FPI_DEVICE_SAMSUNG730B (dev); + + if (self->cancellable) + g_cancellable_cancel (self->cancellable); + fpi_image_device_deactivate_complete (dev, NULL); } -/* ---------- ID 테이블 ---------- */ +/* ---------- ID & Boilerplate ---------- */ static const FpIdEntry samsung730b_ids[] = { { .vid = SAMSUNG730B_VID, .pid = SAMSUNG730B_PID, .driver_data = 0 }, - { .vid = 0, .pid = 0, .driver_data = 0 }, + { .vid = 0, .pid = 0, .driver_data = 0 }, }; -/* ---------- GObject boilerplate ---------- */ - static void fpi_device_samsung730b_init (FpiDeviceSamsung730b *self) { + self->cancellable = NULL; +} + +static void +fpi_device_samsung730b_finalize (GObject *object) +{ + FpiDeviceSamsung730b *self = FPI_DEVICE_SAMSUNG730B (object); + if (self->cancellable) + { + g_cancellable_cancel (self->cancellable); + g_object_unref (self->cancellable); + } + G_OBJECT_CLASS (fpi_device_samsung730b_parent_class)->finalize (object); } static void @@ -650,6 +707,7 @@ fpi_device_samsung730b_class_init (FpiDeviceSamsung730bClass *klass) { FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); dev_class->id = "samsung730b"; dev_class->full_name = "Samsung 730B (experimental)"; @@ -665,4 +723,6 @@ fpi_device_samsung730b_class_init (FpiDeviceSamsung730bClass *klass) img_class->img_width = IMG_W; img_class->img_height = IMG_H; img_class->bz3_threshold = 20; + + gobject_class->finalize = fpi_device_samsung730b_finalize; } \ No newline at end of file