implement printer media query, implement status bar with clock
This commit is contained in:
parent
7a9ebb08b9
commit
4443c43ad6
7 changed files with 274 additions and 64 deletions
2
Makefile
2
Makefile
|
@ -1,6 +1,6 @@
|
||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
PKGCONFIG = $(shell which pkg-config)
|
PKGCONFIG = $(shell which pkg-config)
|
||||||
CFLAGS = $(shell $(PKGCONFIG) --cflags gtk+-3.0 gstreamer-1.0 gstreamer-video-1.0 libgphoto2) -Wl,--export-dynamic -rdynamic
|
CFLAGS = $(shell $(PKGCONFIG) --cflags gtk+-3.0 gstreamer-1.0 gstreamer-video-1.0 libgphoto2) -Wl,--export-dynamic -rdynamic -g
|
||||||
LIBS = $(shell $(PKGCONFIG) --libs gtk+-3.0 gstreamer-1.0 gstreamer-video-1.0 libgphoto2 gmodule-export-2.0)
|
LIBS = $(shell $(PKGCONFIG) --libs gtk+-3.0 gstreamer-1.0 gstreamer-video-1.0 libgphoto2 gmodule-export-2.0)
|
||||||
GLIB_COMPILE_RESOURCES = $(shell $(PKGCONFIG) --variable=glib_compile_resources gio-2.0)
|
GLIB_COMPILE_RESOURCES = $(shell $(PKGCONFIG) --variable=glib_compile_resources gio-2.0)
|
||||||
GLIB_COMPILE_SCHEMAS = $(shell $(PKGCONFIG) --variable=glib_compile_schemas gio-2.0)
|
GLIB_COMPILE_SCHEMAS = $(shell $(PKGCONFIG) --variable=glib_compile_schemas gio-2.0)
|
||||||
|
|
160
photobooth.c
160
photobooth.c
|
@ -13,8 +13,6 @@
|
||||||
* distributed other than under the conditions noted above.
|
* distributed other than under the conditions noted above.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// gcc -Wall -g `pkg-config gstreamer-1.0 gstreamer-video-1.0 libgphoto2 gtk+-3.0 gtk+-x11-3.0 --cflags --libs` photobooth.c focus.c -o photobooth
|
|
||||||
|
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -37,14 +35,17 @@ typedef struct _PhotoBoothPrivate PhotoBoothPrivate;
|
||||||
struct _PhotoBoothPrivate
|
struct _PhotoBoothPrivate
|
||||||
{
|
{
|
||||||
PhotoBoothWindow *win;
|
PhotoBoothWindow *win;
|
||||||
GSettings *settings;
|
guint32 countdown;
|
||||||
guint countdown;
|
gchar *printer_backend;
|
||||||
|
gint prints_remaining;
|
||||||
GstElement *audio_playbin;
|
GstElement *audio_playbin;
|
||||||
GstVideoRectangle video_size;
|
GstVideoRectangle video_size;
|
||||||
|
GThread *capture_thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define DEFAULT_AUDIOFILE_COUNTDOWN "/net/home/fraxinas/microcontroller/photobooth/beep.m4a"
|
#define DEFAULT_AUDIOFILE_COUNTDOWN "/net/home/fraxinas/microcontroller/photobooth/beep.m4a"
|
||||||
#define DEFAULT_COUNTDOWN 5
|
#define DEFAULT_COUNTDOWN 5
|
||||||
|
#define DEFAULT_PRINTER_BACKEND "mitsu9550"
|
||||||
#define PRINT_WIDTH 2076
|
#define PRINT_WIDTH 2076
|
||||||
#define PRINT_HEIGHT 1384
|
#define PRINT_HEIGHT 1384
|
||||||
#define PREVIEW_WIDTH 640
|
#define PREVIEW_WIDTH 640
|
||||||
|
@ -55,6 +56,8 @@ enum
|
||||||
{
|
{
|
||||||
ARG_0,
|
ARG_0,
|
||||||
ARG_COUNTDOWN,
|
ARG_COUNTDOWN,
|
||||||
|
ARG_PRINTER_BACKEND,
|
||||||
|
ARG_PRINTS_REMAINING,
|
||||||
};
|
};
|
||||||
|
|
||||||
G_DEFINE_TYPE_WITH_PRIVATE (PhotoBooth, photo_booth, GTK_TYPE_APPLICATION);
|
G_DEFINE_TYPE_WITH_PRIVATE (PhotoBooth, photo_booth, GTK_TYPE_APPLICATION);
|
||||||
|
@ -82,6 +85,7 @@ static gboolean photo_booth_preview (PhotoBooth *pb);
|
||||||
static void photo_booth_snapshot_start (PhotoBooth *pb);
|
static void photo_booth_snapshot_start (PhotoBooth *pb);
|
||||||
static gboolean photo_booth_snapshot_prepare (PhotoBooth *pb);
|
static gboolean photo_booth_snapshot_prepare (PhotoBooth *pb);
|
||||||
static gboolean photo_booth_snapshot_taken (PhotoBooth *pb);
|
static gboolean photo_booth_snapshot_taken (PhotoBooth *pb);
|
||||||
|
static void photo_booth_get_printer_status (PhotoBooth *pb);
|
||||||
static void photo_booth_print (PhotoBooth *pb);
|
static void photo_booth_print (PhotoBooth *pb);
|
||||||
|
|
||||||
/* libgphoto2 */
|
/* libgphoto2 */
|
||||||
|
@ -109,14 +113,24 @@ static void photo_booth_class_init (PhotoBoothClass *klass)
|
||||||
gp_log_add_func(GP_LOG_ERROR, _gphoto_err, NULL);
|
gp_log_add_func(GP_LOG_ERROR, _gphoto_err, NULL);
|
||||||
|
|
||||||
gobject_class->finalize = photo_booth_finalize;
|
gobject_class->finalize = photo_booth_finalize;
|
||||||
|
gobject_class->dispose = photo_booth_dispose;
|
||||||
gobject_class->set_property = photo_booth_set_property;
|
gobject_class->set_property = photo_booth_set_property;
|
||||||
gobject_class->get_property = photo_booth_get_property;
|
gobject_class->get_property = photo_booth_get_property;
|
||||||
gapplication_class->activate = photo_booth_activate;
|
gapplication_class->activate = photo_booth_activate;
|
||||||
gapplication_class->open = photo_booth_open;
|
gapplication_class->open = photo_booth_open;
|
||||||
|
|
||||||
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_COUNTDOWN,
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_COUNTDOWN,
|
||||||
g_param_spec_uint ("countdown", "Shutter delay (s)",
|
g_param_spec_uint ("countdown", "Shutter delay (s)",
|
||||||
"Shutter actuation delay countdown in seconds", 0, 60, DEFAULT_COUNTDOWN,
|
"Specify shutter actuation delay countdown in seconds", 0, 60, DEFAULT_COUNTDOWN,
|
||||||
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PRINTER_BACKEND,
|
||||||
|
g_param_spec_string ("printer-backend", "Gutenprint backend",
|
||||||
|
"Specify which Gutenprint backend to use", DEFAULT_PRINTER_BACKEND,
|
||||||
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PRINTS_REMAINING,
|
||||||
|
g_param_spec_int ("prints-remaining", "Print media remaining",
|
||||||
|
"Show remaining prints on media roll (-1 = Unknown)", -1, 1000, -1,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void photo_booth_init (PhotoBooth *pb)
|
static void photo_booth_init (PhotoBooth *pb)
|
||||||
|
@ -157,10 +171,7 @@ static void photo_booth_init (PhotoBooth *pb)
|
||||||
g_application_quit (G_APPLICATION (pb));
|
g_application_quit (G_APPLICATION (pb));
|
||||||
}
|
}
|
||||||
|
|
||||||
pb->capture_thread = NULL;
|
priv->capture_thread = NULL;
|
||||||
pb->capture_thread = g_thread_try_new ("gphoto-capture", (GThreadFunc) photo_booth_capture_thread_func, pb, NULL);
|
|
||||||
|
|
||||||
priv->settings = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void photo_booth_setup_window (PhotoBooth *pb)
|
static void photo_booth_setup_window (PhotoBooth *pb)
|
||||||
|
@ -170,7 +181,9 @@ static void photo_booth_setup_window (PhotoBooth *pb)
|
||||||
priv->win = photo_booth_window_new (pb);
|
priv->win = photo_booth_window_new (pb);
|
||||||
gtk_window_present (GTK_WINDOW (priv->win));
|
gtk_window_present (GTK_WINDOW (priv->win));
|
||||||
g_signal_connect (G_OBJECT (priv->win), "destroy", G_CALLBACK (photo_booth_window_destroyed_signal), pb);
|
g_signal_connect (G_OBJECT (priv->win), "destroy", G_CALLBACK (photo_booth_window_destroyed_signal), pb);
|
||||||
|
priv->capture_thread = g_thread_try_new ("gphoto-capture", (GThreadFunc) photo_booth_capture_thread_func, pb, NULL);
|
||||||
photo_booth_setup_gstreamer (pb);
|
photo_booth_setup_gstreamer (pb);
|
||||||
|
photo_booth_get_printer_status (pb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void photo_booth_activate (GApplication *app)
|
static void photo_booth_activate (GApplication *app)
|
||||||
|
@ -188,10 +201,12 @@ static void photo_booth_open (GApplication *app, GFile **files, gint n_files, c
|
||||||
static void photo_booth_finalize (GObject *object)
|
static void photo_booth_finalize (GObject *object)
|
||||||
{
|
{
|
||||||
PhotoBooth *pb = PHOTO_BOOTH (object);
|
PhotoBooth *pb = PHOTO_BOOTH (object);
|
||||||
|
PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb);
|
||||||
|
|
||||||
GST_INFO_OBJECT (pb, "finalize");
|
GST_INFO_OBJECT (pb, "finalize");
|
||||||
SEND_COMMAND (pb, CONTROL_STOP);
|
SEND_COMMAND (pb, CONTROL_STOP);
|
||||||
photo_booth_flush_pipe (pb->video_fd);
|
photo_booth_flush_pipe (pb->video_fd);
|
||||||
g_thread_join (pb->capture_thread);
|
g_thread_join (priv->capture_thread);
|
||||||
if (pb->cam_info)
|
if (pb->cam_info)
|
||||||
photo_booth_cam_close (&pb->cam_info);
|
photo_booth_cam_close (&pb->cam_info);
|
||||||
if (pb->video_fd)
|
if (pb->video_fd)
|
||||||
|
@ -205,7 +220,7 @@ static void photo_booth_dispose (GObject *object)
|
||||||
{
|
{
|
||||||
PhotoBoothPrivate *priv;
|
PhotoBoothPrivate *priv;
|
||||||
priv = photo_booth_get_instance_private (PHOTO_BOOTH (object));
|
priv = photo_booth_get_instance_private (PHOTO_BOOTH (object));
|
||||||
g_clear_object (&priv->settings);
|
g_free (priv->printer_backend);
|
||||||
G_OBJECT_CLASS (photo_booth_parent_class)->dispose (object);
|
G_OBJECT_CLASS (photo_booth_parent_class)->dispose (object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,6 +310,9 @@ static void photo_booth_set_property (GObject * object, guint prop_id, const GVa
|
||||||
case ARG_COUNTDOWN:
|
case ARG_COUNTDOWN:
|
||||||
priv->countdown = g_value_get_uint (value);
|
priv->countdown = g_value_get_uint (value);
|
||||||
break;
|
break;
|
||||||
|
case ARG_PRINTER_BACKEND:
|
||||||
|
priv->printer_backend = g_strdup (g_value_get_string (value));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -308,7 +326,13 @@ static void photo_booth_get_property (GObject * object, guint prop_id, GValue *
|
||||||
|
|
||||||
switch (prop_id) {
|
switch (prop_id) {
|
||||||
case ARG_COUNTDOWN:
|
case ARG_COUNTDOWN:
|
||||||
g_value_set_int (value, priv->countdown);
|
g_value_set_uint (value, priv->countdown);
|
||||||
|
break;
|
||||||
|
case ARG_PRINTER_BACKEND:
|
||||||
|
g_value_set_string (value, priv->printer_backend);
|
||||||
|
break;
|
||||||
|
case ARG_PRINTS_REMAINING:
|
||||||
|
g_value_set_int (value, priv->prints_remaining);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
@ -319,12 +343,12 @@ static void photo_booth_get_property (GObject * object, guint prop_id, GValue *
|
||||||
static void photo_booth_capture_thread_func (PhotoBooth *pb)
|
static void photo_booth_capture_thread_func (PhotoBooth *pb)
|
||||||
{
|
{
|
||||||
PhotoboothCaptureThreadState state = CAPTURE_INIT;
|
PhotoboothCaptureThreadState state = CAPTURE_INIT;
|
||||||
|
PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb);
|
||||||
GST_DEBUG_OBJECT (pb, "enter capture thread fd = %d", pb->video_fd);
|
|
||||||
|
|
||||||
CameraFile *gp_file = NULL;
|
CameraFile *gp_file = NULL;
|
||||||
int gpret, captured_frames = 0;
|
int gpret, captured_frames = 0;
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (pb, "enter capture thread fd = %d", pb->video_fd);
|
||||||
|
|
||||||
if (gp_file_new_from_fd (&gp_file, pb->video_fd) != GP_OK)
|
if (gp_file_new_from_fd (&gp_file, pb->video_fd) != GP_OK)
|
||||||
{
|
{
|
||||||
GST_ERROR_OBJECT (pb, "couldn't start capture thread because gp_file_new_from_fd (%d) failed!", pb->video_fd);
|
GST_ERROR_OBJECT (pb, "couldn't start capture thread because gp_file_new_from_fd (%d) failed!", pb->video_fd);
|
||||||
|
@ -348,8 +372,10 @@ static void photo_booth_capture_thread_func (PhotoBooth *pb)
|
||||||
state = CAPTURE_VIDEO;
|
state = CAPTURE_VIDEO;
|
||||||
g_main_context_invoke (NULL, (GSourceFunc) photo_booth_preview, pb);
|
g_main_context_invoke (NULL, (GSourceFunc) photo_booth_preview, pb);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
|
gtk_label_set_text (priv->win->status, "no camera connected!");
|
||||||
GST_INFO_OBJECT (pb, "no camera info.");
|
GST_INFO_OBJECT (pb, "no camera info.");
|
||||||
|
}
|
||||||
timeout = 5000;
|
timeout = 5000;
|
||||||
}
|
}
|
||||||
else if (state == CAPTURE_PAUSED)
|
else if (state == CAPTURE_PAUSED)
|
||||||
|
@ -399,11 +425,11 @@ static void photo_booth_capture_thread_func (PhotoBooth *pb)
|
||||||
{
|
{
|
||||||
if (pb->cam_info)
|
if (pb->cam_info)
|
||||||
{
|
{
|
||||||
|
gtk_label_set_text (priv->win->status, "taking photo...");
|
||||||
ret = photo_booth_take_photo (pb->cam_info);
|
ret = photo_booth_take_photo (pb->cam_info);
|
||||||
if (ret)
|
if (ret)
|
||||||
g_main_context_invoke (NULL, (GSourceFunc) photo_booth_snapshot_taken, pb);
|
g_main_context_invoke (NULL, (GSourceFunc) photo_booth_snapshot_taken, pb);
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
GST_ERROR_OBJECT (pb, "taking photo failed!");
|
GST_ERROR_OBJECT (pb, "taking photo failed!");
|
||||||
state = CAPTURE_INIT;
|
state = CAPTURE_INIT;
|
||||||
}
|
}
|
||||||
|
@ -656,7 +682,7 @@ static gboolean photo_booth_bus_callback (GstBus *bus, GstMessage *message, Phot
|
||||||
GstStateChange transition = (GstStateChange)GST_STATE_TRANSITION (old_state, new_state);
|
GstStateChange transition = (GstStateChange)GST_STATE_TRANSITION (old_state, new_state);
|
||||||
GstElement *src = GST_ELEMENT (GST_MESSAGE_SRC (message));
|
GstElement *src = GST_ELEMENT (GST_MESSAGE_SRC (message));
|
||||||
GST_LOG ("%" GST_PTR_FORMAT " state transition %s -> %s", src, gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT(transition)), gst_element_state_get_name(GST_STATE_TRANSITION_NEXT(transition)));
|
GST_LOG ("%" GST_PTR_FORMAT " state transition %s -> %s", src, gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT(transition)), gst_element_state_get_name(GST_STATE_TRANSITION_NEXT(transition)));
|
||||||
if (src == pb->video_bin && transition == GST_STATE_CHANGE_PAUSED_TO_PLAYING)
|
if (src == pb->video_bin && transition == GST_STATE_CHANGE_PAUSED_TO_PLAYING && pb->state != PB_STATE_WAITING_FOR_ANSWER)
|
||||||
{
|
{
|
||||||
GST_DEBUG ("video_bin GST_STATE_CHANGE_READY_TO_PAUSED -> CAPTURE VIDEO!");
|
GST_DEBUG ("video_bin GST_STATE_CHANGE_READY_TO_PAUSED -> CAPTURE VIDEO!");
|
||||||
SEND_COMMAND (pb, CONTROL_VIDEO);
|
SEND_COMMAND (pb, CONTROL_VIDEO);
|
||||||
|
@ -678,9 +704,6 @@ static gboolean photo_booth_bus_callback (GstBus *bus, GstMessage *message, Phot
|
||||||
case GST_MESSAGE_STREAM_START:
|
case GST_MESSAGE_STREAM_START:
|
||||||
{
|
{
|
||||||
GST_DEBUG ("GST_MESSAGE_STREAM_START! state=%i", pb->state);
|
GST_DEBUG ("GST_MESSAGE_STREAM_START! state=%i", pb->state);
|
||||||
// if (pb->state == PB_STATE_ASKING)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
|
@ -727,6 +750,7 @@ static void photo_booth_video_widget_ready (PhotoBooth *pb)
|
||||||
|
|
||||||
static gboolean photo_booth_preview (PhotoBooth *pb)
|
static gboolean photo_booth_preview (PhotoBooth *pb)
|
||||||
{
|
{
|
||||||
|
PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb);
|
||||||
GstPad *pad;
|
GstPad *pad;
|
||||||
if (pb->video_block_id)
|
if (pb->video_block_id)
|
||||||
{
|
{
|
||||||
|
@ -747,6 +771,7 @@ static gboolean photo_booth_preview (PhotoBooth *pb)
|
||||||
gst_element_set_state (pb->video_bin, GST_STATE_PLAYING);
|
gst_element_set_state (pb->video_bin, GST_STATE_PLAYING);
|
||||||
GST_DEBUG_OBJECT (pb, "photo_booth_preview done");
|
GST_DEBUG_OBJECT (pb, "photo_booth_preview done");
|
||||||
pb->state = PB_STATE_PREVIEW;
|
pb->state = PB_STATE_PREVIEW;
|
||||||
|
gtk_label_set_text (priv->win->status, "camera ready, showing preview video");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,8 +779,6 @@ void photo_booth_background_clicked (GtkWidget *widget, GdkEventButton *event, P
|
||||||
{
|
{
|
||||||
PhotoBoothPrivate *priv;
|
PhotoBoothPrivate *priv;
|
||||||
PhotoBooth *pb = PHOTO_BOOTH_FROM_WINDOW (win);
|
PhotoBooth *pb = PHOTO_BOOTH_FROM_WINDOW (win);
|
||||||
GST_DEBUG_OBJECT (widget, "photo_booth_background_clicked state=%d", pb->state);
|
|
||||||
|
|
||||||
switch (pb->state) {
|
switch (pb->state) {
|
||||||
case PB_STATE_PREVIEW:
|
case PB_STATE_PREVIEW:
|
||||||
{
|
{
|
||||||
|
@ -777,6 +800,72 @@ void photo_booth_background_clicked (GtkWidget *widget, GdkEventButton *event, P
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void photo_booth_get_printer_status (PhotoBooth *pb)
|
||||||
|
{
|
||||||
|
PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb);
|
||||||
|
gchar *label_string;
|
||||||
|
gchar *backend_environment = g_strdup_printf ("BACKEND=%s", priv->printer_backend);
|
||||||
|
gchar *argv[] = { "/usr/lib/cups/backend/gutenprint52+usb", "-m", NULL };
|
||||||
|
gchar *envp[] = { backend_environment, NULL };
|
||||||
|
gchar *output = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
gint remain = -1;
|
||||||
|
gint ret = 0;
|
||||||
|
|
||||||
|
if (g_spawn_sync (NULL, argv, envp, G_SPAWN_DEFAULT, NULL, NULL, NULL, &output, &ret, &error))
|
||||||
|
{
|
||||||
|
GMatchInfo *match_info;
|
||||||
|
GRegex *regex;
|
||||||
|
if (ret == 0)
|
||||||
|
{
|
||||||
|
regex = g_regex_new ("INFO: Media type\\s.*?: (?<code>\\d+) \\((?<size>.*?)\\)\nINFO: Media remaining\\s.*?: (?<remain>\\d{3})/(?<total>\\d{3})\n", G_REGEX_MULTILINE|G_REGEX_DOTALL, 0, &error);
|
||||||
|
if (error) {
|
||||||
|
g_critical ("%s\n", error->message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (g_regex_match (regex, output, 0, &match_info))
|
||||||
|
{
|
||||||
|
guint code = atoi(g_match_info_fetch_named(match_info, "code"));
|
||||||
|
gchar *size = g_match_info_fetch_named(match_info, "size");
|
||||||
|
remain = atoi(g_match_info_fetch_named(match_info, "remain"));
|
||||||
|
guint total = atoi(g_match_info_fetch_named(match_info, "total"));
|
||||||
|
label_string = g_strdup_printf("printer %s online. media (%s) %i prints remaining", priv->printer_backend, size, remain);
|
||||||
|
GST_INFO_OBJECT (pb, "printer %s status: media code %i (%s) prints remaining %i of %i", priv->printer_backend, code, size, remain, total);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
label_string = g_strdup_printf("can't parse printer backend output");
|
||||||
|
GST_ERROR_OBJECT (pb, "%s: '%s'", label_string, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
regex = g_regex_new ("ERROR: Printer open failure", G_REGEX_MULTILINE|G_REGEX_DOTALL, 0, &error);
|
||||||
|
if (g_regex_match (regex, output, 0, &match_info))
|
||||||
|
{
|
||||||
|
label_string = g_strdup_printf("printer %s off-line", priv->printer_backend);
|
||||||
|
GST_WARNING_OBJECT (pb, "%s", label_string);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
label_string = g_strdup_printf("can't parse printer backend output");
|
||||||
|
GST_ERROR_OBJECT (pb, "%s: '%s'", label_string, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_free (output);
|
||||||
|
g_match_info_free (match_info);
|
||||||
|
g_regex_unref (regex);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
label_string = g_strdup_printf("can't spawn %s", argv[0]);
|
||||||
|
GST_ERROR_OBJECT (pb, "%s %s %s (%s)", label_string, argv[1], envp[0], error->message);
|
||||||
|
g_error_free (error);
|
||||||
|
}
|
||||||
|
priv->prints_remaining = remain;
|
||||||
|
gtk_label_set_text (priv->win->status_printer, label_string);
|
||||||
|
g_free (label_string);
|
||||||
|
g_free (backend_environment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static void photo_booth_snapshot_start (PhotoBooth *pb)
|
static void photo_booth_snapshot_start (PhotoBooth *pb)
|
||||||
{
|
{
|
||||||
PhotoBoothPrivate *priv;
|
PhotoBoothPrivate *priv;
|
||||||
|
@ -914,12 +1003,14 @@ static gboolean photo_booth_take_photo (CameraInfo *cam_info)
|
||||||
|
|
||||||
static gboolean photo_booth_snapshot_taken (PhotoBooth *pb)
|
static gboolean photo_booth_snapshot_taken (PhotoBooth *pb)
|
||||||
{
|
{
|
||||||
|
PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb);
|
||||||
GstElement *appsrc;
|
GstElement *appsrc;
|
||||||
GstBuffer *buffer;
|
GstBuffer *buffer;
|
||||||
GstFlowReturn flowret;
|
GstFlowReturn flowret;
|
||||||
GstPad *pad;
|
GstPad *pad;
|
||||||
|
|
||||||
GST_INFO_OBJECT (pb, "photo_booth_snapshot_taken size=%lu", pb->cam_info->size);
|
GST_INFO_OBJECT (pb, "photo_booth_snapshot_taken size=%lu", pb->cam_info->size);
|
||||||
|
gtk_label_set_text (priv->win->status, "processing photo...");
|
||||||
|
|
||||||
appsrc = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-appsrc");
|
appsrc = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-appsrc");
|
||||||
buffer = gst_buffer_new_wrapped (pb->cam_info->data, pb->cam_info->size);
|
buffer = gst_buffer_new_wrapped (pb->cam_info->data, pb->cam_info->size);
|
||||||
|
@ -928,7 +1019,6 @@ static gboolean photo_booth_snapshot_taken (PhotoBooth *pb)
|
||||||
if (flowret != GST_FLOW_OK)
|
if (flowret != GST_FLOW_OK)
|
||||||
GST_ERROR_OBJECT (appsrc, "couldn't push %" GST_PTR_FORMAT " to appsrc", buffer);
|
GST_ERROR_OBJECT (appsrc, "couldn't push %" GST_PTR_FORMAT " to appsrc", buffer);
|
||||||
gst_object_unref (appsrc);
|
gst_object_unref (appsrc);
|
||||||
GST_INFO_OBJECT (pb, "photo_booth_snapshot now waiting for user input... PB_STATE_ASKING");
|
|
||||||
|
|
||||||
SEND_COMMAND (pb, CONTROL_PAUSE);
|
SEND_COMMAND (pb, CONTROL_PAUSE);
|
||||||
|
|
||||||
|
@ -953,7 +1043,7 @@ static GstPadProbeReturn photo_booth_catch_photo_buffer (GstPad * pad, GstPadPro
|
||||||
photo_booth_window_set_spinner (priv->win, FALSE);
|
photo_booth_window_set_spinner (priv->win, FALSE);
|
||||||
GST_INFO_OBJECT (priv->win->button_yes, "PB_STATE_TAKING_PHOTO -> PB_STATE_PROCESS_PHOTO. hide spinner, show button");
|
GST_INFO_OBJECT (priv->win->button_yes, "PB_STATE_TAKING_PHOTO -> PB_STATE_PROCESS_PHOTO. hide spinner, show button");
|
||||||
gtk_widget_show (GTK_WIDGET (priv->win->button_yes));
|
gtk_widget_show (GTK_WIDGET (priv->win->button_yes));
|
||||||
|
gtk_label_set_text (priv->win->status, "Print Photo? Touch background to cancel!");
|
||||||
return GST_PAD_PROBE_PASS;
|
return GST_PAD_PROBE_PASS;
|
||||||
}
|
}
|
||||||
if (pb->state == PB_STATE_PROCESS_PHOTO)
|
if (pb->state == PB_STATE_PROCESS_PHOTO)
|
||||||
|
@ -1014,10 +1104,22 @@ void photo_booth_button_yes_clicked (GtkButton *button, PhotoBoothWindow *win)
|
||||||
static void photo_booth_print (PhotoBooth *pb)
|
static void photo_booth_print (PhotoBooth *pb)
|
||||||
{
|
{
|
||||||
PhotoBoothPrivate *priv;
|
PhotoBoothPrivate *priv;
|
||||||
GST_DEBUG_OBJECT (pb, "!!!PRINT!!!");
|
priv = photo_booth_get_instance_private (pb);
|
||||||
|
GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "photo_booth_photo_print.dot");
|
||||||
|
photo_booth_get_printer_status (pb);
|
||||||
|
GST_DEBUG_OBJECT (pb, "!!!PRINT!!! prints_remaining=%i", priv->prints_remaining);
|
||||||
|
|
||||||
|
if (priv->prints_remaining > 1)
|
||||||
|
{
|
||||||
priv = photo_booth_get_instance_private (pb);
|
priv = photo_booth_get_instance_private (pb);
|
||||||
gtk_widget_hide (GTK_WIDGET (priv->win->button_yes));
|
gtk_widget_hide (GTK_WIDGET (priv->win->button_yes));
|
||||||
GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "photo_booth_photo_print.dot");
|
gtk_label_set_text (priv->win->status, "PRINTING................");
|
||||||
|
}
|
||||||
|
else if (priv->prints_remaining == -1) {
|
||||||
|
gtk_label_set_text (priv->win->status, "can't print... no printer connected!!!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
gtk_label_set_text (priv->win->status, "can't print... out of paper!!!");
|
||||||
}
|
}
|
||||||
|
|
||||||
PhotoBooth *photo_booth_new (void)
|
PhotoBooth *photo_booth_new (void)
|
||||||
|
@ -1033,7 +1135,7 @@ int main (int argc, char *argv[])
|
||||||
PhotoBooth *pb;
|
PhotoBooth *pb;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
gst_init (&argc, &argv);
|
gst_init (0, NULL);
|
||||||
|
|
||||||
pb = photo_booth_new ();
|
pb = photo_booth_new ();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.countdown_label {
|
.countdown_label {
|
||||||
color: rgba (150, 200, 200, 255);
|
color: rgba (150, 200, 200, 1);
|
||||||
text-shadow: 10px 10px #000000
|
text-shadow: 10px 10px #000000
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 80px;
|
font-size: 80px;
|
||||||
|
@ -11,9 +11,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.button_yes {
|
.button_yes {
|
||||||
color: rgba (0, 0, 110, 255);
|
color: rgba (0, 0, 110, 1);
|
||||||
background: rgba (168, 233, 255, 200);
|
background: rgba (168, 233, 255, 0.9);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 80px;
|
font-size: 80px;
|
||||||
padding: 20px 20px 20px 20px;
|
padding: 20px 20px 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statusbox {
|
||||||
|
background: rgba (0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status_label {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,6 @@ struct _PhotoBooth
|
||||||
gulong photo_block_id;
|
gulong photo_block_id;
|
||||||
|
|
||||||
int control_sock[2];
|
int control_sock[2];
|
||||||
GThread *capture_thread;
|
|
||||||
PhotoboothState state;
|
PhotoboothState state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
123
photobooth.ui
123
photobooth.ui
|
@ -13,15 +13,12 @@
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="events">GDK_BUTTON_PRESS_MASK</property>
|
<property name="events">GDK_BUTTON_PRESS_MASK</property>
|
||||||
<signal name="button-press-event" handler="photo_booth_background_clicked" swapped="no"/>
|
<signal name="button-press-event" handler="photo_booth_background_clicked" swapped="no"/>
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child type="overlay">
|
<child type="overlay">
|
||||||
<object class="GtkLabel" id="countdown_label">
|
<object class="GtkLabel" id="countdown_label">
|
||||||
<property name="width_request">400</property>
|
<property name="width_request">400</property>
|
||||||
<property name="height_request">300</property>
|
<property name="height_request">300</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="opacity">0.9</property>
|
<property name="opacity">0.90000000000000002</property>
|
||||||
<property name="halign">center</property>
|
<property name="halign">center</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="label" translatable="yes">label</property>
|
<property name="label" translatable="yes">label</property>
|
||||||
|
@ -36,26 +33,6 @@
|
||||||
<class name="countdown_label"/>
|
<class name="countdown_label"/>
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
|
||||||
<property name="index">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child type="overlay">
|
|
||||||
<object class="GtkSpinner" id="spinner">
|
|
||||||
<property name="width_request">200</property>
|
|
||||||
<property name="height_request">200</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="opacity">0.8</property>
|
|
||||||
<property name="halign">center</property>
|
|
||||||
<property name="valign">center</property>
|
|
||||||
<property name="hexpand">False</property>
|
|
||||||
<property name="vexpand">False</property>
|
|
||||||
<property name="active">True</property>
|
|
||||||
<style>
|
|
||||||
<class name="transparentbg"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
<packing>
|
<packing>
|
||||||
<property name="index">2</property>
|
<property name="index">2</property>
|
||||||
</packing>
|
</packing>
|
||||||
|
@ -89,7 +66,103 @@
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="pass_through">True</property>
|
<property name="pass_through">True</property>
|
||||||
<property name="index">2</property>
|
<property name="index">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child type="overlay">
|
||||||
|
<object class="GtkBox" id="statusbox">
|
||||||
|
<property name="name">statusbox</property>
|
||||||
|
<property name="width_request">800</property>
|
||||||
|
<property name="height_request">40</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">end</property>
|
||||||
|
<property name="margin_left">10</property>
|
||||||
|
<property name="margin_right">10</property>
|
||||||
|
<property name="margin_bottom">10</property>
|
||||||
|
<property name="homogeneous">True</property>
|
||||||
|
<property name="baseline_position">bottom</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="status_clock">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="label" translatable="yes">label</property>
|
||||||
|
<property name="single_line_mode">True</property>
|
||||||
|
<property name="track_visited_links">False</property>
|
||||||
|
<style>
|
||||||
|
<class name="status_label"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="status">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="label" translatable="yes">label</property>
|
||||||
|
<property name="justify">center</property>
|
||||||
|
<style>
|
||||||
|
<class name="status_label"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="status_printer">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="label" translatable="yes">label</property>
|
||||||
|
<property name="justify">right</property>
|
||||||
|
<style>
|
||||||
|
<class name="status_label"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="statusbox"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="index">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child type="overlay">
|
||||||
|
<object class="GtkSpinner" id="spinner">
|
||||||
|
<property name="width_request">200</property>
|
||||||
|
<property name="height_request">200</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="opacity">0.80000000000000004</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="hexpand">False</property>
|
||||||
|
<property name="vexpand">False</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="transparentbg"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="index">9</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
#include <time.h>
|
||||||
#include "photobooth.h"
|
#include "photobooth.h"
|
||||||
#include "photoboothwin.h"
|
#include "photoboothwin.h"
|
||||||
|
|
||||||
|
@ -33,6 +33,9 @@ G_DEFINE_TYPE_WITH_PRIVATE (PhotoBoothWindow, photo_booth_window, GTK_TYPE_APPLI
|
||||||
GST_DEBUG_CATEGORY_STATIC (photo_booth_windows_debug);
|
GST_DEBUG_CATEGORY_STATIC (photo_booth_windows_debug);
|
||||||
#define GST_CAT_DEFAULT photo_booth_windows_debug
|
#define GST_CAT_DEFAULT photo_booth_windows_debug
|
||||||
|
|
||||||
|
gboolean _pbw_tick_countdown (PhotoBoothWindow *win);
|
||||||
|
gboolean _pbw_clock_tick (GtkLabel *status_clock);
|
||||||
|
|
||||||
static void photo_booth_window_class_init (PhotoBoothWindowClass *klass)
|
static void photo_booth_window_class_init (PhotoBoothWindowClass *klass)
|
||||||
{
|
{
|
||||||
GST_DEBUG_CATEGORY_INIT (photo_booth_windows_debug, "photoboothwin", GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_BLUE, "PhotoBoothWindow");
|
GST_DEBUG_CATEGORY_INIT (photo_booth_windows_debug, "photoboothwin", GST_DEBUG_BOLD | GST_DEBUG_FG_WHITE | GST_DEBUG_BG_BLUE, "PhotoBoothWindow");
|
||||||
|
@ -41,6 +44,9 @@ static void photo_booth_window_class_init (PhotoBoothWindowClass *klass)
|
||||||
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, spinner);
|
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, spinner);
|
||||||
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, countdown_label);
|
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, countdown_label);
|
||||||
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, button_yes);
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, button_yes);
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, status_clock);
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, status);
|
||||||
|
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), PhotoBoothWindow, status_printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void photo_booth_window_init (PhotoBoothWindow *win)
|
static void photo_booth_window_init (PhotoBoothWindow *win)
|
||||||
|
@ -58,6 +64,7 @@ static void photo_booth_window_init (PhotoBoothWindow *win)
|
||||||
gtk_style_context_add_provider_for_screen (screen, (GtkStyleProvider *)cssprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
gtk_style_context_add_provider_for_screen (screen, (GtkStyleProvider *)cssprovider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
g_object_unref (cssfile);
|
g_object_unref (cssfile);
|
||||||
}
|
}
|
||||||
|
g_timeout_add (1000, (GSourceFunc) _pbw_clock_tick, win->status_clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void photo_booth_window_add_gtkgstwidget (PhotoBoothWindow *win, GtkWidget *gtkgstwidget)
|
void photo_booth_window_add_gtkgstwidget (PhotoBoothWindow *win, GtkWidget *gtkgstwidget)
|
||||||
|
@ -72,6 +79,21 @@ void photo_booth_window_add_gtkgstwidget (PhotoBoothWindow *win, GtkWidget *gtkg
|
||||||
win->gtkgstwidget = gtkgstwidget;
|
win->gtkgstwidget = gtkgstwidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean _pbw_clock_tick (GtkLabel *status_clock)
|
||||||
|
{
|
||||||
|
gchar clockstr[200];
|
||||||
|
time_t now;
|
||||||
|
struct tm *now_tm;
|
||||||
|
time (&now);
|
||||||
|
now_tm = localtime (&now);
|
||||||
|
if (!now_tm)
|
||||||
|
return TRUE;
|
||||||
|
if (strftime(clockstr, sizeof(clockstr), "%A, %d. %B %Y\t%T", now_tm) == 0)
|
||||||
|
return TRUE;
|
||||||
|
gtk_label_set_text (status_clock, clockstr);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
void photo_booth_window_set_spinner (PhotoBoothWindow *win, gboolean active)
|
void photo_booth_window_set_spinner (PhotoBoothWindow *win, gboolean active)
|
||||||
{
|
{
|
||||||
PhotoBoothWindowPrivate *priv;
|
PhotoBoothWindowPrivate *priv;
|
||||||
|
@ -97,6 +119,8 @@ gboolean _pbw_tick_countdown (PhotoBoothWindow *win)
|
||||||
GST_DEBUG ("_pbw_tick_countdown %i", priv->countdown);
|
GST_DEBUG ("_pbw_tick_countdown %i", priv->countdown);
|
||||||
if (priv->countdown > 0)
|
if (priv->countdown > 0)
|
||||||
{
|
{
|
||||||
|
gchar *status_str = g_strdup_printf ("Taking photo in %d seconds...", priv->countdown);
|
||||||
|
gtk_label_set_text (win->status, status_str);
|
||||||
str = g_strdup_printf ("%d...", priv->countdown);
|
str = g_strdup_printf ("%d...", priv->countdown);
|
||||||
gtk_label_set_text (priv->countdown_label, str);
|
gtk_label_set_text (priv->countdown_label, str);
|
||||||
g_free (str);
|
g_free (str);
|
||||||
|
|
|
@ -35,6 +35,7 @@ struct _PhotoBoothWindow
|
||||||
GtkApplicationWindow parent;
|
GtkApplicationWindow parent;
|
||||||
GtkWidget *gtkgstwidget;
|
GtkWidget *gtkgstwidget;
|
||||||
GtkButton *button_yes;
|
GtkButton *button_yes;
|
||||||
|
GtkLabel *status_clock, *status, *status_printer;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _PhotoBoothWindowClass
|
struct _PhotoBoothWindowClass
|
||||||
|
|
Loading…
Add table
Reference in a new issue