/*
This file is part of darktable,
copyright (c) 2010-2012 Henrik Andersson.
copyright (c) 2012 Tobias Ellinghaus.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "common/camera_control.h"
#include "common/exif.h"
#include "control/control.h"
#include <gphoto2/gphoto2-file.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
/***/
typedef enum _camctl_camera_job_type_t
{
/** Start a scan of devices and announce new and removed. */
_JOB_TYPE_DETECT_DEVICES,
/** Remotly executes a capture. */
_JOB_TYPE_EXECUTE_CAPTURE,
/** Fetch a preview for live view. */
_JOB_TYPE_EXECUTE_LIVE_VIEW,
/** Read a copy of remote camera config into cache. */
_JOB_TYPE_READ_CONFIG,
/** Writes changed properties in cache to camera */
_JOB_TYPE_WRITE_CONFIG,
/** Set's a property in config cache. \todo This shouldn't be a job in jobqueue !? */
_JOB_TYPE_SET_PROPERTY_STRING,
_JOB_TYPE_SET_PROPERTY_CHOICE,
/** For some reason stopping live view needs to pass an int, not a string. */
_JOB_TYPE_SET_PROPERTY_INT,
/** get's a property from config cache. \todo This shouldn't be a job in jobqueue !? */
_JOB_TYPE_GET_PROPERTY
} _camctl_camera_job_type_t;
typedef struct _camctl_camera_job_t
{
_camctl_camera_job_type_t type;
} _camctl_camera_job_t;
typedef struct _camctl_camera_set_property_string_job_t
{
_camctl_camera_job_type_t type;
char *name;
char *value;
} _camctl_camera_set_property_string_job_t;
typedef struct _camctl_camera_set_property_choice_job_t
{
_camctl_camera_job_type_t type;
char *name;
int value;
} _camctl_camera_set_property_choice_job_t;
typedef struct _camctl_camera_set_property_int_job_t
{
_camctl_camera_job_type_t type;
char *name;
int value;
} _camctl_camera_set_property_int_job_t;
/** Initializes camera */
gboolean _camera_initialize(const dt_camctl_t *c, dt_camera_t *cam);
/** Poll camera events, this one is called from the thread handling the camera. */
void _camera_poll_events(const dt_camctl_t *c, const dt_camera_t *cam);
/** Lock camera control and notify listener. \note Locks mutex and signals CAMERA_CONTROL_BUSY. \remarks all
* interface functions available to host application should lock/unlock its operation. */
void _camctl_lock(const dt_camctl_t *c, const dt_camera_t *cam);
/** Lock camera control and notify listener. \note Locks mutex and signals CAMERA_CONTROL_AVAILABLE. \see
* _camctl_lock() */
void _camctl_unlock(const dt_camctl_t *c);
/** Updates the cached configuration with a copy of camera configuration */
void _camera_configuration_update(const dt_camctl_t *c, const dt_camera_t *camera);
/** Commit the changes in cached configuration to the camera configuration */
void _camera_configuration_commit(const dt_camctl_t *c, const dt_camera_t *camera);
/** Merges source with destination and notifies listeners of the changes. \param notify_all If true every
* widget is notified as change */
void _camera_configuration_merge(const dt_camctl_t *c, const dt_camera_t *camera, CameraWidget *source,
CameraWidget *destination, gboolean notify_all);
/** Put a job on the queue */
void _camera_add_job(const dt_camctl_t *c, const dt_camera_t *camera, gpointer job);
/** Get a job from the queue */
gpointer _camera_get_job(const dt_camctl_t *c, const dt_camera_t *camera);
static void _camera_process_job(const dt_camctl_t *c, const dt_camera_t *camera, gpointer job);
/** Dispatch functions for listener interfaces */
const char *_dispatch_request_image_path(const dt_camctl_t *c, time_t *exif_time, const dt_camera_t *camera);
const char *_dispatch_request_image_filename(const dt_camctl_t *c, const char *filename,
time_t *exif_time, const dt_camera_t *camera);
void _dispatch_camera_image_downloaded(const dt_camctl_t *c, const dt_camera_t *camera, const char *filename);
void _dispatch_camera_connected(const dt_camctl_t *c, const dt_camera_t *camera);
void _dispatch_camera_disconnected(const dt_camctl_t *c, const dt_camera_t *camera);
void _dispatch_control_status(const dt_camctl_t *c, dt_camctl_status_t status);
void _dispatch_camera_error(const dt_camctl_t *c, const dt_camera_t *camera, dt_camera_error_t error);
int _dispatch_camera_storage_image_filename(const dt_camctl_t *c, const dt_camera_t *camera,
const char *filename, CameraFile *preview, CameraFile *exif);
void _dispatch_camera_property_value_changed(const dt_camctl_t *c, const dt_camera_t *camera,
const char *name, const char *value);
void _dispatch_camera_property_accessibility_changed(const dt_camctl_t *c, const dt_camera_t *camera,
const char *name, gboolean read_only);
void _dispatch_camera_exif_data_changed(const dt_camctl_t *c,const time_t exif_time,
const dt_camera_t *camera);
/** Helper function to destroy a dt_camera_t object */
static void dt_camctl_camera_destroy(dt_camera_t *cam);
/** Wrapper to asynchronously look for cameras */
static int _detect_cameras_callback(dt_job_t *job);
static int logid = 0;
void _gphoto_log25(GPLogLevel level, const char *domain, const char *log, void *data)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] %s %s\n", domain, log);
}
#ifndef HAVE_GPHOTO_25_OR_NEWER
void _gphoto_log(GPLogLevel level, const char *domain, const char *format, va_list args, void *data)
{
char log[4096] = { 0 };
vsnprintf(log, sizeof(log), format, args);
_gphoto_log25(level, domain, log, data);
}
#endif
void _enable_debug()
{
#ifdef HAVE_GPHOTO_25_OR_NEWER
logid = gp_log_add_func(GP_LOG_DATA, (GPLogFunc)_gphoto_log25, NULL);
#else
logid = gp_log_add_func(GP_LOG_DATA, (GPLogFunc)_gphoto_log, NULL);
#endif
}
void _disable_debug()
{
gp_log_remove_func(logid);
}
// static void _idle_func_dispatch(GPContext *context, void *data)
// {
// // Let's do gtk main event iteration for not locking the ui
// gdk_threads_enter();
// if(gtk_events_pending()) gtk_main_iteration();
// gdk_threads_leave();
// }
static void _error_func_dispatch25(GPContext *context, const char *text, void *data)
{
dt_camctl_t *camctl = (dt_camctl_t *)data;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] gphoto2 error: %s\n", text);
if(strstr(text, "PTP"))
{
/* remove camera for camctl camera list */
GList *ci = g_list_find(camctl->cameras, camctl->active_camera);
if(ci) camctl->cameras = g_list_remove(camctl->cameras, ci);
/* notify client of camera connection broken */
_dispatch_camera_error(camctl, camctl->active_camera, CAMERA_CONNECTION_BROKEN);
/* notify client of camera disconnection */
_dispatch_camera_disconnected(camctl, camctl->active_camera);
}
}
static void _status_func_dispatch25(GPContext *context, const char *text, void *data)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] gphoto2 status: %s\n", text);
}
static void _message_func_dispatch25(GPContext *context, const char *text, void *data)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] gphoto2 message: %s\n", text);
}
#ifndef HAVE_GPHOTO_25_OR_NEWER
static void _status_func_dispatch(GPContext *context, const char *format, va_list args, void *data)
{
char buffer[4096];
vsnprintf(buffer, sizeof(buffer), format, args);
_status_func_dispatch25(context, buffer, data);
}
static void _error_func_dispatch(GPContext *context, const char *format, va_list args, void *data)
{
char buffer[4096];
vsnprintf(buffer, sizeof(buffer), format, args);
_error_func_dispatch25(context, buffer, data);
}
static void _message_func_dispatch(GPContext *context, const char *format, va_list args, void *data)
{
char buffer[4096];
vsnprintf(buffer, sizeof(buffer), format, args);
_message_func_dispatch25(context, buffer, data);
}
#endif
static gboolean _camera_timeout_job(gpointer data)
{
dt_camera_t *cam = (dt_camera_t *)data;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Calling timeout func for camera %p.\n", cam);
cam->timeout(cam->gpcam, cam->gpcontext);
return TRUE;
}
static int _camera_start_timeout_func(Camera *c, unsigned int timeout, CameraTimeoutFunc func, void *data)
{
dt_print(DT_DEBUG_CAMCTL,
"[camera_control] start timeout %d seconds for camera %p requested by driver.\n", timeout, data);
dt_camera_t *cam = (dt_camera_t *)data;
cam->timeout = func;
return g_timeout_add_seconds(timeout, _camera_timeout_job, cam);
}
static void _camera_stop_timeout_func(Camera *c, int id, void *data)
{
dt_camera_t *cam = (dt_camera_t *)data;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Removing timeout %d for camera %p.\n", id, cam);
g_source_remove(id);
cam->timeout = NULL;
}
void _camera_add_job(const dt_camctl_t *c, const dt_camera_t *camera, gpointer job)
{
dt_camera_t *cam = (dt_camera_t *)camera;
dt_pthread_mutex_lock(&cam->jobqueue_lock);
cam->jobqueue = g_list_append(cam->jobqueue, job);
dt_pthread_mutex_unlock(&cam->jobqueue_lock);
}
gpointer _camera_get_job(const dt_camctl_t *c, const dt_camera_t *camera)
{
dt_camera_t *cam = (dt_camera_t *)camera;
dt_pthread_mutex_lock(&cam->jobqueue_lock);
gpointer job = NULL;
if(g_list_length(cam->jobqueue) > 0)
{
job = g_list_nth_data(cam->jobqueue, 0);
cam->jobqueue = g_list_remove(cam->jobqueue, job);
}
dt_pthread_mutex_unlock(&cam->jobqueue_lock);
return job;
}
static void _camera_process_job(const dt_camctl_t *c, const dt_camera_t *camera, gpointer job)
{
dt_camera_t *cam = (dt_camera_t *)camera;
_camctl_camera_job_t *j = (_camctl_camera_job_t *)job;
switch(j->type)
{
case _JOB_TYPE_EXECUTE_CAPTURE:
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] executing remote camera capture job\n");
CameraFilePath fp;
int res = GP_OK;
if((res = gp_camera_capture(camera->gpcam, GP_CAPTURE_IMAGE, &fp, c->gpcontext)) == GP_OK)
{
CameraFile *destination;
const char *output_path = _dispatch_request_image_path(c, NULL, camera);
if(!output_path) output_path = "/tmp";
const char *fname = _dispatch_request_image_filename(c, fp.name, NULL, cam);
if(!fname) break;
char *output = g_build_filename(output_path, fname, (char *)NULL);
int handle = open(output, O_CREAT | O_WRONLY, 0666);
if(handle != -1)
{
gp_file_new_from_fd(&destination, handle);
if(gp_camera_file_get(camera->gpcam, fp.folder, fp.name, GP_FILE_TYPE_NORMAL, destination,
c->gpcontext) == GP_OK)
{
// Notify listeners of captured image
_dispatch_camera_image_downloaded(c, camera, output);
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to download file %s\n", output);
close(handle);
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to download file %s\n", output);
g_free(output);
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] capture job failed to capture image: %s\n",
gp_result_as_string(res));
}
break;
case _JOB_TYPE_EXECUTE_LIVE_VIEW:
{
CameraFile *fp = NULL;
int res = GP_OK;
const gchar *data = NULL;
unsigned long int data_size = 0;
gp_file_new(&fp);
if((res = gp_camera_capture_preview(cam->gpcam, fp, c->gpcontext)) != GP_OK)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] live view failed to capture preview: %s\n",
gp_result_as_string(res));
}
else if((res = gp_file_get_data_and_size(fp, &data, &data_size)) != GP_OK)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] live view failed to get preview data: %s\n",
gp_result_as_string(res));
}
else
{
// everything worked
GError *error = NULL;
GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type(
"image/jpeg", &error); // there were cases where GDKPixbufLoader failed to recognize the JPEG
if(error)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] live view failed to create jpeg image loader: %s\n",
error->message);
g_error_free(error);
}
else
{
if(gdk_pixbuf_loader_write(loader, (guchar *)data, data_size, NULL) == TRUE)
{
dt_pthread_mutex_lock(&cam->live_view_pixbuf_mutex);
if(cam->live_view_pixbuf != NULL) g_object_unref(cam->live_view_pixbuf);
cam->live_view_pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
dt_pthread_mutex_unlock(&cam->live_view_pixbuf_mutex);
}
}
gdk_pixbuf_loader_close(loader, NULL);
}
if(fp) gp_file_free(fp);
dt_pthread_mutex_unlock(&cam->live_view_synch);
dt_control_queue_redraw_center();
}
break;
case _JOB_TYPE_SET_PROPERTY_STRING:
{
_camctl_camera_set_property_string_job_t *spj = (_camctl_camera_set_property_string_job_t *)job;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] executing set camera config job %s=%s\n", spj->name,
spj->value);
CameraWidget *config; // Copy of camera configuration
CameraWidget *widget;
gp_camera_get_config(cam->gpcam, &config, c->gpcontext);
if(gp_widget_get_child_by_name(config, spj->name, &widget) == GP_OK)
{
gp_widget_set_value(widget, spj->value);
gp_camera_set_config(cam->gpcam, config, c->gpcontext);
}
/* dt_pthread_mutex_lock( &cam->config_lock );
CameraWidget *widget;
if( gp_widget_get_child_by_name ( camera->configuration, spj->name, &widget) == GP_OK) {
gp_widget_set_value ( widget , spj->value);
//gp_widget_set_changed( widget, 1 );
cam->config_changed=TRUE;
}
dt_pthread_mutex_unlock( &cam->config_lock);*/
g_free(spj->name);
g_free(spj->value);
}
break;
case _JOB_TYPE_SET_PROPERTY_CHOICE:
{
_camctl_camera_set_property_choice_job_t *spj = (_camctl_camera_set_property_choice_job_t *)job;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] executing set camera config job %s=%d", spj->name, spj->value);
CameraWidget *config; // Copy of camera configuration
CameraWidget *widget;
gp_camera_get_config(cam->gpcam, &config, c->gpcontext);
if(gp_widget_get_child_by_name(config, spj->name, &widget) == GP_OK)
{
if(spj->value >= 0 && spj->value < gp_widget_count_choices(widget))
{
const char *choice;
gp_widget_get_choice(widget, spj->value, &choice);
dt_print(DT_DEBUG_CAMCTL, " (%s)", choice);
gp_widget_set_value(widget, choice);
gp_camera_set_config(cam->gpcam, config, c->gpcontext);
}
}
/* dt_pthread_mutex_lock( &cam->config_lock );
CameraWidget *widget;
if( gp_widget_get_child_by_name ( camera->configuration, spj->name, &widget) == GP_OK) {
gp_widget_set_value ( widget , spj->value);
//gp_widget_set_changed( widget, 1 );
cam->config_changed=TRUE;
}
dt_pthread_mutex_unlock( &cam->config_lock);*/
dt_print(DT_DEBUG_CAMCTL, "\n");
g_free(spj->name);
}
break;
case _JOB_TYPE_SET_PROPERTY_INT:
{
_camctl_camera_set_property_int_job_t *spj = (_camctl_camera_set_property_int_job_t *)job;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] executing set camera config job %s=%d\n", spj->name,
spj->value);
CameraWidget *config; // Copy of camera configuration
CameraWidget *widget;
gp_camera_get_config(cam->gpcam, &config, c->gpcontext);
if(gp_widget_get_child_by_name(config, spj->name, &widget) == GP_OK)
{
int value = spj->value;
gp_widget_set_value(widget, &value);
gp_camera_set_config(cam->gpcam, config, c->gpcontext);
}
/* dt_pthread_mutex_lock( &cam->config_lock );
CameraWidget *widget;
if( gp_widget_get_child_by_name ( camera->configuration, spj->name, &widget) == GP_OK) {
gp_widget_set_value ( widget , spj->value);
//gp_widget_set_changed( widget, 1 );
cam->config_changed=TRUE;
}
dt_pthread_mutex_unlock( &cam->config_lock);*/
g_free(spj->name);
}
break;
default:
dt_print(DT_DEBUG_CAMCTL, "[camera_control] process of unknown job type 0x%x\n", j->type);
break;
}
g_free(j);
}
/*************/
/* LIVE VIEW */
/*************/
static void *dt_camctl_camera_get_live_view(void *data)
{
dt_camctl_t *camctl = (dt_camctl_t *)data;
dt_camera_t *cam = (dt_camera_t *)camctl->active_camera;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] live view thread started\n");
int frames = 0;
double capture_time = dt_get_wtime();
while(cam->is_live_viewing == TRUE)
{
dt_pthread_mutex_lock(&cam->live_view_synch);
// calculate FPS
double current_time = dt_get_wtime();
if(current_time - capture_time >= 1.0)
{
// a second has passed
dt_print(DT_DEBUG_CAMCTL, "%d fps\n", frames + 1);
frames = 0;
capture_time = current_time;
}
else
{
// just increase the frame counter
frames++;
}
_camctl_camera_job_t *job = g_malloc(sizeof(_camctl_camera_job_t));
job->type = _JOB_TYPE_EXECUTE_LIVE_VIEW;
_camera_add_job(camctl, cam, job);
g_usleep((1.0 / 15) * G_USEC_PER_SEC); // never update faster than 15 FPS. going too fast will result in
// too many redraws without a real benefit
}
dt_print(DT_DEBUG_CAMCTL, "[camera_control] live view thread stopped\n");
return NULL;
}
gboolean dt_camctl_camera_start_live_view(const dt_camctl_t *c)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
dt_camera_t *cam = (dt_camera_t *)camctl->active_camera;
if(cam == NULL)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Failed to start live view, camera==NULL\n");
return FALSE;
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Starting live view\n");
if(cam->can_live_view == FALSE)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Camera does not support live view\n");
return FALSE;
}
cam->is_live_viewing = TRUE;
dt_camctl_camera_set_property_int(camctl, NULL, "eosviewfinder", 1);
dt_pthread_create(&cam->live_view_thread, &dt_camctl_camera_get_live_view, (void *)camctl);
return TRUE;
}
void dt_camctl_camera_stop_live_view(const dt_camctl_t *c)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
dt_camera_t *cam = (dt_camera_t *)camctl->active_camera;
if(cam->is_live_viewing == FALSE)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Not in live view mode, nothing to stop\n");
return;
}
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Stopping live view\n");
cam->is_live_viewing = FALSE;
pthread_join(cam->live_view_thread, NULL);
// tell camera to get back to normal state (close mirror)
dt_camctl_camera_set_property_int(camctl, NULL, "eosviewfinder", 0);
}
void _camctl_lock(const dt_camctl_t *c, const dt_camera_t *cam)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
dt_pthread_mutex_lock(&camctl->lock);
dt_print(DT_DEBUG_CAMCTL, "[camera_control] camera control locked for camera %p\n", cam);
camctl->active_camera = cam;
_dispatch_control_status(c, CAMERA_CONTROL_BUSY);
}
void _camctl_unlock(const dt_camctl_t *c)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
const dt_camera_t *cam = camctl->active_camera;
camctl->active_camera = NULL;
dt_pthread_mutex_unlock(&camctl->lock);
dt_print(DT_DEBUG_CAMCTL, "[camera_control] camera control un-locked for camera %p\n", cam);
_dispatch_control_status(c, CAMERA_CONTROL_AVAILABLE);
}
dt_camctl_t *dt_camctl_new()
{
dt_camctl_t *camctl = g_malloc0(sizeof(dt_camctl_t));
dt_print(DT_DEBUG_CAMCTL, "[camera_control] creating new context %p\n", camctl);
// Initialize gphoto2 context and setup dispatch callbacks
camctl->gpcontext = gp_context_new();
// gp_context_set_idle_func(camctl->gpcontext, (GPContextIdleFunc)_idle_func_dispatch, camctl);
#ifdef HAVE_GPHOTO_25_OR_NEWER
gp_context_set_status_func(camctl->gpcontext, (GPContextStatusFunc)_status_func_dispatch25, camctl);
gp_context_set_error_func(camctl->gpcontext, (GPContextErrorFunc)_error_func_dispatch25, camctl);
gp_context_set_message_func(camctl->gpcontext, (GPContextMessageFunc)_message_func_dispatch25, camctl);
#else
gp_context_set_status_func(camctl->gpcontext, (GPContextStatusFunc)_status_func_dispatch, camctl);
gp_context_set_error_func(camctl->gpcontext, (GPContextErrorFunc)_error_func_dispatch, camctl);
gp_context_set_message_func(camctl->gpcontext, (GPContextMessageFunc)_message_func_dispatch, camctl);
#endif
// Load all camera drivers we know...
gp_abilities_list_new(&camctl->gpcams);
gp_abilities_list_load(camctl->gpcams, camctl->gpcontext);
dt_print(DT_DEBUG_CAMCTL, "[camera_control] loaded %d camera drivers.\n",
gp_abilities_list_count(camctl->gpcams));
dt_pthread_mutex_init(&camctl->lock, NULL);
dt_pthread_mutex_init(&camctl->listeners_lock, NULL);
// asynchronously call dt_camctl_detect_cameras(camctl); to fill in camctl
dt_job_t *job = dt_control_job_create(&_detect_cameras_callback, "detect connected cameras");
if(job)
{
dt_control_job_set_params(job, camctl, NULL);
dt_control_add_job(darktable.control, DT_JOB_QUEUE_SYSTEM_BG, job);
}
return camctl;
}
static void dt_camctl_camera_destroy(dt_camera_t *cam)
{
if(!cam) return;
gp_camera_exit(cam->gpcam, cam->gpcontext);
gp_camera_unref(cam->gpcam);
gp_widget_unref(cam->configuration);
if(cam->live_view_pixbuf != NULL)
{
g_object_unref(cam->live_view_pixbuf);
cam->live_view_pixbuf = NULL; // just in case someone else is using this
}
g_free(cam->model);
g_free(cam->port);
dt_pthread_mutex_destroy(&cam->config_lock);
dt_pthread_mutex_destroy(&cam->live_view_pixbuf_mutex);
dt_pthread_mutex_destroy(&cam->live_view_synch);
// TODO: cam->jobqueue
g_free(cam);
}
void dt_camctl_destroy(dt_camctl_t *camctl)
{
if(!camctl) return;
// Go thru all c->cameras and release them..
for(GList *it = g_list_first(camctl->cameras); it != NULL; it = g_list_delete_link(it, it))
{
dt_camctl_camera_destroy((dt_camera_t *)it->data);
}
gp_context_unref(camctl->gpcontext);
gp_abilities_list_free(camctl->gpcams);
gp_port_info_list_free(camctl->gpports);
dt_pthread_mutex_destroy(&camctl->lock);
dt_pthread_mutex_destroy(&camctl->listeners_lock);
g_free(camctl);
}
int dt_camctl_have_cameras(const dt_camctl_t *c)
{
return (g_list_length(c->cameras) > 0) ? 1 : 0;
}
void dt_camctl_register_listener(const dt_camctl_t *c, dt_camctl_listener_t *listener)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
// Just locking mutex and prevent signalling CAMERA_CONTROL_BUSY
dt_pthread_mutex_lock(&camctl->listeners_lock);
if(g_list_find(camctl->listeners, listener) == NULL)
{
camctl->listeners = g_list_append(camctl->listeners, listener);
dt_print(DT_DEBUG_CAMCTL, "[camera_control] registering listener %p\n", listener);
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] registering already registered listener %p\n", listener);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
void dt_camctl_unregister_listener(const dt_camctl_t *c, dt_camctl_listener_t *listener)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
// Just locking mutex and prevent signalling CAMERA_CONTROL_BUSY
dt_pthread_mutex_lock(&camctl->listeners_lock);
dt_print(DT_DEBUG_CAMCTL, "[camera_control] unregistering listener %p\n", listener);
camctl->listeners = g_list_remove(camctl->listeners, listener);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
gint _compare_camera_by_port(gconstpointer a, gconstpointer b)
{
dt_camera_t *ca = (dt_camera_t *)a;
dt_camera_t *cb = (dt_camera_t *)b;
return strcmp(ca->port, cb->port);
}
void dt_camctl_detect_cameras(const dt_camctl_t *c)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
dt_pthread_mutex_lock(&camctl->lock);
/* reload portdrivers */
if(camctl->gpports) gp_port_info_list_free(camctl->gpports);
gp_port_info_list_new(&camctl->gpports);
gp_port_info_list_load(camctl->gpports);
dt_print(DT_DEBUG_CAMCTL, "[camera_control] loaded %d port drivers.\n",
gp_port_info_list_count(camctl->gpports));
CameraList *available_cameras = NULL;
gp_list_new(&available_cameras);
gp_abilities_list_detect(c->gpcams, c->gpports, available_cameras, c->gpcontext);
dt_print(DT_DEBUG_CAMCTL, "[camera_control] %d cameras connected\n",
gp_list_count(available_cameras) > 0 ? gp_list_count(available_cameras) : 0);
for(int i = 0; i < gp_list_count(available_cameras); i++)
{
dt_camera_t *camera = g_malloc0(sizeof(dt_camera_t));
const gchar *s;
gp_list_get_name(available_cameras, i, &s);
camera->model = g_strdup(s);
gp_list_get_value(available_cameras, i, &s);
camera->port = g_strdup(s);
dt_pthread_mutex_init(&camera->config_lock, NULL);
dt_pthread_mutex_init(&camera->live_view_pixbuf_mutex, NULL);
dt_pthread_mutex_init(&camera->live_view_synch, NULL);
// if(strcmp(camera->port,"usb:")==0) { g_free(camera); continue; }
GList *citem;
if((citem = g_list_find_custom(c->cameras, camera, _compare_camera_by_port)) == NULL
|| strcmp(((dt_camera_t *)citem->data)->model, camera->model) != 0)
{
if(citem == NULL)
{
// Newly connected camera
if(_camera_initialize(c, camera) == FALSE)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to initialize device %s on port %s, probably "
"causes are: locked by another application, no access to udev etc.\n",
camera->model, camera->port);
dt_camctl_camera_destroy(camera);
continue;
}
// Check if camera has capabilities for being presented to darktable
if(camera->can_import == FALSE && camera->can_tether == FALSE)
{
dt_print(
DT_DEBUG_CAMCTL,
"[camera_control] device %s on port %s doesn't support import or tether, skipping device.\n",
camera->model, camera->port);
dt_camctl_camera_destroy(camera);
continue;
}
// Fetch some summary of camera
if(gp_camera_get_summary(camera->gpcam, &camera->summary, c->gpcontext) == GP_OK)
{
// Remove device property summary:
char *eos = strstr(camera->summary.text, "Device Property Summary:\n");
if(eos) eos[0] = '\0';
}
// Add to camera list
camctl->cameras = g_list_append(camctl->cameras, camera);
// Notify listeners of connected camera
_dispatch_camera_connected(camctl, camera);
}
}
else
dt_camctl_camera_destroy(camera);
}
/* check c->cameras in available_cameras */
if(c->cameras && g_list_length(c->cameras) > 0)
{
GList *citem = c->cameras;
do
{
int index = 0;
dt_camera_t *cam = (dt_camera_t *)citem->data;
if(gp_list_find_by_name(available_cameras, &index, cam->model) != GP_OK)
{
/* remove camera from cached list.. */
dt_camera_t *oldcam = (dt_camera_t *)citem->data;
camctl->cameras = citem = g_list_delete_link(c->cameras, citem);
dt_camctl_camera_destroy(oldcam);
}
} while(citem && (citem = g_list_next(citem)) != NULL);
}
gp_list_unref(available_cameras);
dt_pthread_mutex_unlock(&camctl->lock);
// tell the world that we are done. this assumes that there is just one global camctl.
// if there would ever be more it would be easy to pass c with the signal.
// TODO: only raise it when the connected cameras changed?
dt_control_signal_raise(darktable.signals, DT_SIGNAL_CAMERA_DETECTED);
}
static int _detect_cameras_callback(dt_job_t *job)
{
dt_camctl_t *c = dt_control_job_get_params(job);
dt_camctl_detect_cameras(c);
return 0;
}
static void *_camera_event_thread(void *data)
{
dt_camctl_t *camctl = (dt_camctl_t *)data;
const dt_camera_t *camera = camctl->active_camera;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] starting camera event thread of context %p\n", data);
while(camera->is_tethering == TRUE)
{
// Poll event from camera
_camera_poll_events(camctl, camera);
// Let's check if there are jobs in queue to process
gpointer job;
while((job = _camera_get_job(camctl, camera)) != NULL) _camera_process_job(camctl, camera, job);
// Check it jobs did change the configuration
if(camera->config_changed == TRUE) _camera_configuration_commit(camctl, camera);
}
dt_print(DT_DEBUG_CAMCTL, "[camera_control] exiting camera thread.\n");
return NULL;
}
gboolean _camera_initialize(const dt_camctl_t *c, dt_camera_t *cam)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
CameraAbilities a;
GPPortInfo pi;
if(cam->gpcam == NULL)
{
gp_camera_new(&cam->gpcam);
int m = gp_abilities_list_lookup_model(c->gpcams, cam->model);
gp_abilities_list_get_abilities(c->gpcams, m, &a);
gp_camera_set_abilities(cam->gpcam, a);
int p = gp_port_info_list_lookup_path(c->gpports, cam->port);
gp_port_info_list_get_info(c->gpports, p, &pi);
gp_camera_set_port_info(cam->gpcam, pi);
// Check for abilities
if((a.operations & GP_OPERATION_CAPTURE_IMAGE)) cam->can_tether = TRUE;
if((a.operations & GP_OPERATION_CAPTURE_PREVIEW)) cam->can_live_view = TRUE;
if(cam->can_tether && (a.operations & GP_OPERATION_CONFIG)) cam->can_config = TRUE;
if(!(a.file_operations & GP_FILE_OPERATION_NONE)) cam->can_import = TRUE;
if(gp_camera_init(cam->gpcam, camctl->gpcontext) != GP_OK)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to initialize camera %s on port %s\n", cam->model,
cam->port);
return FALSE;
}
// read a full copy of config to configuration cache
gp_camera_get_config(cam->gpcam, &cam->configuration, c->gpcontext);
// TODO: find a more robust way for this, once we find out how to do it with non-EOS cameras
cam->can_live_view_advanced = cam->can_live_view &&
dt_camctl_camera_property_exists(camctl, cam, "eoszoomposition");
// initialize timeout callbacks eg. keep alive, some cameras needs it.
cam->gpcontext = camctl->gpcontext;
gp_camera_set_timeout_funcs(cam->gpcam, (CameraTimeoutStartFunc)_camera_start_timeout_func,
(CameraTimeoutStopFunc)_camera_stop_timeout_func, cam);
dt_pthread_mutex_init(&cam->jobqueue_lock, NULL);
dt_print(DT_DEBUG_CAMCTL, "[camera_control] device %s on port %s initialized\n", cam->model, cam->port);
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] device %s on port %s already initialized\n", cam->model,
cam->port);
return TRUE;
}
void dt_camctl_import(const dt_camctl_t *c, const dt_camera_t *cam, GList *images)
{
// dt_camctl_t *camctl=(dt_camctl_t *)c;
_camctl_lock(c, cam);
GList *ifile = g_list_first(images);
if(ifile) do
{
// Split file into folder and filename
char *eos;
char folder[PATH_MAX] = { 0 };
char filename[PATH_MAX] = { 0 };
char *file = (char *)ifile->data;
eos = file + strlen(file);
while(--eos > file && *eos != '/')
;
char *_file = g_strndup(file, eos - file);
g_strlcat(folder, _file, sizeof(folder));
g_strlcat(filename, eos + 1, sizeof(filename));
g_free(_file);
CameraFile* camfile;
int res = GP_OK;
const uint8_t* data = NULL;
unsigned long int size;
gp_file_new(&camfile);
if ((res = gp_camera_file_get(cam->gpcam, folder, filename, GP_FILE_TYPE_NORMAL, camfile, NULL)) < GP_OK) {
dt_print(DT_DEBUG_CAMCTL, "[camera_control] import failed: %s\n", gp_result_as_string(res));
gp_file_free(camfile);
continue;
}
if ((res = gp_file_get_data_and_size(camfile, (const char**)&data, &size)) < GP_OK) {
dt_print(DT_DEBUG_CAMCTL, "[camera_control] import failed: %s\n", gp_result_as_string(res));
gp_file_free(camfile);
continue;
}
time_t exif_time;
gboolean have_exif_time = dt_exif_get_datetime_taken(data, size, &exif_time);
const char *output_path = _dispatch_request_image_path(c, have_exif_time ? &exif_time : NULL, cam);
const char *fname = _dispatch_request_image_filename(c, filename, have_exif_time ? &exif_time : NULL, cam);
if(!fname) continue;
char *output = g_build_filename(output_path, fname, (char *)NULL);
int handle = open(output, O_CREAT | O_WRONLY, 0666);
if (handle > 0) {
if (write(handle, data, size) > 0) {
_dispatch_camera_image_downloaded(c, cam, output);
}
else {
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to download file %s\n", output);
}
close(handle);
}
else {
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to download file %s\n", output);
}
gp_file_free(camfile);
g_free(output);
} while((ifile = g_list_next(ifile)));
_dispatch_control_status(c, CAMERA_CONTROL_AVAILABLE);
_camctl_unlock(c);
}
int _camctl_recursive_get_previews(const dt_camctl_t *c, dt_camera_preview_flags_t flags, char *path)
{
CameraList *files;
CameraList *folders;
const char *filename;
const char *foldername;
gp_list_new(&files);
gp_list_new(&folders);
// Process files in current folder...
if(gp_camera_folder_list_files(c->active_camera->gpcam, path, files, c->gpcontext) == GP_OK)
{
for(int i = 0; i < gp_list_count(files); i++)
{
gp_list_get_name(files, i, &filename);
char *file = g_strconcat(path, "/", filename, NULL);
// Lets check the type of file...
CameraFileInfo cfi;
if(gp_camera_file_get_info(c->active_camera->gpcam, path, filename, &cfi, c->gpcontext) == GP_OK)
{
CameraFile *preview = NULL;
CameraFile *exif = NULL;
/*
* Fetch image preview if flagged...
*/
if(flags & CAMCTL_IMAGE_PREVIEW_DATA)
{
gp_file_new(&preview);
if(gp_camera_file_get(c->active_camera->gpcam, path, filename, GP_FILE_TYPE_PREVIEW, preview,
c->gpcontext) < GP_OK)
{
// No preview for file lets check image size to see if we should download full image for
// preview...
if(cfi.file.size > 0 && cfi.file.size < 512000)
{
if(gp_camera_file_get(c->active_camera->gpcam, path, filename, GP_FILE_TYPE_NORMAL, preview,
c->gpcontext) < GP_OK)
{
preview = NULL;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to retrieve preview of file %s\n",
filename);
}
}
else if(!strncmp(c->active_camera->port, "disk:", 5))
{
char fullpath[PATH_MAX] = { 0 };
snprintf(fullpath, sizeof(fullpath), "%s/%s/%s", c->active_camera->port + 5, path, filename);
uint8_t *buf = NULL; // gphoto takes care of freeing it eventually
size_t bufsize;
char *mime_type = NULL;
if(!dt_exif_get_thumbnail(fullpath, &buf, &bufsize, &mime_type))
gp_file_set_data_and_size(preview, (char *)buf, bufsize);
free(mime_type);
}
}
}
if(flags & CAMCTL_IMAGE_EXIF_DATA)
{
gp_file_new(&exif);
if(gp_camera_file_get(c->active_camera->gpcam, path, filename, GP_FILE_TYPE_EXIF, exif,
c->gpcontext) < GP_OK)
{
exif = NULL;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to retrieve exif of file %s\n", filename);
}
}
// let's dispatch to host app.. return if we should stop...
int res = _dispatch_camera_storage_image_filename(c, c->active_camera, file, preview, exif);
gp_file_free(preview);
if(!res)
{
g_free(file);
return 0;
}
}
else
dt_print(DT_DEBUG_CAMCTL,
"[camera_control] failed to get file information of %s in folder %s on device\n", filename,
path);
g_free(file);
}
}
// Recurse into folders in current folder...
if(gp_camera_folder_list_folders(c->active_camera->gpcam, path, folders, c->gpcontext) == GP_OK)
{
for(int i = 0; i < gp_list_count(folders); i++)
{
char buffer[PATH_MAX] = { 0 };
g_strlcat(buffer, path, sizeof(buffer));
if(path[1] != '\0') g_strlcat(buffer, "/", sizeof(buffer));
gp_list_get_name(folders, i, &foldername);
g_strlcat(buffer, foldername, sizeof(buffer));
if(!_camctl_recursive_get_previews(c, flags, buffer)) return 0;
}
}
gp_list_free(files);
gp_list_free(folders);
return 1;
}
void dt_camctl_select_camera(const dt_camctl_t *c, const dt_camera_t *cam)
{
_camctl_lock(c, cam);
dt_camctl_t *camctl = (dt_camctl_t *)c;
camctl->wanted_camera = cam;
_camctl_unlock(c);
}
void dt_camctl_get_previews(const dt_camctl_t *c, dt_camera_preview_flags_t flags, const dt_camera_t *cam)
{
_camctl_lock(c, cam);
_camctl_recursive_get_previews(c, flags, "/");
_camctl_unlock(c);
}
int dt_camctl_can_enter_tether_mode(const dt_camctl_t *c, const dt_camera_t *cam)
{
/* first check if camera is provided else use wanted cam */
if(cam == NULL) cam = c->wanted_camera;
/* check if wanted cam is available else use active camera */
if(cam == NULL) cam = c->active_camera;
/* check if active cam is available else use first detected one */
if(cam == NULL && c->cameras) cam = g_list_nth_data(c->cameras, 0);
if(cam && cam->can_tether)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
camctl->wanted_camera = cam;
return 1;
}
return 0;
}
void dt_camctl_tether_mode(const dt_camctl_t *c, const dt_camera_t *cam, gboolean enable)
{
/* first check if camera is provided else use wanted cam */
if(cam == NULL) cam = c->wanted_camera;
/* check if wanted cam is available else use active camera */
if(cam == NULL) cam = c->active_camera;
/* check if active cam is available else use first detected one */
if(cam == NULL && c->cameras) cam = g_list_nth_data(c->cameras, 0);
if(cam && cam->can_tether)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
dt_camera_t *camera = (dt_camera_t *)cam;
if(enable == TRUE && camera->is_tethering != TRUE)
{
_camctl_lock(c, cam);
// Start up camera event polling thread
dt_print(DT_DEBUG_CAMCTL, "[camera_control] enabling tether mode\n");
camctl->active_camera = camera;
camera->is_tethering = TRUE;
dt_pthread_create(&camctl->camera_event_thread, &_camera_event_thread, (void *)c);
}
else
{
camera->is_live_viewing = FALSE;
camera->is_tethering = FALSE;
dt_print(DT_DEBUG_CAMCTL, "[camera_control] disabling tether mode\n");
_camctl_unlock(c);
// Wait for tether thread with join??
}
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to set tether mode with reason: %s\n",
cam ? "device does not support tethered capture" : "no active camera");
}
const char *dt_camctl_camera_get_model(const dt_camctl_t *c, const dt_camera_t *cam)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && (cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to get model of camera, camera==NULL\n");
return NULL;
}
return cam->model;
}
void _camera_build_property_menu(CameraWidget *widget, GtkMenu *menu, GCallback item_activate,
gpointer user_data)
{
int num_childs = 0;
const char *sk;
CameraWidgetType type;
/* if widget has children lets add menutitem and recurse into container */
if((num_childs = gp_widget_count_children(widget)) > 0)
{
CameraWidget *child = NULL;
for(int i = 0; i < num_childs; i++)
{
gp_widget_get_child(widget, i, &child);
gp_widget_get_name(child, &sk);
/* Check if widget is submenu */
if(gp_widget_count_children(child) > 0)
{
/* create submenu item */
GtkMenuItem *item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(sk));
gtk_menu_item_set_submenu(item, gtk_menu_new());
/* recurse into submenu */
_camera_build_property_menu(child, GTK_MENU(gtk_menu_item_get_submenu(item)), item_activate,
user_data);
/* add submenu item to menu if not empty*/
GList *childs = gtk_container_get_children(GTK_CONTAINER(gtk_menu_item_get_submenu(item)));
if(childs)
{
gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item));
g_list_free(childs);
}
}
else
{
/* check widget type */
gp_widget_get_type(child, &type);
if(type == GP_WIDGET_MENU || type == GP_WIDGET_TEXT || type == GP_WIDGET_RADIO)
{
/* construct menu item for property */
gp_widget_get_name(child, &sk);
GtkMenuItem *item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(sk));
g_signal_connect(G_OBJECT(item), "activate", item_activate, user_data);
/* add submenu item to menu */
gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item));
}
}
}
}
}
void dt_camctl_camera_build_property_menu(const dt_camctl_t *c, const dt_camera_t *cam, GtkMenu **menu,
GCallback item_activate, gpointer user_data)
{
/* get active camera */
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && (cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to build property menu from camera, camera==NULL\n");
return;
}
dt_print(DT_DEBUG_CAMCTL, "[camera_control] building property menu from camera configuration\n");
/* lock camera config mutex while recursive building property menu */
dt_camera_t *camera = (dt_camera_t *)cam;
dt_pthread_mutex_lock(&camera->config_lock);
*menu = GTK_MENU(gtk_menu_new());
_camera_build_property_menu(camera->configuration, *menu, item_activate, user_data);
gtk_widget_show_all(GTK_WIDGET(*menu));
dt_pthread_mutex_unlock(&camera->config_lock);
}
void dt_camctl_camera_set_property_string(const dt_camctl_t *c, const dt_camera_t *cam,
const char *property_name, const char *value)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && (cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to set property from camera, camera==NULL\n");
return;
}
dt_camera_t *camera = (dt_camera_t *)cam;
_camctl_camera_set_property_string_job_t *job = g_malloc(sizeof(_camctl_camera_set_property_string_job_t));
job->type = _JOB_TYPE_SET_PROPERTY_STRING;
job->name = g_strdup(property_name);
job->value = g_strdup(value);
// Push the job on the jobqueue
_camera_add_job(camctl, camera, job);
}
void dt_camctl_camera_set_property_choice(const dt_camctl_t *c, const dt_camera_t *cam,
const char *property_name, const int value)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && (cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to set property from camera, camera==NULL\n");
return;
}
dt_camera_t *camera = (dt_camera_t *)cam;
_camctl_camera_set_property_choice_job_t *job = g_malloc(sizeof(_camctl_camera_set_property_choice_job_t));
job->type = _JOB_TYPE_SET_PROPERTY_CHOICE;
job->name = g_strdup(property_name);
job->value = value;
// Push the job on the jobqueue
_camera_add_job(camctl, camera, job);
}
void dt_camctl_camera_set_property_int(const dt_camctl_t *c, const dt_camera_t *cam,
const char *property_name, const int value)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && (cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to set property from camera, camera==NULL\n");
return;
}
dt_camera_t *camera = (dt_camera_t *)cam;
_camctl_camera_set_property_int_job_t *job = g_malloc(sizeof(_camctl_camera_set_property_int_job_t));
job->type = _JOB_TYPE_SET_PROPERTY_INT;
job->name = g_strdup(property_name);
job->value = value;
// Push the job on the jobqueue
_camera_add_job(camctl, camera, job);
}
const char *dt_camctl_camera_get_property(const dt_camctl_t *c, const dt_camera_t *cam,
const char *property_name)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && (cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to get property from camera, camera==NULL\n");
return NULL;
}
dt_camera_t *camera = (dt_camera_t *)cam;
dt_pthread_mutex_lock(&camera->config_lock);
const char *value = NULL;
CameraWidget *widget;
if(gp_widget_get_child_by_name(camera->configuration, property_name, &widget) == GP_OK)
{
gp_widget_get_value(widget, &value);
}
dt_pthread_mutex_unlock(&camera->config_lock);
return value;
}
int dt_camctl_camera_property_exists(const dt_camctl_t *c, const dt_camera_t *cam, const char *property_name)
{
int exists = 0;
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && ((cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL))
{
dt_print(DT_DEBUG_CAMCTL,
"[camera_control] failed to check if property exists in camera configuration, camera==NULL\n");
return 0;
}
dt_camera_t *camera = (dt_camera_t *)cam;
dt_pthread_mutex_lock(&camera->config_lock);
CameraWidget *widget;
if(gp_widget_get_child_by_name(camera->configuration, property_name, &widget) == GP_OK) exists = 1;
dt_pthread_mutex_unlock(&camera->config_lock);
return exists;
}
const char *dt_camctl_camera_property_get_first_choice(const dt_camctl_t *c, const dt_camera_t *cam,
const char *property_name)
{
const char *value = NULL;
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && ((cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL))
{
dt_print(DT_DEBUG_CAMCTL,
"[camera_control] failed to get first choice of property from camera, camera==NULL\n");
return NULL;
}
dt_camera_t *camera = (dt_camera_t *)cam;
dt_pthread_mutex_lock(&camera->config_lock);
if(gp_widget_get_child_by_name(camera->configuration, property_name, &camera->current_choice.widget)
== GP_OK)
{
camera->current_choice.index = 0;
gp_widget_get_choice(camera->current_choice.widget, camera->current_choice.index, &value);
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] property name '%s' not found in camera configuration.\n",
property_name);
dt_pthread_mutex_unlock(&camera->config_lock);
return value;
}
const char *dt_camctl_camera_property_get_next_choice(const dt_camctl_t *c, const dt_camera_t *cam,
const char *property_name)
{
const char *value = NULL;
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && ((cam = camctl->active_camera) == NULL && (cam = camctl->wanted_camera) == NULL))
{
dt_print(DT_DEBUG_CAMCTL,
"[camera_control] Failed to get next choice of property from camera, camera==NULL\n");
return NULL;
}
dt_camera_t *camera = (dt_camera_t *)cam;
dt_pthread_mutex_lock(&camera->config_lock);
if(camera->current_choice.widget != NULL)
{
if(++camera->current_choice.index < gp_widget_count_choices(camera->current_choice.widget))
{
// get the choice value...
gp_widget_get_choice(camera->current_choice.widget, camera->current_choice.index, &value);
}
else
{
// No more choices, reset current_choices for further use
camera->current_choice.index = 0;
camera->current_choice.widget = NULL;
}
}
dt_pthread_mutex_unlock(&camera->config_lock);
return value;
}
void dt_camctl_camera_capture(const dt_camctl_t *c, const dt_camera_t *cam)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
if(!cam && (cam = camctl->active_camera) == NULL)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Failed to capture from camera, camera==NULL\n");
return;
}
dt_camera_t *camera = (dt_camera_t *)cam;
_camctl_camera_job_t *job = g_malloc(sizeof(_camctl_camera_job_t));
job->type = _JOB_TYPE_EXECUTE_CAPTURE;
_camera_add_job(camctl, camera, job);
}
void _camera_poll_events(const dt_camctl_t *c, const dt_camera_t *cam)
{
CameraEventType event;
gpointer data;
if(gp_camera_wait_for_event(cam->gpcam, 30, &event, &data, c->gpcontext) == GP_OK)
{
if(event == GP_EVENT_UNKNOWN)
{
/* this is really some undefined behavior, seems like its
camera driver dependent... very ugly! */
if(strstr((char *)data, "4006") || // Nikon PTP driver
(strstr((char *)data, "PTP Property")
&& strstr((char *)data, "changed")) // Some Canon driver maybe all ??
)
{
// Property change event occurred on camera
// let's update cache and signalling
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Camera configuration change event, lets update internal "
"configuration cache.\n");
_camera_configuration_update(c, cam);
}
}
else if(event == GP_EVENT_FILE_ADDED)
{
if(cam->is_tethering)
{
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Camera file added event\n");
CameraFilePath *fp = (CameraFilePath *)data;
CameraFile *destination;
const char *output_path = _dispatch_request_image_path(c, NULL, cam);
if(!output_path) output_path = "/tmp";
const char *fname = _dispatch_request_image_filename(c, fp->name, NULL, cam);
if(!fname) fname = fp->name;
char *output = g_build_filename(output_path, fname, (char *)NULL);
int handle = open(output, O_CREAT | O_WRONLY, 0666);
if(handle != -1)
{
gp_file_new_from_fd(&destination, handle);
if(gp_camera_file_get(cam->gpcam, fp->folder, fp->name, GP_FILE_TYPE_NORMAL, destination,
c->gpcontext) == GP_OK)
{
// Notify listeners of captured image
_dispatch_camera_image_downloaded(c, cam, output);
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to download file %s\n", output);
close(handle);
}
else
dt_print(DT_DEBUG_CAMCTL, "[camera_control] failed to download file %s\n", output);
g_free(output);
}
}
}
}
void _camera_configuration_merge(const dt_camctl_t *c, const dt_camera_t *camera, CameraWidget *source,
CameraWidget *destination, gboolean notify_all)
{
int childs = 0;
const char *sk;
const char *stv;
CameraWidget *dw;
const char *dtv;
CameraWidgetType type;
// If source widget has childs let's recurse into each children
if((childs = gp_widget_count_children(source)) > 0)
{
CameraWidget *child = NULL;
for(int i = 0; i < childs; i++)
{
gp_widget_get_child(source, i, &child);
// gp_widget_get_name( source, &sk );
_camera_configuration_merge(c, camera, child, destination, notify_all);
}
}
else
{
gboolean changed = TRUE;
gp_widget_get_type(source, &type);
// Get the two keys to compare
gp_widget_get_name(source, &sk);
gp_widget_get_child_by_name(destination, sk, &dw);
//
// First of all check if widget has change accessibility
//
/// TODO: Resolve this 2.4.8 libgphoto2 dependency
/*
int sa,da;
gp_widget_get_readonly( source, &sa );
gp_widget_get_readonly( dw, &da );
if( notify_all || ( sa != da ) ) {
// update destination widget to new accessibility if differ then notify of the change
if( ( sa != da ) )
gp_widget_set_readonly( dw, sa );
_dispatch_camera_property_accessibility_changed(c, camera,sk, ( sa == 1 ) ? TRUE: FALSE) ;
}
*/
//
// Lets compare values and notify on change or by notifyAll flag
//
if(type == GP_WIDGET_MENU || type == GP_WIDGET_TEXT || type == GP_WIDGET_RADIO)
{
// Get source and destination value to be compared
gp_widget_get_value(source, &stv);
gp_widget_get_value(dw, &dtv);
if(((stv && dtv) && strcmp(stv, dtv) != 0) && (changed = TRUE))
{
gp_widget_set_value(dw, stv);
// Don't flag this change as changed, otherwise a read-only widget might get tried
// to update the camera configuration...
gp_widget_set_changed(dw, 0);
}
if((stv && dtv) && (notify_all || changed)) _dispatch_camera_property_value_changed(c, camera, sk, stv);
}
}
}
void _camera_configuration_commit(const dt_camctl_t *c, const dt_camera_t *camera)
{
g_assert(camera != NULL);
dt_camera_t *cam = (dt_camera_t *)camera;
dt_pthread_mutex_lock(&cam->config_lock);
if(gp_camera_set_config(camera->gpcam, camera->configuration, c->gpcontext) != GP_OK)
dt_print(DT_DEBUG_CAMCTL, "[camera_control] Failed to commit configuration changes to camera\n");
cam->config_changed = FALSE;
dt_pthread_mutex_unlock(&cam->config_lock);
}
void _camera_configuration_update(const dt_camctl_t *c, const dt_camera_t *camera)
{
// dt_camctl_t *camctl=(dt_camctl_t *)c;
dt_camera_t *cam = (dt_camera_t *)camera;
dt_pthread_mutex_lock(&cam->config_lock);
CameraWidget *remote; // Copy of remote configuration
gp_camera_get_config(camera->gpcam, &remote, c->gpcontext);
// merge remote copy with cache and notify on changed properties to host application
_camera_configuration_merge(c, camera, remote, camera->configuration, FALSE);
dt_pthread_mutex_unlock(&cam->config_lock);
}
const char *_dispatch_request_image_filename(const dt_camctl_t *c, const char *filename,
time_t *exif_time, const dt_camera_t *camera)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
const char *path = NULL;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->request_image_filename != NULL)
path = ((dt_camctl_listener_t *)listener->data)
->request_image_filename(camera, filename, exif_time,
((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
return path;
}
const char *_dispatch_request_image_path(const dt_camctl_t *c, time_t *exif_time, const dt_camera_t *camera)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
const char *path = NULL;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->request_image_path != NULL)
path = ((dt_camctl_listener_t *)listener->data)
->request_image_path(camera, exif_time, ((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
return path;
}
void _dispatch_camera_connected(const dt_camctl_t *c, const dt_camera_t *camera)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->camera_connected != NULL)
((dt_camctl_listener_t *)listener->data)
->camera_connected(camera, ((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
void _dispatch_camera_disconnected(const dt_camctl_t *c, const dt_camera_t *camera)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->camera_disconnected != NULL)
((dt_camctl_listener_t *)listener->data)
->camera_disconnected(camera, ((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
void _dispatch_camera_image_downloaded(const dt_camctl_t *c, const dt_camera_t *camera, const char *filename)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->image_downloaded != NULL)
((dt_camctl_listener_t *)listener->data)
->image_downloaded(camera, filename, ((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
int _dispatch_camera_storage_image_filename(const dt_camctl_t *c, const dt_camera_t *camera,
const char *filename, CameraFile *preview, CameraFile *exif)
{
int res = 0;
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->camera_storage_image_filename != NULL)
res = ((dt_camctl_listener_t *)listener->data)
->camera_storage_image_filename(camera, filename, preview, exif,
((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
return res;
}
void _dispatch_control_status(const dt_camctl_t *c, dt_camctl_status_t status)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->control_status != NULL)
((dt_camctl_listener_t *)listener->data)
->control_status(status, ((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
void _dispatch_camera_property_value_changed(const dt_camctl_t *c, const dt_camera_t *camera,
const char *name, const char *value)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->camera_property_value_changed != NULL)
((dt_camctl_listener_t *)listener->data)
->camera_property_value_changed(camera, name, value,
((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
void _dispatch_camera_property_accessibility_changed(const dt_camctl_t *c, const dt_camera_t *camera,
const char *name, gboolean read_only)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->camera_property_accessibility_changed != NULL)
((dt_camctl_listener_t *)listener->data)
->camera_property_accessibility_changed(camera, name, read_only,
((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
void _dispatch_camera_error(const dt_camctl_t *c, const dt_camera_t *camera, dt_camera_error_t error)
{
dt_camctl_t *camctl = (dt_camctl_t *)c;
GList *listener;
dt_pthread_mutex_lock(&camctl->listeners_lock);
if((listener = g_list_first(camctl->listeners)) != NULL) do
{
if(((dt_camctl_listener_t *)listener->data)->camera_error != NULL)
((dt_camctl_listener_t *)listener->data)
->camera_error(camera, error, ((dt_camctl_listener_t *)listener->data)->data);
} while((listener = g_list_next(listener)) != NULL);
dt_pthread_mutex_unlock(&camctl->listeners_lock);
}
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;