diff --git a/.depend b/.depend
index b82d5168..f3db5284 100644
--- a/.depend
+++ b/.depend
@@ -360,6 +360,16 @@ mce-wltimer.pic.o:\
mce-wltimer.h\
mce.h\
+mce-worker.o:\
+ mce-worker.c\
+ mce-log.h\
+ mce-worker.h\
+
+mce-worker.pic.o:\
+ mce-worker.c\
+ mce-log.h\
+ mce-worker.h\
+
mce.o:\
mce.c\
builtin-gconf.h\
@@ -379,6 +389,7 @@ mce.o:\
mce-sensorfw.h\
mce-wakelock.h\
mce-wltimer.h\
+ mce-worker.h\
mce.h\
modetransition.h\
powerkey.h\
@@ -403,6 +414,7 @@ mce.pic.o:\
mce-sensorfw.h\
mce-wakelock.h\
mce-wltimer.h\
+ mce-worker.h\
mce.h\
modetransition.h\
powerkey.h\
diff --git a/Makefile b/Makefile
index e1d0a621..63aec769 100644
--- a/Makefile
+++ b/Makefile
@@ -282,6 +282,7 @@ MCE_CORE += mce-gconf.c
MCE_CORE += mce-hbtimer.c
MCE_CORE += mce-wltimer.c
MCE_CORE += mce-wakelock.c
+MCE_CORE += mce-worker.c
MCE_CORE += event-input.c
MCE_CORE += event-switches.c
MCE_CORE += mce-hal.c
@@ -547,6 +548,8 @@ NORMALIZE_USES_SPC =\
mce-sensorfw.h\
mce-wakelock.c\
mce-wakelock.h\
+ mce-worker.c\
+ mce-worker.h\
modetransition.h\
modules/alarm.c\
modules/audiorouting.c\
diff --git a/mce-worker.c b/mce-worker.c
new file mode 100644
index 00000000..abadef6b
--- /dev/null
+++ b/mce-worker.c
@@ -0,0 +1,772 @@
+/**
+ * @file mce-worker.c
+ *
+ * Mode Control Entity - Offload blocking operations to a worker thread
+ *
+ *
+ *
+ * Copyright (C) 2015 Jolla Ltd.
+ *
+ *
+ *
+ * @author Simo Piiroinen
+ *
+ * mce is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * mce is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mce. If not, see .
+ */
+
+#include "mce-worker.h"
+#include "mce-log.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+/* ========================================================================= *
+ * FUNCTIONALITY
+ * ========================================================================= */
+
+/* ------------------------------------------------------------------------- *
+ * MISC_UTIL
+ * ------------------------------------------------------------------------- */
+
+static guint mw_add_iowatch(int fd, bool close_on_unref, GIOCondition cnd, GIOFunc io_cb, gpointer aptr);
+
+/* ------------------------------------------------------------------------- *
+ * MCE_JOB
+ * ------------------------------------------------------------------------- */
+
+typedef struct mce_job_t mce_job_t;
+
+/** Job object */
+struct mce_job_t
+{
+ /** Link to the next job in line */
+ mce_job_t *mj_next;
+
+ /** Validation context for this job */
+ char *mj_context;
+
+ /** Name of this job */
+ char *mj_name;
+
+ /** Callback for executing the job */
+ void *(*mj_handle)(void *);
+
+ /** Callback for notifying job executed */
+ void (*mj_notify)(void *, void *);
+
+ /** User data to be passed to the callbacks */
+ void *mj_param;
+
+ /** Reply value from execute callback, passed to notification callback */
+ void *mj_reply;
+};
+
+static const char *mce_job_context (const mce_job_t *self);
+static const char *mce_job_name (const mce_job_t *self);
+
+static void mce_job_notify (mce_job_t *self);
+static void mce_job_execute (mce_job_t *self);
+
+static void mce_job_delete (mce_job_t *self);
+static mce_job_t *mce_job_create (const char *context, const char *name, void *(*handle)(void *), void (*notify)(void *, void *), void *param);
+
+/* ------------------------------------------------------------------------- *
+ * MCE_JOBLIST
+ * ------------------------------------------------------------------------- */
+
+/** Job list object */
+typedef struct {
+ /* Pointer to the first job in the queue */
+ mce_job_t *mjl_head;
+ /* Pointer to the last job in the queue */
+ mce_job_t *mjl_tail;
+} mce_joblist_t;
+
+static mce_job_t *mce_joblist_pull (mce_joblist_t *self);
+static void mce_joblist_push (mce_joblist_t *self, mce_job_t *job);
+static void mce_joblist_delete (mce_joblist_t *self);
+static mce_joblist_t *mce_joblist_create (void);
+
+/* ------------------------------------------------------------------------- *
+ * MCE_WORKER
+ * ------------------------------------------------------------------------- */
+
+static gboolean mce_worker_notify_cb (GIOChannel *chn, GIOCondition cnd, gpointer data);
+static void mce_worker_execute (void);
+static void *mce_worker_main (void *aptr);
+
+void mce_worker_add_job (const char *context, const char *name, void *(*handle)(void *), void (*notify)(void *, void *), void *param);
+
+void mce_worker_add_context(const char *context);
+void mce_worker_rem_context(const char *context);
+static bool mce_worker_has_context(const char *context);
+
+bool mce_worker_init (void);
+void mce_worker_quit (void);
+
+/** Flag for: Worker thread is running */
+static bool mw_is_ready = false;
+
+/** List of jobs to be executed */
+static mce_joblist_t *mw_req_list = 0;
+
+/** Mutex protecting access to mw_req_list */
+static pthread_mutex_t mw_req_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/** eventfd descriptor for waking up worker thread after adding new jobs */
+static int mw_req_evfd = -1;
+
+/** Worker thread id */
+static pthread_t mw_req_tid = 0;
+
+/** List of jobs already executed */
+static mce_joblist_t *mw_rsp_list = 0;
+
+/** Mutex protecting access to mw_rsp_list */
+static pthread_mutex_t mw_rsp_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/** eventfd descriptor for waking up main thread after executing jobs */
+static int mw_rsp_evfd = -1;
+
+/** I/O watch identifier for mw_rsp_evfd */
+static guint mw_rsp_wid = 0;
+
+/** Lookup table containing valid context strings */
+static GHashTable *mw_ctx_lut = 0;
+
+/** Mutex protecting access to mw_ctx_lut */
+static pthread_mutex_t mw_ctx_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* ========================================================================= *
+ * MISC_UTIL
+ * ========================================================================= */
+
+/** Helper for creating I/O watch for file descriptor
+ */
+static guint
+mw_add_iowatch(int fd, bool close_on_unref,
+ GIOCondition cnd, GIOFunc io_cb, gpointer aptr)
+{
+ guint wid = 0;
+ GIOChannel *chn = 0;
+
+ if( !(chn = g_io_channel_unix_new(fd)) )
+ goto cleanup;
+
+ g_io_channel_set_close_on_unref(chn, close_on_unref);
+
+ cnd |= G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+
+ if( !(wid = g_io_add_watch(chn, cnd, io_cb, aptr)) )
+ goto cleanup;
+
+cleanup:
+ if( chn != 0 ) g_io_channel_unref(chn);
+
+ return wid;
+
+}
+
+/* ========================================================================= *
+ * MCE_JOB
+ * ========================================================================= */
+
+/** Get job name string
+ *
+ * @param self job object, or NULL
+ *
+ * @return name of the job, or "unknown"
+ */
+static const char *
+mce_job_name(const mce_job_t *self)
+{
+ const char *name = 0;
+ if( self )
+ name = self->mj_name;
+ return name ?: "unknown";
+}
+
+/** Get job context string
+ *
+ * @param self job object, or NULL
+ *
+ * @return context of the job, or "global"
+ */
+static const char *
+mce_job_context(const mce_job_t *self)
+{
+ const char *context = 0;
+ if( self )
+ context = self->mj_context;
+ return context ?: "global";
+}
+
+/** Job executed notification
+ *
+ * This must be called from the mainloop thread.
+ *
+ * @param self job object, or NULL
+ */
+static void
+mce_job_notify(mce_job_t *self)
+{
+ if( !self )
+ goto EXIT;
+
+ if( !self->mj_notify )
+ goto EXIT;
+
+ mce_log(LL_DEBUG, "job(%s:%s) notify", mce_job_context(self), mce_job_name(self));
+
+ pthread_mutex_lock(&mw_ctx_mutex);
+ if( mce_worker_has_context(self->mj_context) )
+ self->mj_notify(self->mj_param, self->mj_reply);
+ pthread_mutex_unlock(&mw_ctx_mutex);
+
+EXIT:
+ return;
+}
+
+/** Execute job
+ *
+ * This must be called from the worker thread.
+ *
+ * @param self job object, or NULL
+ */
+static void
+mce_job_execute(mce_job_t *self)
+{
+ if( !self )
+ goto EXIT;
+
+ if( !self->mj_handle )
+ goto EXIT;
+
+ mce_log(LL_DEBUG, "job(%s:%s) execute", mce_job_context(self), mce_job_name(self));
+
+ pthread_mutex_lock(&mw_ctx_mutex);
+ if( mce_worker_has_context(self->mj_context) )
+ self->mj_reply = self->mj_handle(self->mj_param);
+ pthread_mutex_unlock(&mw_ctx_mutex);
+
+EXIT:
+ return;
+}
+
+/** Delete job object
+ *
+ * @param self job object, or NULL
+ */
+static void
+mce_job_delete(mce_job_t *self)
+{
+ if( !self )
+ goto EXIT;
+
+ mce_log(LL_DEBUG, "job(%s:%s) deleted", mce_job_context(self), mce_job_name(self));
+
+ free(self->mj_name);
+ free(self);
+
+EXIT:
+ return;
+}
+
+/** Create job object
+ *
+ * @param context Validation context string
+ * @param name Job name string
+ * @param handle Execute callback (in worker thread)
+ * @param notify Finished callback (in main thread)
+ * @param param User data to be passed to callbacks
+ *
+ * @return job object
+ */
+
+static mce_job_t *
+mce_job_create(const char *context,
+ const char *name,
+ void *(*handle)(void *),
+ void (*notify)(void *, void *),
+ void *param)
+{
+ mce_job_t *self = calloc(1, sizeof *self);
+
+ self->mj_next = 0;
+ self->mj_context = context ? strdup(context) : 0;
+ self->mj_name = name ? strdup(name) : 0;
+ self->mj_handle = handle;
+ self->mj_notify = notify;
+ self->mj_param = param;
+ self->mj_reply = 0;
+
+ mce_log(LL_DEBUG, "job(%s:%s) created", mce_job_context(self), mce_job_name(self));
+
+ return self;
+}
+
+/* ========================================================================= *
+ * MCE_JOBLIST
+ * ========================================================================= */
+
+/** Pull a job object from a list of jobs
+ *
+ * Owenership of non-null job is transferred to the caller.
+
+ * @param self Job list object, or NULL
+ *
+ * @return job object, or NULL
+ */
+static mce_job_t *
+mce_joblist_pull(mce_joblist_t *self)
+{
+ mce_job_t *job = 0;
+
+ if( !self )
+ goto EXIT;
+
+ if( !(job = self->mjl_head) )
+ goto EXIT;
+
+ if( !(self->mjl_head = job->mj_next) )
+ self->mjl_tail = 0;
+ job->mj_next = 0;
+
+EXIT:
+ return job;
+}
+
+/** Pull a job object from a list of jobs
+ *
+ * Owenership of non-null job is transferred to the joblist.
+ *
+ * @param self Job list object, or NULL
+ * @param job Job object, or NULL
+ */
+static void
+mce_joblist_push(mce_joblist_t *self, mce_job_t *job)
+{
+ if( !self || !job )
+ goto EXIT;
+
+ if( self->mjl_tail )
+ self->mjl_tail->mj_next = job;
+ else
+ self->mjl_head = job;
+ self->mjl_tail = job;
+ job = 0;
+
+EXIT:
+ if( job ) {
+ mce_log(LL_ERR, "job(%s:%s) could not be queued; deleting",
+ mce_job_context(job), mce_job_name(job));
+ mce_job_delete(job);
+ }
+
+ return;
+}
+
+/** Delete job list object and all contained jobs
+ *
+ * @param self Job list object, or NULL
+ */
+static void
+mce_joblist_delete(mce_joblist_t *self)
+{
+ mce_job_t *job;
+
+ if( !self )
+ goto EXIT;
+
+ while( (job = mce_joblist_pull(self)) )
+ mce_job_delete(job);
+
+EXIT:
+ return;
+}
+
+/** Create job list object
+ *
+ * @return job list object
+ */
+static mce_joblist_t *
+mce_joblist_create(void)
+{
+ mce_joblist_t *self = calloc(1, sizeof *self);
+
+ self->mjl_head = 0;
+ self->mjl_tail = 0;
+
+ return self;
+}
+
+/* ========================================================================= *
+ * MCE_WORKER
+ * ========================================================================= */
+
+/** Check validity of job context
+ *
+ * Note: Caller must hold mw_ctx_mutex.
+ *
+ * @param context Context string, or NULL for global
+ *
+ * @return true if context is valid, false otherwise
+ */
+static bool
+mce_worker_has_context(const char *context)
+{
+ if( !mw_is_ready )
+ return false;
+
+ if( !context )
+ return true;
+
+ if( !mw_ctx_lut )
+ return false;;
+
+ return g_hash_table_lookup(mw_ctx_lut, context) != 0;
+}
+
+/** Mark job context as valid
+ *
+ * @param context Context string, or NULL for nop
+ */
+void
+mce_worker_add_context(const char *context)
+{
+ if( !mw_is_ready )
+ goto EXIT;
+
+ if( !context )
+ goto EXIT;
+
+ if( !mw_ctx_lut )
+ goto EXIT;
+
+ pthread_mutex_lock(&mw_ctx_mutex);
+ g_hash_table_replace(mw_ctx_lut, g_strdup(context), GINT_TO_POINTER(1));
+ pthread_mutex_unlock(&mw_ctx_mutex);
+
+ mce_log(LL_DEBUG, "%s: context enabled", context);
+
+EXIT:
+ return;
+}
+
+/** Mark job context as invalid
+ *
+ * @param context Context string, or NULL for nop
+ */
+void
+mce_worker_rem_context(const char *context)
+{
+ if( !mw_ctx_lut )
+ goto EXIT;
+
+ if( !context )
+ goto EXIT;
+
+ pthread_mutex_lock(&mw_ctx_mutex);
+ g_hash_table_remove(mw_ctx_lut, context);
+ pthread_mutex_unlock(&mw_ctx_mutex);
+
+ mce_log(LL_DEBUG, "%s: context disabled", context);
+
+EXIT:
+ return;
+}
+
+/** Callback for: Handle job executed notifications
+ *
+ * Note: This is called from main thread.
+ *
+ * @param chn I/O channel for eventfd
+ * @param cnd Wakeup reason
+ * @param data User data (not used)
+ *
+ * @return FALSE if io watch should be disabled, TRUE otherwise
+ */
+static gboolean
+mce_worker_notify_cb(GIOChannel *chn, GIOCondition cnd, gpointer data)
+{
+ (void)data;
+
+ gboolean keep_going = FALSE;
+
+ if( !mw_rsp_wid )
+ goto cleanup_nak;
+
+ int fd = g_io_channel_unix_get_fd(chn);
+
+ if( fd < 0 )
+ goto cleanup_nak;
+
+ if( cnd & ~G_IO_IN )
+ goto cleanup_nak;
+
+ if( !(cnd & G_IO_IN) )
+ goto cleanup_ack;
+
+ uint64_t cnt = 0;
+
+ int rc = read(fd, &cnt, sizeof cnt);
+
+ if( rc == 0 ) {
+ mce_log(LL_ERR, "unexpected eof");
+ goto cleanup_nak;
+ }
+
+ if( rc == -1 ) {
+ if( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
+ goto cleanup_ack;
+
+ mce_log(LL_ERR, "read error: %m");
+ goto cleanup_nak;
+ }
+
+ if( rc != sizeof cnt )
+ goto cleanup_nak;
+
+ for( ;; ) {
+ pthread_mutex_lock(&mw_rsp_mutex);
+ mce_job_t *job = mce_joblist_pull(mw_rsp_list);
+ pthread_mutex_unlock(&mw_rsp_mutex);
+
+ if( !job )
+ break;
+
+ mce_job_notify(job);
+ mce_job_delete(job);
+ }
+
+cleanup_ack:
+ keep_going = TRUE;
+
+cleanup_nak:
+
+ if( !keep_going ) {
+ mw_rsp_wid = 0;
+ mce_log(LL_CRIT, "worker notifications disabled");
+ }
+
+ return keep_going;
+}
+
+/** Execute queued jobs
+ *
+ * Note: This is called from worker thread
+ */
+static void
+mce_worker_execute(void)
+{
+ for( ;; ) {
+ pthread_mutex_lock(&mw_req_mutex);
+ mce_job_t *job = mce_joblist_pull(mw_req_list);
+ pthread_mutex_unlock(&mw_req_mutex);
+
+ if( !job )
+ break;
+
+ mce_job_execute(job);
+
+ pthread_mutex_lock(&mw_rsp_mutex);
+ mce_joblist_push(mw_rsp_list, job);
+ pthread_mutex_unlock(&mw_rsp_mutex);
+
+ uint64_t cnt = 1;
+ write(mw_rsp_evfd, &cnt, sizeof cnt);
+ }
+}
+
+/** Worker thread mainloop
+ *
+ * @param aptr user data (not used)
+ *
+ * @return NULL
+ */
+static void *
+mce_worker_main(void *aptr)
+{
+ (void)aptr;
+
+ /* Allow quick and dirty cancellation */
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
+
+ for( ;; ) {
+ uint64_t cnt = 0;
+ int rc = read(mw_req_evfd, &cnt, sizeof cnt);
+
+ if( rc == -1 ) {
+ if( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
+ continue;
+ mce_log(LL_ERR, "read: %m");
+ goto EXIT;
+ }
+
+ if( rc != sizeof cnt )
+ continue;
+
+ if( cnt > 0 )
+ mce_worker_execute();
+ }
+
+EXIT:
+ return 0;
+}
+
+/** Queue a job to be executed in worker thread
+ *
+ * @param context Validation context string, or NULL for global
+ * @param handle Execute job callback
+ * @param notify Job finished notification callback
+ * @param param Pointer to be passed to the callbacks
+ */
+void
+mce_worker_add_job(const char *context, const char *name,
+ void *(*handle)(void *),
+ void (*notify)(void *, void *),
+ void *param)
+{
+ if( !mw_is_ready ) {
+ mce_log(LL_ERR, "job(%s:%s) scheduled while not ready", context, name);
+ goto EXIT;
+ }
+
+ mce_job_t *job = mce_job_create(context, name, handle, notify, param);
+ pthread_mutex_lock(&mw_req_mutex);
+ mce_joblist_push(mw_req_list, job);
+ pthread_mutex_unlock(&mw_req_mutex);
+
+ uint64_t cnt = 1;
+ write(mw_req_evfd, &cnt, sizeof cnt);
+
+EXIT:
+ return;
+}
+
+/** Terminate worker thread
+ */
+void
+mce_worker_quit(void)
+{
+ /* No longer ready to accept jobs */
+ mw_is_ready = false;
+
+ /* Stop worker thread */
+
+ if( mw_req_tid ) {
+ if( pthread_cancel(mw_req_tid) != 0 ) {
+ mce_log(LOG_ERR, "failed to stop worker thread");
+ }
+ else {
+ void *status = 0;
+ pthread_join(mw_req_tid, &status);
+ mce_log(LOG_DEBUG, "worker stopped, status = %p", status);
+ }
+ mw_req_tid = 0;
+ }
+
+ /* Note: The worker thread is killed asynchronously, so it is
+ * possible that the mutexes are left in locked state
+ * and thus must not be used after this stage.
+ */
+
+ /* Remove request pipeline */
+
+ mce_joblist_delete(mw_req_list),
+ mw_req_list = 0;
+
+ if( mw_req_evfd != -1 )
+ close(mw_req_evfd), mw_req_evfd = -1;
+
+ /* Remove notify pipeline */
+
+ if( mw_rsp_wid )
+ g_source_remove(mw_rsp_wid), mw_rsp_wid = 0;
+
+ mce_joblist_delete(mw_rsp_list),
+ mw_rsp_list = 0;
+
+ if( mw_rsp_evfd != -1 )
+ close(mw_rsp_evfd), mw_req_evfd = -1;
+
+ /* Remove context lookup table */
+
+ if( mw_ctx_lut )
+ g_hash_table_unref(mw_ctx_lut), mw_ctx_lut = 0;
+}
+
+/** Start worker thread
+ *
+ * @return true on success, false on failure
+ */
+bool
+mce_worker_init(void)
+{
+ bool ack = false;
+
+ /* Setup context lookup table */
+
+ mw_ctx_lut = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, 0);
+
+ /* Setup notify pipeline */
+
+ if( !(mw_rsp_list = mce_joblist_create()) )
+ goto EXIT;
+
+ if( (mw_rsp_evfd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)) == -1 )
+ goto EXIT;
+
+ mw_rsp_wid = mw_add_iowatch(mw_rsp_evfd, false, G_IO_IN,
+ mce_worker_notify_cb, 0);
+ if( !mw_rsp_wid )
+ goto EXIT;
+
+ /* Setup request pipeline */
+
+ if( !(mw_req_list = mce_joblist_create()) )
+ goto EXIT;
+
+ if( (mw_req_evfd = eventfd(0, EFD_CLOEXEC)) == -1 )
+ goto EXIT;
+
+ /* Start worker thread */
+ if( pthread_create(&mw_req_tid, 0, mce_worker_main, 0) != 0 ) {
+ mw_req_tid = 0;
+ goto EXIT;
+ }
+
+ /* Note: From now on joblist access must use mutex locking */
+
+ /* Ready to accept jobs */
+ mw_is_ready = true;
+
+ ack = true;
+
+EXIT:
+
+ /* All or nothing */
+ if( !ack )
+ mce_worker_quit();
+
+ return ack;
+}
diff --git a/mce-worker.h b/mce-worker.h
new file mode 100644
index 00000000..0f1ef76e
--- /dev/null
+++ b/mce-worker.h
@@ -0,0 +1,49 @@
+/**
+ * @file mce-worker.h
+ *
+ * Mode Control Entity - Offload blocking operations to a worker thread
+ *
+ *
+ *
+ * Copyright (C) 2015 Jolla Ltd.
+ *
+ *
+ *
+ * @author Simo Piiroinen
+ *
+ * mce is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * mce is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mce. If not, see .
+ */
+#ifndef MCE_WORKER_H_
+# define MCE_WORKER_H_
+
+# include
+
+# ifdef __cplusplus
+extern "C" {
+# elif 0
+} /* fool JED indentation ... */
+# endif
+
+void mce_worker_add_job (const char *context, const char *name, void *(*handle)(void *), void (*notify)(void *, void *), void *param);
+
+void mce_worker_add_context(const char *context);
+void mce_worker_rem_context(const char *context);
+
+void mce_worker_quit (void);
+bool mce_worker_init (void);
+
+# ifdef __cplusplus
+};
+# endif
+
+#endif /* MCE_WORKER_H_ */
diff --git a/mce.c b/mce.c
index 21e385da..031ab717 100644
--- a/mce.c
+++ b/mce.c
@@ -32,6 +32,7 @@
#include "mce-command-line.h"
#include "mce-sensorfw.h"
#include "mce-wakelock.h"
+#include "mce-worker.h"
#include "tklock.h"
#include "powerkey.h"
#include "event-input.h"
@@ -948,6 +949,10 @@ int main(int argc, char **argv)
/* Open fbdev as early as possible */
mce_fbdev_init();
+ /* Start worker thread */
+ if( !mce_worker_init() )
+ goto EXIT;
+
/* Get configuration options */
if( !mce_conf_init() ) {
mce_log(LL_CRIT,
@@ -1074,6 +1079,7 @@ int main(int argc, char **argv)
mce_gconf_exit();
mce_dbus_exit();
mce_conf_exit();
+ mce_worker_quit();
mce_fbdev_quit();
/* If the mainloop is initialised, unreference it */