kee

Offline IOU signer with QR as transport
git clone https://holbrook.no/src/kee
Info | Log | Files | Refs | README | LICENSE

commit ceb1f6b91c628390be9360939f35643d209789e4
parent 1633c87b8b96e2b8437d7e1c1149812ed2c5550a
Author: lash <dev@holbrook.no>
Date:   Sun, 10 Mar 2024 03:34:07 +0000

Replace gstreamer scan code with zbar tee

Diffstat:
Msrc/gtk/kee-uicontext.c | 3++-
Msrc/gtk/main.c | 5+----
Msrc/gtk/menu.c | 14+++++++++-----
Msrc/gtk/scan.c | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/gtk/scan.h | 1+
Msrc/gtk/ui.c | 106++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/gtk/ui.h | 10+++++++---
7 files changed, 274 insertions(+), 50 deletions(-)

diff --git a/src/gtk/kee-uicontext.c b/src/gtk/kee-uicontext.c @@ -93,13 +93,14 @@ static void kee_uicontext_class_init(KeeUicontextClass *kls) { static void kee_uicontext_init(KeeUicontext *self) { //KeeUicontextPrivate *o = kee_uicontext_get_instance_private(self); - } void kee_uicontext_scanstart(KeeUicontext *o) { if (KEE_UI_STATE_IS_SCANNING(o->ui)) { + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "already in scanning state"); return; } + ui_state_change(o->ui, KEE_UI_STATE_SCANNING, 0); g_signal_emit(o, kee_sigs[SCAN_WANT], 0); } diff --git a/src/gtk/main.c b/src/gtk/main.c @@ -22,7 +22,6 @@ static void deactivate(GtkApplication *app, gpointer user_data) { ui_free(user_data); } - int main(int argc, char **argv) { int r; KeeUicontext *uctx; @@ -36,7 +35,6 @@ int main(int argc, char **argv) { kee_context_new(&ctx, &ui); uctx = g_object_new(KEE_TYPE_UICONTEXT, "ui_container", &ui, "core_context", &ctx, NULL); - g_object_set(&uctx, "app", ui.gapp, NULL); settings_new_from_xdg(&ctx.settings); settings_init(&ctx.settings); @@ -45,11 +43,10 @@ int main(int argc, char **argv) { g_signal_connect (ui.gapp, "startup", G_CALLBACK (startup), uctx); g_signal_connect (ui.gapp, "activate", G_CALLBACK (activate), &ui); g_signal_connect (ui.gapp, "shutdown", G_CALLBACK (deactivate), uctx); - //g_signal_connect (uctx, "scan_want", G_CALLBACK( tmpscan ), NULL); + g_signal_connect (uctx, "scan_want", G_CALLBACK( ui_handle_scan) , &ui); r = g_application_run (G_APPLICATION (ui.gapp), argc, argv); - g_object_unref(ui.gapp); return r; } diff --git a/src/gtk/menu.c b/src/gtk/menu.c @@ -3,16 +3,20 @@ #include "kee-uicontext.h" -static void act_scan(GSimpleAction *act, GVariant *param, GApplication *app) { +static void act_scan(GSimpleAction *act, GVariant *param, KeeUicontext *ui) { //GDBusConnection *conn; g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "scan clicked"); + kee_uicontext_scanstart(ui); //conn = g_application_get_dbus_connection(app); } -static void act_quit(GSimpleAction *act, GVariant *param, GApplication *app) { - g_application_quit(app); +static void act_quit(GSimpleAction *act, GVariant *param, KeeUicontext *ui) { + GApplication *gapp; + g_object_get(ui, "app", &gapp, NULL); + + g_application_quit(gapp); } //void menu_setup(struct ui_container *ui) { @@ -39,11 +43,11 @@ void menu_setup(KeeUicontext *ui) { act = g_simple_action_new("quit", NULL); g_action_map_add_action(G_ACTION_MAP(gapp), G_ACTION(act)); - g_signal_connect(act, "activate", G_CALLBACK(act_quit), gapp); + g_signal_connect(act, "activate", G_CALLBACK(act_quit), ui); act = g_simple_action_new("scan", NULL); g_action_map_add_action(G_ACTION_MAP(gapp), G_ACTION(act)); - g_signal_connect(act, "activate", G_CALLBACK(act_scan), gapp); + g_signal_connect(act, "activate", G_CALLBACK(act_scan), ui); g_menu_item_set_submenu(menu_item_menu, G_MENU_MODEL(menu)); g_object_unref(menu); diff --git a/src/gtk/scan.c b/src/gtk/scan.c @@ -4,61 +4,190 @@ #include "scan.h" #include "err.h" -gboolean msg_func(GstBus *bus, GstMessage *msg, gpointer user_data) { - return false; -} +//gboolean msg_func(GstBus *bus, GstMessage *msg, gpointer user_data) { +// GError *err; +// gchar *debug_info; +// GstState oldstate; +// GstState newstate; +// GstState pendingstate; +// //struct _gst_data *data; +// //GstStateChangeReturn rsc; +// +// //data = (struct _gst_data*)user_data; +// +// switch (GST_MESSAGE_TYPE (msg)) { +// case GST_MESSAGE_ERROR: +// gst_message_parse_error(msg, &err, &debug_info); +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "logg %s: %s", GST_OBJECT_NAME(msg->src), err->message); +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "debug %s", debug_info ? debug_info : "none"); +// g_clear_error(&err); +// g_free(debug_info); +// break; +// case GST_MESSAGE_EOS: +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "eos"); +// break; +// case GST_MESSAGE_STATE_CHANGED: +// gst_message_parse_state_changed(msg, &oldstate, &newstate, &pendingstate); +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "state change: %s -> %s", gst_element_state_get_name(oldstate), gst_element_state_get_name(newstate)); +// break; +// default: +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "unknown message (ext %d): %s", GST_MESSAGE_TYPE_IS_EXTENDED(msg), GST_MESSAGE_TYPE_NAME(msg)); +// } +// +// return false; +//} + +//int scan_init(struct kee_scanner *scan) { +// GdkPaintable *img; +// GstCaps *caps; +// GstElement *convert; +// GstElement *filter; +// GstStateChangeReturn rsc; +// gchar *txt; +// +// memset(scan, 0, sizeof(struct kee_scanner)); +// +// scan->pipeline = gst_pipeline_new("kee-qr-scan"); +// scan->source = gst_element_factory_make("v4l2src", "v4l2src"); +// scan->video_sink = gst_element_factory_make("gtk4paintablesink", "gtk4paintablesink"); +// if (scan->video_sink == NULL) { +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "cant create gl sinks"); +// return 1; +// } +// //g_object_set(G_OBJECT(scan->source), "device", "/dev/video0", NULL); +// g_object_set(G_OBJECT(scan->source), "device", "/dev/video4", NULL); +// +// g_object_get(scan->video_sink, "paintable", &img, NULL); +// if (!img) { +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "failed getting paintable"); +// return 1; +// } +// +// g_object_get(scan->source, "device", &txt, NULL); +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "using video device: %s", txt); +// +// filter = gst_element_factory_make("capsfilter", "capsfilter"); +// convert = gst_element_factory_make("videoconvert", "videoconvert"); +// caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "RGB", "width", G_TYPE_INT, 640, "height", G_TYPE_INT, 480, "framerate", GST_TYPE_FRACTION, 30, 1, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL); +// g_object_set(G_OBJECT(filter), "caps", caps, NULL); +// scan->video_view = GTK_PICTURE(gtk_picture_new_for_paintable(img)); +// gtk_picture_set_content_fit(scan->video_view, GTK_CONTENT_FIT_CONTAIN); +// gst_bin_add_many(GST_BIN(scan->pipeline), scan->source, convert, filter, scan->video_sink, NULL); +// +// if (gst_element_link_many(scan->source, convert, filter, scan->video_sink, NULL) != TRUE) { +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "gst pipeline link fail"); +// gst_object_unref(scan->pipeline); +// return 1; +// } +// +// rsc = gst_element_set_state(scan->pipeline, GST_STATE_PLAYING); +// if (rsc == GST_STATE_CHANGE_FAILURE) { +// g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "fail set pipeline to pause"); +// gst_object_unref(scan->pipeline); +// return 1; +// } +// +// return ERR_OK; +//} int scan_init(struct kee_scanner *scan) { - GdkPaintable *img; - GstCaps *caps; - GstElement *convert; - GstElement *filter; + GstElement *tee; + GstElement *zbar; + GstElement *queue_display; + GstElement *queue_scan; + GstPad *queue_display_pad; + GstPad *queue_scan_pad; + GstPad *tee_display; + GstPad *tee_scan; GstStateChangeReturn rsc; - GstBus *bus; - gchar *txt; + GstElement *convert_display; + GstElement *convert_scan; + GstElement *filter; + GstElement *devnull; + GstCaps *caps; + GdkPaintable *img; + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "camera setup"); memset(scan, 0, sizeof(struct kee_scanner)); - scan->pipeline = gst_pipeline_new("kee-qr-scan"); + scan->pipeline = gst_pipeline_new("webcam-zbar"); scan->source = gst_element_factory_make("v4l2src", "v4l2src"); + filter = gst_element_factory_make("capsfilter", "capsfilter"); + convert_display = gst_element_factory_make("videoconvert", "videoconvert_display"); + convert_scan = gst_element_factory_make("videoconvert", "videoconvert_scan"); scan->video_sink = gst_element_factory_make("gtk4paintablesink", "gtk4paintablesink"); - if (scan->video_sink == NULL) { - g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "cant create gl sinks"); + tee = gst_element_factory_make("tee", "split-view-zbar"); + zbar = gst_element_factory_make("zbar", "zbar"); + devnull = gst_element_factory_make("fakesink", "fakesink"); + queue_display = gst_element_factory_make("queue", "queue-display"); + queue_scan = gst_element_factory_make("queue", "queue-scan"); + + caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "RGB", "width", G_TYPE_INT, 640, "height", G_TYPE_INT, 360, "framerate", GST_TYPE_FRACTION, 30, 1, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL); + g_object_set(G_OBJECT(filter), "caps", caps, NULL); + + gst_bin_add_many(GST_BIN(scan->pipeline), scan->source, scan->video_sink, convert_display, convert_scan, filter, tee, zbar, queue_display, queue_scan, devnull, NULL); + if (gst_element_link_many(scan->source, tee, NULL) != TRUE) { + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "fail link src to muxer"); + gst_object_unref(scan->pipeline); + return 1; + } + + if (gst_element_link_many(queue_display, convert_display, filter, scan->video_sink, NULL) != TRUE) { + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "fail link muxer to display"); + gst_object_unref(scan->pipeline); return 1; } - g_object_set(G_OBJECT(scan->source), "device", "/dev/video0", NULL); + if (gst_element_link_many(queue_scan, convert_scan, zbar, devnull, NULL) != TRUE) { + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "fail link muxer to scanner"); + gst_object_unref(scan->pipeline); + return 1; + } + + tee_display = gst_element_request_pad_simple(tee, "src_%u"); + queue_display_pad = gst_element_get_static_pad(queue_display, "sink"); + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "display queue linked with %s", gst_pad_get_name(tee_display)); + tee_scan = gst_element_request_pad_simple(tee, "src_%u"); + queue_scan_pad = gst_element_get_static_pad(queue_scan, "sink"); + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "scan queue linked with %s", gst_pad_get_name(tee_scan)); + + g_object_set(G_OBJECT(scan->source), "device", "/dev/video4", NULL); g_object_get(scan->video_sink, "paintable", &img, NULL); if (!img) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "failed getting paintable"); return 1; } - - g_object_get(scan->source, "device", &txt, NULL); - g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "using video device: %s", txt); - - filter = gst_element_factory_make("capsfilter", "capsfilter"); - convert = gst_element_factory_make("videoconvert", "videoconvert"); - caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "RGB", "width", G_TYPE_INT, 640, "height", G_TYPE_INT, 480, "framerate", GST_TYPE_FRACTION, 30, 1, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL); - g_object_set(G_OBJECT(filter), "caps", caps, NULL); scan->video_view = GTK_PICTURE(gtk_picture_new_for_paintable(img)); - gst_bin_add_many(GST_BIN(scan->pipeline), scan->source, convert, filter, scan->video_sink, NULL); + gtk_picture_set_content_fit(scan->video_view, GTK_CONTENT_FIT_CONTAIN); - if (gst_element_link_many(scan->source, convert, filter, scan->video_sink, NULL) != TRUE) { - g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "gst pipeline link fail"); + if (gst_pad_link(tee_display, queue_display_pad) != GST_PAD_LINK_OK) { + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "mux link fail for display"); gst_object_unref(scan->pipeline); return 1; } - rsc = gst_element_set_state(scan->pipeline, GST_STATE_PAUSED); + if (gst_pad_link(tee_scan, queue_scan_pad) != GST_PAD_LINK_OK) { + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "mux link fail for scan"); + gst_object_unref(scan->pipeline); + return 1; + } + + g_object_unref(queue_display_pad); + g_object_unref(queue_scan_pad); + + rsc = gst_element_set_state(scan->pipeline, GST_STATE_PLAYING); if (rsc == GST_STATE_CHANGE_FAILURE) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "fail set pipeline to pause"); gst_object_unref(scan->pipeline); return 1; } - bus = gst_element_get_bus(scan->pipeline); - gst_bus_add_watch(bus, msg_func, &scan); - return ERR_OK; } + +void scan_set_handler(struct kee_scanner *scan, gboolean(*fn)(GstBus *bus, GstMessage *msg, gpointer user_data)) { + GstBus *bus; + + bus = gst_element_get_bus(scan->pipeline); + gst_bus_add_watch(bus, fn, scan); +} diff --git a/src/gtk/scan.h b/src/gtk/scan.h @@ -13,5 +13,6 @@ struct kee_scanner { }; int scan_init(struct kee_scanner *scan); +void scan_set_handler(struct kee_scanner *scan, gboolean(*fn)(GstBus *bus, GstMessage *msg, gpointer user_data)); #endif // _KEE_GTK_SCAN_H diff --git a/src/gtk/ui.c b/src/gtk/ui.c @@ -1,3 +1,4 @@ +#include <string.h> #include <gtk/gtk.h> #include <gst/gst.h> @@ -13,7 +14,7 @@ int ui_init(struct ui_container *ui) { if (ui->gapp == NULL) { return ERR_FAIL; } - //scan_init(&ui->scan); + ui->state = 0; return ERR_OK; } @@ -46,7 +47,19 @@ GtkWidget* ui_build_unlock(struct ui_container *ui) { return GTK_WIDGET(box); } -GtkWidget* ui_build_view(struct ui_container *ui) { +static GtkWidget* ui_build_scan(struct ui_container *ui) { + GtkWidget *label; + + ui->front_scan = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 10)); + + label = gtk_label_new("please scan qr code"); + gtk_box_append(GTK_BOX(ui->front_scan), label); + + return GTK_WIDGET(ui->front_scan); +} + + +static GtkWidget* ui_build_view(struct ui_container *ui) { GtkSelectionModel *sel; GtkListItemFactory *factory; @@ -60,8 +73,7 @@ GtkWidget* ui_build_view(struct ui_container *ui) { } void ui_build(GtkApplication *app, struct ui_container *ui) { - GtkWidget *unlock; - GtkWidget *view; + GtkWidget *widget; ui->win = GTK_APPLICATION_WINDOW(gtk_application_window_new (app)); ui->stack = GTK_STACK(gtk_stack_new()); @@ -72,12 +84,14 @@ void ui_build(GtkApplication *app, struct ui_container *ui) { gtk_window_set_child(GTK_WINDOW(ui->win), GTK_WIDGET(ui->stack)); gtk_application_window_set_show_menubar(GTK_APPLICATION_WINDOW(ui->win), TRUE); - unlock = ui_build_unlock(ui); - gtk_stack_add_child(ui->stack, unlock); - view = ui_build_view(ui); - gtk_stack_add_child(ui->stack, view); + widget = ui_build_unlock(ui); + gtk_stack_add_child(ui->stack, widget); + gtk_stack_set_visible_child(GTK_STACK(ui->stack), widget); + widget = ui_build_view(ui); + gtk_stack_add_child(ui->stack, widget); + widget = ui_build_scan(ui); + gtk_stack_add_child(ui->stack, widget); - gtk_stack_set_visible_child(GTK_STACK(ui->stack), unlock); //gtk_stack_set_visible_child(GTK_STACK(ui->stack), view); gtk_window_present(GTK_WINDOW (ui->win)); @@ -112,3 +126,77 @@ int ui_state_change(struct ui_container *ui, int set, int reset) { ui->state |= set; return ui->state; } + +gboolean ui_scan_code_handler(GstBus *bus, GstMessage *msg, gpointer user_data) { + GError *err; + gchar *debug_info; + GstState oldstate; + GstState newstate; + GstState pendingstate; + const gchar *src; + const gchar *code; + const GstStructure *strctr; + //struct _gst_data *data; + //GstStateChangeReturn rsc; + + //data = (struct _gst_data*)user_data; + + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "scan msg got"); + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &err, &debug_info); + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "logg %s: %s", GST_OBJECT_NAME(msg->src), err->message); + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "debug %s", debug_info ? debug_info : "none"); + g_clear_error(&err); + g_free(debug_info); + break; + case GST_MESSAGE_EOS: + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "eos"); + break; + case GST_MESSAGE_STATE_CHANGED: + gst_message_parse_state_changed(msg, &oldstate, &newstate, &pendingstate); + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "state change: %s -> %s", gst_element_state_get_name(oldstate), gst_element_state_get_name(newstate)); + break; + case GST_MESSAGE_ELEMENT: + src = gst_object_get_name(msg->src); + if (strcmp(src, "zbar")) { + break; + } + strctr = gst_message_get_structure(msg); + code = gst_structure_get_string(strctr, "symbol"); + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "message %s: %d (%s) - decoded: %s", src, msg->type, gst_message_type_get_name(msg->type), code); + break; + default: + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "unknown message (ext %d): %s", GST_MESSAGE_TYPE_IS_EXTENDED(msg), GST_MESSAGE_TYPE_NAME(msg)); + break; + } + + return true; +} + +GtkWidget* ui_build_scan_attach(struct ui_container *ui) { + int r; + struct kee_scanner *scan; + GtkWidget *view; + + scan = &ui->scan; + r = scan_init(scan); + if (r) { + g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "fail scan setup"); + return NULL; + } + view = GTK_WIDGET(scan->video_view); + gtk_box_append(GTK_BOX(ui->front_scan), view); + scan_set_handler(scan, ui_scan_code_handler); + return view; +} + +void ui_handle_scan(GtkApplication *app, struct ui_container *ui) { + if (!(ui->state & KEE_UI_STATE_SCAN_INIT)) { + ui_build_scan_attach(ui); + + } + ui_state_change(ui, KEE_UI_STATE_SCANNING | KEE_UI_STATE_SCAN_INIT, 0); + gtk_stack_set_visible_child(GTK_STACK(ui->stack), GTK_WIDGET(ui->front_scan)); +} diff --git a/src/gtk/ui.h b/src/gtk/ui.h @@ -7,11 +7,12 @@ typedef enum { - KEE_UI_INIT = 1, - KEE_UI_SCANNING = 2, + KEE_UI_STATE_INIT = 1, + KEE_UI_STATE_SCAN_INIT = 2, + KEE_UI_STATE_SCANNING = 4, } UiState; -#define KEE_UI_STATE_IS_SCANNING(c) c->state & KEE_UI_SCANNING +#define KEE_UI_STATE_IS_SCANNING(c) c->state & KEE_UI_STATE_SCANNING struct ui_container { GtkApplication *gapp; @@ -19,6 +20,7 @@ struct ui_container { GtkStack *stack; GListModel *front_list; GtkListView *front_view; + GtkBox *front_scan; struct kee_scanner scan; int state; }; @@ -28,4 +30,6 @@ void ui_build(GtkApplication *app, struct ui_container *ui); int ui_state_change(struct ui_container *ui, int set, int reset); void ui_free(struct ui_container *ui); +void ui_handle_scan(GtkApplication *app, struct ui_container *ui); + #endif // _UI_H