summaryrefslogtreecommitdiffstats
path: root/gtk
diff options
context:
space:
mode:
Diffstat (limited to 'gtk')
-rw-r--r--gtk/Makefile.am88
-rw-r--r--gtk/zbargtk.c908
-rw-r--r--gtk/zbargtkprivate.h98
-rw-r--r--gtk/zbarmarshal.list1
4 files changed, 1095 insertions, 0 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
new file mode 100644
index 0000000..8744682
--- /dev/null
+++ b/gtk/Makefile.am
@@ -0,0 +1,88 @@
+lib_LTLIBRARIES = libzbargtk.la
+libzbargtk_la_CPPFLAGS = $(GTK_CFLAGS) $(AM_CPPFLAGS)
+libzbargtk_la_LDFLAGS = -version-info $(ZGTK_LIB_VERSION) \
+ -export-symbols-regex "^zbar_.*" $(AM_LDFLAGS) -no-undefined
+libzbargtk_la_LIBADD = $(GTK_LIBS) ../zbar/libzbar.la $(AM_LIBADD)
+libzbargtk_la_DEPENDENCIES = ../zbar/libzbar.la
+
+
+if HAVE_X
+libzbargtk_la_CPPFLAGS += -DHAVE_X
+endif
+
+dist_libzbargtk_la_SOURCES = zbargtk.c zbargtkprivate.h
+nodist_libzbargtk_la_SOURCES = zbarmarshal.c zbarmarshal.h
+BUILT_SOURCES = zbarmarshal.c zbarmarshal.h
+CLEANFILES = $(BUILT_SOURCES)
+EXTRA_DIST = zbarmarshal.list
+
+%.h: %.list
+ $(GLIB_GENMARSHAL) --g-fatal-warnings --prefix=zbar_marshal \
+ --header $^ > $@
+
+%.c: %.list
+ $(GLIB_GENMARSHAL) --g-fatal-warnings --prefix=zbar_marshal \
+ --body $^ > $@
+
+../zbar/libzbar.la:
+ $(MAKE) -C $(abs_top_srcdir) zbar/libzbar.la
+
+../zbarcam/zbarcam-gtk: libzbargtk.la
+ $(MAKE) -C $(abs_top_srcdir) zbarcam/zbarcam-gtk
+
+# GObject Introspection
+
+include $(INTROSPECTION_MAKEFILE)
+
+
+
+# NOTE:
+#
+# At least with Fedora 30 builds using mock (e. g. inside a chroot and
+# having the build dir different than the source dir, using
+# gobject-introspection-1.60, there is a bug with GIR file generation:
+# sometimes, g-ir-scanner is not capable of producing a C file that would
+# be loading libzbargtk.so.0. So, it fails with:
+#
+# error while loading shared libraries: libzbargtk.so.0:
+# cannot open shared object file: No such file or directory
+#
+# I suspect that it has something to do with libtool-2.4.6.
+
+# The fix is hackish, but it should be safe: it should manually include
+# the paths where libtool generate those at INTROSPECTION_SCANNER_ARGS.
+#
+# It should be noticed that, this shouldn't affect a non-buggy environment,
+# so, better to be safe than sorry.
+INTROSPECTION_SCANNER_ARGS = --warn-all \
+ --symbol-prefix=zbar \
+ --identifier-prefix=zbar_ \
+ --identifier-prefix=ZBar \
+ --library-path=$(abs_builddir)/.libs \
+ --library-path=$(abs_top_builddir)/zbar/.libs
+
+# Just in case
+INTROSPECTION_SCANNER_ENV = GI_SCANNER_DISABLE_CACHE=yes
+
+INTROSPECTION_GIRS = ZBar-1.0.gir
+
+ZBar_1_0_gir_NAMESPACE = ZBar
+ZBar_1_0_gir_VERSION = 1.0
+ZBar_1_0_gir_LIBS = $(lib_LTLIBRARIES) $(top_builddir)/zbar/libzbar.la
+ZBar_1_0_gir_FILES = $(top_builddir)/include/zbar/zbargtk.h zbargtk.c
+ZBar_1_0_gir_INCLUDES = Gtk-@GTK_VERSION_MAJOR@ Gdk-@GTK_VERSION_MAJOR@
+ZBar_1_0_gir_CFLAGS = $(libzbargtk_la_CPPFLAGS)
+
+# This may generate some warnings, but it is needed for "make dist"
+ZBar-1.0.gir: $(lib_LTLIBRARIES)
+
+if HAVE_INTROSPECTION
+
+girdir = $(INTROSPECTION_GIRDIR)
+dist_gir_DATA = $(INTROSPECTION_GIRS)
+typelibdir = $(INTROSPECTION_TYPELIBDIR)
+typelib_DATA = ZBar-1.0.typelib
+
+CLEANFILES += $(dist_gir_DATA) $(typelib_DATA)
+
+endif
diff --git a/gtk/zbargtk.c b/gtk/zbargtk.c
new file mode 100644
index 0000000..8c86016
--- /dev/null
+++ b/gtk/zbargtk.c
@@ -0,0 +1,908 @@
+/*------------------------------------------------------------------------
+ * Copyright 2008-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
+ *
+ * This file is part of the ZBar Bar Code Reader.
+ *
+ * The ZBar Bar Code Reader is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * The ZBar Bar Code Reader 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 Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License
+ * along with the ZBar Bar Code Reader; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * http://sourceforge.net/projects/zbar
+ *------------------------------------------------------------------------*/
+
+#include <gtk/gtk.h>
+#ifdef HAVE_X
+#include <gdk/gdkx.h>
+#elif defined(_WIN32)
+#include <gdk/gdkwin32.h>
+#endif
+
+#include "zbargtkprivate.h"
+#include "zbarmarshal.h"
+#include <zbar/zbargtk.h>
+
+#ifndef G_PARAM_STATIC_STRINGS
+#define G_PARAM_STATIC_STRINGS \
+ (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)
+#endif
+
+#define DEFAULT_WIDTH 640
+#define DEFAULT_HEIGHT 480
+
+enum
+{
+ DECODED,
+ DECODED_TEXT,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_VIDEO_DEVICE,
+ PROP_VIDEO_ENABLED,
+ PROP_VIDEO_OPENED,
+};
+
+static guint zbar_gtk_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE(ZBarGtk, zbar_gtk, GTK_TYPE_WIDGET);
+
+/* FIXME what todo w/errors? OOM? */
+/* FIXME signal failure notifications to main gui idle handler */
+
+void zbar_gtk_release_pixbuf(zbar_image_t *img)
+{
+ GdkPixbuf *pixbuf = zbar_image_get_userdata(img);
+
+ g_assert(GDK_IS_PIXBUF(pixbuf));
+
+ /* remove reference */
+ zbar_image_set_userdata(img, NULL);
+
+ /* release reference to associated pixbuf and it's data */
+ g_object_unref(pixbuf);
+}
+
+gboolean zbar_gtk_image_from_pixbuf(zbar_image_t *zimg, GdkPixbuf *pixbuf)
+{
+ unsigned pitch, width, height;
+ GdkColorspace colorspace;
+ unsigned long datalen;
+ int nchannels, bps;
+ long type;
+
+ /* apparently should always be packed RGB? */
+ colorspace = gdk_pixbuf_get_colorspace(pixbuf);
+
+ if (colorspace != GDK_COLORSPACE_RGB) {
+ g_warning("non-RGB color space not supported: %d\n", colorspace);
+ return (FALSE);
+ }
+
+ nchannels = gdk_pixbuf_get_n_channels(pixbuf);
+ bps = gdk_pixbuf_get_bits_per_sample(pixbuf);
+ type = 0;
+
+ /* these are all guesses... */
+ if (nchannels == 3 && bps == 8)
+ type = zbar_fourcc('R', 'G', 'B', '3');
+ else if (nchannels == 4 && bps == 8)
+ type = zbar_fourcc('B', 'G', 'R', '4'); /* FIXME alpha flipped?! */
+ else if (nchannels == 1 && bps == 8)
+ type = zbar_fourcc('Y', '8', '0', '0');
+ else if (nchannels == 3 && bps == 5)
+ type = zbar_fourcc('R', 'G', 'B', 'R');
+ else if (nchannels == 3 && bps == 4)
+ type = zbar_fourcc('R', '4', '4', '4'); /* FIXME maybe? */
+ else {
+ g_warning("unsupported combination: nchannels=%d bps=%d\n", nchannels,
+ bps);
+ return (FALSE);
+ }
+ zbar_image_set_format(zimg, type);
+
+ /* FIXME we don't deal w/bpl...
+ * this will cause problems w/unpadded pixbufs :|
+ */
+ pitch = gdk_pixbuf_get_rowstride(pixbuf);
+ width = pitch / ((nchannels * bps) / 8);
+
+ if ((width * nchannels * 8 / bps) != pitch) {
+ g_warning("unsupported: width=%d nchannels=%d bps=%d rowstride=%d\n",
+ width, nchannels, bps, pitch);
+ return (FALSE);
+ }
+ height = gdk_pixbuf_get_height(pixbuf);
+ /* FIXME this isn't correct either */
+ datalen = width * height * nchannels;
+
+ zbar_image_set_size(zimg, width, height);
+
+ /* when the zbar image is released, the pixbuf will be
+ * automatically be released
+ */
+ zbar_image_set_userdata(zimg, pixbuf);
+ zbar_image_set_data(zimg, gdk_pixbuf_get_pixels(pixbuf), datalen,
+ zbar_gtk_release_pixbuf);
+#ifdef DEBUG_ZBARGTK
+ g_message("colorspace=%d nchannels=%d bps=%d type=%.4s(%08lx)\n"
+ "\tpitch=%d width=%d height=%d datalen=0x%lx\n",
+ colorspace, nchannels, bps, (char *)&type, type, pitch, width,
+ height, datalen);
+#endif
+ return (TRUE);
+}
+
+static inline gboolean zbar_gtk_video_open(ZBarGtk *self,
+ const char *video_device)
+{
+ ZBarGtkPrivate *zbar = ZBAR_GTK_PRIVATE(self->_private);
+ gboolean video_opened = FALSE;
+
+ zbar->video_opened = FALSE;
+ if (zbar->idle_id)
+ g_object_notify(G_OBJECT(self), "video-opened");
+
+ if (zbar->window) {
+ /* ensure old video doesn't have image ref
+ * (FIXME handle video destroyed w/images outstanding)
+ */
+ zbar_window_draw(zbar->window, NULL);
+ gtk_widget_queue_draw(GTK_WIDGET(self));
+ }
+
+ if (zbar->video) {
+ zbar_video_destroy(zbar->video);
+ zbar->video = NULL;
+ }
+
+ if (video_device && video_device[0] && zbar->idle_id) {
+ /* create video
+ * FIXME video should support re-open
+ */
+ zbar->video = zbar_video_create();
+ g_assert(zbar->video);
+
+ if (zbar_video_open(zbar->video, video_device)) {
+ zbar_video_error_spew(zbar->video, 0);
+ zbar_video_destroy(zbar->video);
+ zbar->video = NULL;
+ /* FIXME error propagation */
+ return (FALSE);
+ }
+
+ /* negotiation accesses the window format list,
+ * so we hold the lock for this part
+ */
+ if (zbar->video_width && zbar->video_height)
+ zbar_video_request_size(zbar->video, zbar->video_width,
+ zbar->video_height);
+
+ video_opened = !zbar_negotiate_format(zbar->video, zbar->window);
+
+ if (video_opened) {
+ zbar->req_width = zbar_video_get_width(zbar->video);
+ zbar->req_height = zbar_video_get_height(zbar->video);
+ }
+ gtk_widget_queue_resize(GTK_WIDGET(self));
+
+ zbar->video_opened = video_opened;
+ if (zbar->idle_id)
+ g_object_notify(G_OBJECT(self), "video-opened");
+ }
+
+ return (video_opened);
+}
+
+static inline int zbar_gtk_process_image(ZBarGtk *self, zbar_image_t *image)
+{
+ ZBarGtkPrivate *zbar;
+ int rc;
+
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ if (!image)
+ return (-1);
+
+ zbar_image_t *tmp =
+ zbar_image_convert(image, zbar_fourcc('Y', '8', '0', '0'));
+ if (!tmp)
+ return (-1);
+
+ zbar_image_scanner_recycle_image(zbar->scanner, image);
+ rc = zbar_scan_image(zbar->scanner, tmp);
+ zbar_image_set_symbols(image, zbar_image_get_symbols(tmp));
+ zbar_image_destroy(tmp);
+ if (rc < 0)
+ return (rc);
+
+ if (rc && zbar->idle_id) {
+ /* update decode results */
+ const zbar_symbol_t *sym;
+ for (sym = zbar_image_first_symbol(image); sym;
+ sym = zbar_symbol_next(sym))
+ if (!zbar_symbol_get_count(sym)) {
+ zbar_symbol_type_t type = zbar_symbol_get_type(sym);
+ const char *data = zbar_symbol_get_data(sym);
+ g_signal_emit(self, zbar_gtk_signals[DECODED], 0, type, data);
+
+ /* FIXME skip this when unconnected? */
+ gchar *text =
+ g_strconcat(zbar_get_symbol_name(type), ":", data, NULL);
+ g_signal_emit(self, zbar_gtk_signals[DECODED_TEXT], 0, text);
+ g_free(text);
+ }
+ }
+
+ if (zbar->window) {
+ rc = zbar_window_draw(zbar->window, image);
+ gtk_widget_queue_draw(GTK_WIDGET(self));
+ } else
+ rc = -1;
+
+ return (rc);
+}
+
+static gboolean zbar_processing_idle_callback(gpointer data)
+{
+ ZBarGtk *self = data;
+ ZBarGtkPrivate *zbar = ZBAR_GTK_PRIVATE(self->_private);
+ GValue *msg = g_async_queue_try_pop(zbar->queue);
+ GType type;
+
+ if (!msg) {
+ if (zbar->video_enabled_state) {
+ zbar_image_t *image = zbar_video_next_image(zbar->video);
+ if (zbar_gtk_process_image(self, image) < 0)
+ zbar->video_enabled_state = FALSE;
+ if (image)
+ zbar_image_destroy(image);
+
+ if (zbar->video_enabled_state)
+ return TRUE;
+
+ if (zbar_video_enable(zbar->video, 0)) {
+ zbar_video_error_spew(zbar->video, 0);
+ zbar->video_enabled_state = FALSE;
+ }
+
+ zbar_image_scanner_enable_cache(zbar->scanner, 0);
+ /* release video image and revert to logo */
+ if (zbar->window) {
+ zbar_window_draw(zbar->window, NULL);
+ gtk_widget_queue_draw(GTK_WIDGET(self));
+ }
+
+ /* must have been an error while streaming */
+ zbar_gtk_video_open(self, NULL);
+ }
+
+ return TRUE;
+ }
+
+ g_assert(G_IS_VALUE(msg));
+
+ type = G_VALUE_TYPE(msg);
+ if (type == G_TYPE_INT) {
+ /* video state change */
+ int state = g_value_get_int(msg);
+ if (state < 0) {
+ /* error identifying state */
+ g_value_unset(msg);
+ g_free(msg);
+ return TRUE;
+ }
+ g_assert(state >= 0 && state <= 1);
+ zbar->video_enabled_state = (state != 0);
+ } else if (type == G_TYPE_STRING) {
+ /* open new video device */
+ const char *video_device = g_value_get_string(msg);
+
+ zbar->video_enabled_state = zbar_gtk_video_open(self, video_device);
+ } else if (type == GDK_TYPE_PIXBUF) {
+ /* scan provided image and broadcast results */
+ zbar_image_t *image = zbar_image_create();
+ GdkPixbuf *pixbuf = GDK_PIXBUF(g_value_dup_object(msg));
+
+ if (zbar_gtk_image_from_pixbuf(image, pixbuf))
+ zbar_gtk_process_image(self, image);
+ else
+ g_object_unref(pixbuf);
+ zbar_image_destroy(image);
+ } else {
+ gchar *dbg = g_strdup_value_contents(msg);
+
+ g_warning("unknown message type (%x) received: %s\n", (unsigned)type,
+ dbg);
+ g_free(dbg);
+ }
+ g_value_unset(msg);
+ g_free(msg);
+ msg = NULL;
+
+ if (zbar->video_enabled_state) {
+ /* release reference to any previous pixbuf */
+ if (zbar->window)
+ zbar_window_draw(zbar->window, NULL);
+
+ if (zbar_video_enable(zbar->video, 1)) {
+ zbar_video_error_spew(zbar->video, 0);
+ zbar->video_enabled_state = FALSE;
+ return TRUE;
+ }
+ zbar_image_scanner_enable_cache(zbar->scanner, 1);
+
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+static void zbar_gtk_realize(GtkWidget *widget)
+{
+ ZBarGtk *self = ZBAR_GTK(widget);
+ ZBarGtkPrivate *zbar;
+ GdkWindowAttr attributes;
+ GtkAllocation allocation;
+
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ gtk_widget_set_realized(widget, TRUE);
+
+#if GTK_CHECK_VERSION(3, 14, 0)
+ // FIXME: this is deprecated after 3.14 - no idea what replaces it
+#else
+ gtk_widget_set_double_buffered(widget, FALSE);
+#endif
+
+ gtk_widget_get_allocation(widget, &allocation);
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = (gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK);
+
+ GdkWindow *window = gdk_window_new(gtk_widget_get_parent_window(widget),
+ &attributes, GDK_WA_X | GDK_WA_Y);
+ gtk_widget_set_window(widget, window);
+ gdk_window_set_user_data(window, widget);
+#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 18
+ gdk_window_set_background_pattern(window, NULL);
+#elif GTK_MAJOR_VERSION < 3
+ gdk_window_set_back_pixmap(window, NULL, TRUE);
+#endif
+
+ /* attach zbar_window to underlying X window */
+#ifdef HAVE_X
+ if (zbar_window_attach(zbar->window,
+ GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
+ GDK_WINDOW_XID(window)))
+#elif defined(_WIN32)
+ if (zbar_window_attach(zbar->window, GDK_WINDOW_HWND(window), 0))
+#endif
+ zbar_window_error_spew(zbar->window, 0);
+}
+
+static inline GValue *zbar_gtk_new_value(GType type)
+{
+ return (g_value_init(g_malloc0(sizeof(GValue)), type));
+}
+
+static void zbar_gtk_unrealize(GtkWidget *widget)
+{
+ ZBarGtkPrivate *zbar;
+ GdkWindow *window;
+ ZBarGtk *self;
+
+ if (gtk_widget_get_mapped(widget))
+ gtk_widget_unmap(widget);
+
+ gtk_widget_set_mapped(widget, FALSE);
+ self = ZBAR_GTK(widget);
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ if (zbar->video_enabled) {
+ zbar->video_enabled = FALSE;
+ GValue *msg = zbar_gtk_new_value(G_TYPE_INT);
+ g_value_set_int(msg, 0);
+ g_async_queue_push(zbar->queue, msg);
+ }
+
+ zbar_window_attach(zbar->window, NULL, 0);
+
+ gtk_widget_set_realized(widget, FALSE);
+
+ window = gtk_widget_get_window(widget);
+ gdk_window_set_user_data(window, NULL);
+ gdk_window_destroy(window);
+ gtk_widget_set_window(widget, NULL);
+}
+
+#if GTK_MAJOR_VERSION >= 3
+
+static void zbar_get_preferred_width(GtkWidget *widget, gint *minimum_width,
+ gint *natural_width)
+{
+ unsigned int screen_width;
+ ZBarGtkPrivate *zbar;
+ ZBarGtk *self;
+#if GTK_CHECK_VERSION(3, 22, 0)
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+ GdkRectangle geo;
+#endif
+ self = ZBAR_GTK(widget);
+
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ /* use native video size (max) if available,
+ * arbitrary defaults otherwise.
+ * video attributes maintained under main gui idle handler
+ */
+#if GTK_CHECK_VERSION(3, 22, 0)
+ display = gdk_display_get_default();
+ monitor = gdk_display_get_monitor(display, 0);
+ gdk_monitor_get_geometry(monitor, &geo);
+
+ screen_width = geo.width;
+#else
+ screen_width = gdk_screen_width();
+#endif
+
+ if (zbar->req_width > screen_width) {
+ float scale = screen_width * .8 / zbar->req_width;
+
+ zbar->req_width *= scale;
+ zbar->req_height *= scale;
+ }
+
+ *minimum_width = zbar->req_width;
+ *natural_width = zbar->req_width;
+}
+
+static void zbar_get_preferred_height(GtkWidget *widget, gint *minimum_height,
+ gint *natural_height)
+{
+ ZBarGtk *self = ZBAR_GTK(widget);
+ unsigned int screen_height;
+#if GTK_CHECK_VERSION(3, 22, 0)
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+ GdkRectangle geo;
+#endif
+
+ if (!self->_private)
+ return;
+ ZBarGtkPrivate *zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ /* use native video size (max) if available,
+ * arbitrary defaults otherwise.
+ * video attributes maintained under main gui idle handler
+ */
+#if GTK_CHECK_VERSION(3, 22, 0)
+ display = gdk_display_get_default();
+ monitor = gdk_display_get_monitor(display, 0);
+ gdk_monitor_get_geometry(monitor, &geo);
+
+ screen_height = geo.height;
+#else
+ screen_height = gdk_screen_height();
+#endif
+
+ if (zbar->req_height > screen_height) {
+ float scale = screen_height * .8 / zbar->req_height;
+
+ zbar->req_width *= scale;
+ zbar->req_height *= scale;
+ }
+
+ *minimum_height = zbar->req_height;
+ *natural_height = zbar->req_height;
+}
+
+static gboolean zbar_gtk_scale_draw(GtkWidget *widget, cairo_t *cr)
+{
+ // NOTE: should we change something here?
+
+ ZBarGtk *self = ZBAR_GTK(widget);
+
+ if (!self->_private)
+ return (FALSE);
+ ZBarGtkPrivate *zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ if (gtk_widget_get_visible(widget) && gtk_widget_get_mapped(widget) &&
+ zbar_window_redraw(zbar->window))
+ return (TRUE);
+ return (FALSE);
+}
+
+#else
+static void zbar_gtk_size_request(GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ ZBarGtk *self = ZBAR_GTK(widget);
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ /* use native video size (max) if available,
+ * arbitrary defaults otherwise.
+ * video attributes maintained under main gui idle handler
+ */
+ requisition->width = zbar->req_width;
+ requisition->height = zbar->req_height;
+}
+
+static gboolean zbar_gtk_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+ ZBarGtk *self = ZBAR_GTK(widget);
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return (FALSE);
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ if (gtk_widget_get_visible(widget) && gtk_widget_get_mapped(widget) &&
+ zbar_window_redraw(zbar->window))
+ return (TRUE);
+ return (FALSE);
+}
+#endif
+
+static void zbar_gtk_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+ ZBarGtk *self = ZBAR_GTK(widget);
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ (*GTK_WIDGET_CLASS(zbar_gtk_parent_class)->size_allocate)(widget,
+ allocation);
+ if (zbar->window)
+ zbar_window_resize(zbar->window, allocation->width, allocation->height);
+}
+
+void zbar_gtk_scan_image(ZBarGtk *self, GdkPixbuf *img)
+{
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ g_object_ref(G_OBJECT(img));
+
+ /* queue for scanning by the processor idle handler */
+ GValue *msg = zbar_gtk_new_value(GDK_TYPE_PIXBUF);
+
+ /* this grabs a new reference to the image,
+ * eventually released by the processor idle handler
+ */
+ g_value_set_object(msg, img);
+ g_async_queue_push(zbar->queue, msg);
+}
+
+const char *zbar_gtk_get_video_device(ZBarGtk *self)
+{
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return (NULL);
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+ if (zbar->video_device)
+ return (zbar->video_device);
+ else
+ return ("");
+}
+
+void zbar_gtk_set_video_device(ZBarGtk *self, const char *video_device)
+{
+ ZBarGtkPrivate *zbar;
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ g_free((void *)zbar->video_device);
+ zbar->video_device = g_strdup(video_device);
+ zbar->video_enabled = video_device && video_device[0];
+
+ /* push another copy to processor idle handler */
+ GValue *msg = zbar_gtk_new_value(G_TYPE_STRING);
+ if (video_device)
+ g_value_set_string(msg, video_device);
+ else
+ g_value_set_static_string(msg, "");
+ g_async_queue_push(zbar->queue, msg);
+
+ g_object_freeze_notify(G_OBJECT(self));
+ g_object_notify(G_OBJECT(self), "video-device");
+ g_object_notify(G_OBJECT(self), "video-enabled");
+ g_object_thaw_notify(G_OBJECT(self));
+}
+
+gboolean zbar_gtk_get_video_enabled(ZBarGtk *self)
+{
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return (FALSE);
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+ return (zbar->video_enabled);
+}
+
+void zbar_gtk_set_video_enabled(ZBarGtk *self, gboolean video_enabled)
+{
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ video_enabled = (video_enabled != FALSE);
+ if (zbar->video_enabled != video_enabled) {
+ zbar->video_enabled = video_enabled;
+
+ /* push state change to processor idle handler */
+ GValue *msg = zbar_gtk_new_value(G_TYPE_INT);
+ g_value_set_int(msg, zbar->video_enabled);
+ g_async_queue_push(zbar->queue, msg);
+
+ g_object_notify(G_OBJECT(self), "video-enabled");
+ }
+}
+
+gboolean zbar_gtk_get_video_opened(ZBarGtk *self)
+{
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return (FALSE);
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ return (zbar->video_opened);
+}
+
+void zbar_gtk_request_video_size(ZBarGtk *self, int width, int height)
+{
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private || width < 0 || height < 0)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ zbar->req_width = zbar->video_width = width;
+ zbar->req_height = zbar->video_height = height;
+ gtk_widget_queue_resize(GTK_WIDGET(self));
+}
+
+static void zbar_gtk_set_property(GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ ZBarGtk *self = ZBAR_GTK(object);
+
+ switch (prop_id) {
+ case PROP_VIDEO_DEVICE:
+ zbar_gtk_set_video_device(self, g_value_get_string(value));
+ break;
+ case PROP_VIDEO_ENABLED:
+ zbar_gtk_set_video_enabled(self, g_value_get_boolean(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void zbar_gtk_get_property(GObject *object, guint prop_id, GValue *value,
+ GParamSpec *pspec)
+{
+ ZBarGtk *self = ZBAR_GTK(object);
+ ZBarGtkPrivate *zbar;
+
+ if (!self->_private)
+ return;
+ zbar = ZBAR_GTK_PRIVATE(self->_private);
+
+ switch (prop_id) {
+ case PROP_VIDEO_DEVICE:
+ if (zbar->video_device)
+ g_value_set_string(value, zbar->video_device);
+ else
+ g_value_set_static_string(value, "");
+ break;
+ case PROP_VIDEO_ENABLED:
+ g_value_set_boolean(value, zbar->video_enabled);
+ break;
+ case PROP_VIDEO_OPENED:
+ g_value_set_boolean(value, zbar->video_opened);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void zbar_gtk_init(ZBarGtk *self)
+{
+ ZBarGtkPrivate *zbar = g_object_new(ZBAR_TYPE_GTK_PRIVATE, NULL);
+ self->_private = (void *)zbar;
+
+ zbar->window = zbar_window_create();
+ g_assert(zbar->window);
+
+ zbar->req_width = zbar->video_width = DEFAULT_WIDTH;
+ zbar->req_height = zbar->video_height = DEFAULT_HEIGHT;
+
+ /* use a queue to signalize about the need to handle decoding and video */
+ zbar->queue = g_async_queue_new();
+ zbar->idle_id = g_idle_add(zbar_processing_idle_callback, self);
+ zbar->video_enabled_state = FALSE;
+
+ /* Prepare for idle handler */
+ g_object_ref(zbar);
+ g_assert(zbar->queue);
+ g_async_queue_ref(zbar->queue);
+
+ zbar->scanner = zbar_image_scanner_create();
+ g_assert(zbar->scanner);
+}
+
+static void zbar_gtk_dispose(GObject *object)
+{
+ ZBarGtk *self = ZBAR_GTK(object);
+
+ if (!self->_private)
+ return;
+
+ ZBarGtkPrivate *zbar = ZBAR_GTK_PRIVATE(self->_private);
+ self->_private = NULL;
+
+ g_free((void *)zbar->video_device);
+ zbar->video_device = NULL;
+
+ /* signal processor idle handler to exit */
+ GValue *msg = zbar_gtk_new_value(G_TYPE_INT);
+ g_value_set_int(msg, -1);
+ g_async_queue_push(zbar->queue, msg);
+ zbar->idle_id = 0;
+
+ /* there are no external references which might call other APIs */
+ g_async_queue_unref(zbar->queue);
+
+ g_object_unref(G_OBJECT(zbar));
+}
+
+static void zbar_gtk_private_finalize(GObject *object)
+{
+ ZBarGtkPrivate *zbar = ZBAR_GTK_PRIVATE(object);
+
+ if (zbar->idle_id) {
+ if (zbar->window)
+ zbar_window_draw(zbar->window, NULL);
+ g_object_unref(zbar);
+ g_source_remove(zbar->idle_id);
+ zbar->idle_id = 0;
+ }
+
+ if (zbar->window) {
+ zbar_window_destroy(zbar->window);
+ zbar->window = NULL;
+ }
+ if (zbar->scanner) {
+ zbar_image_scanner_destroy(zbar->scanner);
+ zbar->scanner = NULL;
+ }
+ if (zbar->video) {
+ zbar_video_destroy(zbar->video);
+ zbar->video = NULL;
+ }
+ g_async_queue_unref(zbar->queue);
+ zbar->queue = NULL;
+}
+
+static void zbar_gtk_class_init(ZBarGtkClass *klass)
+{
+ GParamSpec *p;
+
+ zbar_gtk_parent_class = g_type_class_peek_parent(klass);
+
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ object_class->dispose = zbar_gtk_dispose;
+ object_class->set_property = zbar_gtk_set_property;
+ object_class->get_property = zbar_gtk_get_property;
+
+ GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
+ widget_class->realize = zbar_gtk_realize;
+ widget_class->unrealize = zbar_gtk_unrealize;
+#if GTK_MAJOR_VERSION >= 3
+ widget_class->get_preferred_width = zbar_get_preferred_width;
+ widget_class->get_preferred_height = zbar_get_preferred_height;
+ widget_class->draw = zbar_gtk_scale_draw;
+#else
+ widget_class->size_request = zbar_gtk_size_request;
+ widget_class->expose_event = zbar_gtk_expose;
+#endif
+ widget_class->size_allocate = zbar_gtk_size_allocate;
+
+ widget_class->unmap = NULL;
+
+ zbar_gtk_signals[DECODED] =
+ g_signal_new("decoded", G_TYPE_FROM_CLASS(object_class),
+ G_SIGNAL_RUN_CLEANUP,
+ G_STRUCT_OFFSET(ZBarGtkClass, decoded), NULL, NULL,
+ zbar_marshal_VOID__INT_STRING, G_TYPE_NONE, 2, G_TYPE_INT,
+ G_TYPE_STRING);
+
+ zbar_gtk_signals[DECODED_TEXT] = g_signal_new(
+ "decoded-text", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_CLEANUP,
+ G_STRUCT_OFFSET(ZBarGtkClass, decoded_text), NULL, NULL,
+ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ p = g_param_spec_string("video-device", "Video device",
+ "the platform specific name of the video device",
+ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property(object_class, PROP_VIDEO_DEVICE, p);
+
+ p = g_param_spec_boolean("video-enabled", "Video enabled",
+ "controls streaming from the video device", FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property(object_class, PROP_VIDEO_ENABLED, p);
+
+ p = g_param_spec_boolean("video-opened", "Video opened",
+ "current opened state of the video device", FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property(object_class, PROP_VIDEO_OPENED, p);
+}
+
+static void zbar_gtk_private_class_init(ZBarGtkPrivateClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = zbar_gtk_private_finalize;
+}
+
+static GType zbar_gtk_private_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(ZBarGtkPrivateClass),
+ NULL,
+ NULL,
+ (GClassInitFunc)zbar_gtk_private_class_init,
+ NULL,
+ NULL,
+ sizeof(ZBarGtkPrivate),
+ };
+ type =
+ g_type_register_static(G_TYPE_OBJECT, "ZBarGtkPrivate", &info, 0);
+ }
+ return (type);
+}
+
+GtkWidget *zbar_gtk_new(void)
+{
+ return (GTK_WIDGET(g_object_new(ZBAR_TYPE_GTK, NULL)));
+}
diff --git a/gtk/zbargtkprivate.h b/gtk/zbargtkprivate.h
new file mode 100644
index 0000000..f570393
--- /dev/null
+++ b/gtk/zbargtkprivate.h
@@ -0,0 +1,98 @@
+/*------------------------------------------------------------------------
+ * Copyright 2008-2009 (c) Jeff Brown <spadix@users.sourceforge.net>
+ *
+ * This file is part of the ZBar Bar Code Reader.
+ *
+ * The ZBar Bar Code Reader is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * The ZBar Bar Code Reader 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 Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License
+ * along with the ZBar Bar Code Reader; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * http://sourceforge.net/projects/zbar
+ *------------------------------------------------------------------------*/
+#ifndef __ZBAR_GTK_PRIVATE_H__
+#define __ZBAR_GTK_PRIVATE_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include <zbar.h>
+
+G_BEGIN_DECLS
+
+#define ZBAR_TYPE_GTK_PRIVATE (zbar_gtk_private_get_type())
+#define ZBAR_GTK_PRIVATE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), ZBAR_TYPE_GTK_PRIVATE, ZBarGtkPrivate))
+#define ZBAR_GTK_PRIVATE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), ZBAR_TYPE_GTK_PRIVATE, \
+ ZBarGtkPrivateClass))
+#define ZBAR_IS_GTK_PRIVATE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), ZBAR_TYPE_GTK_PRIVATE))
+#define ZBAR_IS_GTK_PRIVATE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), ZBAR_TYPE_GTK_PRIVATE))
+#define ZBAR_GTK_PRIVATE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), ZBAR_TYPE_GTK_PRIVATE, \
+ ZBarGtkPrivateClass))
+
+/* zbar widget processor thread shared/private data */
+typedef struct _ZBarGtkPrivate {
+ GObject object;
+
+ /* these are all owned by the main gui thread */
+ gint idle_id;
+ const char *video_device;
+ gboolean video_enabled, video_enabled_state;
+
+ /* messages are queued from the gui thread to the processor thread.
+ * each message is a GValue containing one of:
+ * - G_TYPE_INT: state change
+ * 1 = video enable
+ * 0 = video disable
+ * -1 = terminate processor thread
+ * - G_TYPE_STRING: a named video device to open ("" to close)
+ * - GDK_TYPE_PIXBUF: an image to scan
+ */
+ GAsyncQueue *queue;
+
+ /* current processor state is shared:
+ * written by processor thread just after opening video or
+ * scanning an image, read by main gui thread during size_request.
+ * protected by main gui lock
+ */
+ unsigned req_width, req_height;
+ unsigned video_width, video_height;
+ gboolean video_opened;
+
+ /* window is shared: owned by main gui thread.
+ * processor thread only calls draw() and negotiate_format().
+ * protected by main gui lock (and internal lock)
+ */
+ zbar_window_t *window;
+
+ /* video and scanner are owned by the processor thread */
+ zbar_video_t *video;
+ zbar_image_scanner_t *scanner;
+
+} ZBarGtkPrivate;
+
+typedef struct _ZBarGtkPrivateClass {
+ GObjectClass parent_class;
+
+} ZBarGtkPrivateClass;
+
+static GType zbar_gtk_private_get_type(void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif
diff --git a/gtk/zbarmarshal.list b/gtk/zbarmarshal.list
new file mode 100644
index 0000000..158cf7a
--- /dev/null
+++ b/gtk/zbarmarshal.list
@@ -0,0 +1 @@
+VOID:INT,STRING