I have made the following changes intended for :
  CE:MW:Shared / mce

Please review and accept or decline.
BOSS has already run some checks on this request.
See the "Messages from BOSS" section below.

https://build.pub.meego.com//request/show/8390

Thank You,
spiiroin

[This message was auto-generated]

---

Request # 8390:

Messages from BOSS:

State: review at 2013-03-08T14:16:45 by bossbot

Reviews:
       accepted by bossbot : Prechecks succeeded.
       new for CE-maintainers : Please replace this text with a review and 
approve/reject the review (not the SR). BOSS will take care of the rest

Changes:
  submit: home:spiiroin:branches:CE:MW:Shared / mce -> CE:MW:Shared / mce
  
changes files:
--------------
--- mce.changes
+++ mce.changes
@@ -0,0 +1,5 @@
+* Wed Mar 06 2013 Simo Piiroinen <[email protected]> - 1.12.8
+- Allow disabling of automatic screen blanking from lock screen
+- System startup ready detection is based on init-done flag file
+- Implement cpu-keepalive plugin for mce
+

old:
----
  mce-1.12.7.tar.bz2

new:
----
  mce-1.12.8.tar.bz2

spec files:
-----------
--- mce.spec
+++ mce.spec
@@ -1,6 +1,6 @@
 Name:       mce
 Summary:    Mode Control Entity for Nokia mobile computers
-Version:    1.12.7
+Version:    1.12.8
 Release:    1
 Group:      System/System Control
 License:    LGPLv2
@@ -20,7 +20,7 @@
 BuildRequires:  pkgconfig(dsme) >= 0.58
 BuildRequires:  pkgconfig(gconf-2.0)
 BuildRequires:  pkgconfig(glib-2.0) >= 2.18.0
-BuildRequires:  pkgconfig(mce) >= 1.12.2
+BuildRequires:  pkgconfig(mce) >= 1.12.3
 BuildRequires:  kernel-headers >= 2.6.32
 BuildRequires:  libi2c-devel
 BuildRequires:  systemd

other changes:
--------------

++++++ mce-1.12.7.tar.bz2 -> mce-1.12.8.tar.bz2
--- .depend
+++ .depend
@@ -66,6 +66,16 @@
        mce-io.h\
        mce.h\
 
+filewatcher.o:\
+       filewatcher.c\
+       filewatcher.h\
+       mce-log.h\
+
+filewatcher.pic.o:\
+       filewatcher.c\
+       filewatcher.h\
+       mce-log.h\
+
 libwakelock.o:\
        libwakelock.c\
        libwakelock.h\
@@ -352,6 +362,18 @@
        mce.h\
        modules/camera.h\
 
+modules/cpu-keepalive.o:\
+       modules/cpu-keepalive.c\
+       mce-dbus.h\
+       mce-log.h\
+       libwakelock.h\
+
+modules/cpu-keepalive.pic.o:\
+       modules/cpu-keepalive.c\
+       mce-dbus.h\
+       mce-log.h\
+       libwakelock.h\
+
 modules/display.o:\
        modules/display.c\
        datapipe.h\
@@ -363,6 +385,7 @@
        mce-lib.h\
        mce-log.h\
        mce.h\
+       filewatcher.h\
        libwakelock.h\
        modules/display.h\
        tklock.h\
@@ -378,6 +401,7 @@
        mce-lib.h\
        mce-log.h\
        mce.h\
+       filewatcher.h\
        libwakelock.h\
        modules/display.h\
        tklock.h\
@@ -608,6 +632,14 @@
        mce.h\
        powerkey.h\
 
+test_inotify.o:\
+       test_inotify.c\
+       filewatcher.h\
+
+test_inotify.pic.o:\
+       test_inotify.c\
+       filewatcher.h\
+
 tklock.o:\
        tklock.c\
        datapipe.h\
@@ -647,6 +679,7 @@
 tools/mcetool.o:\
        tools/mcetool.c\
        modules/display.h\
+       modules/filter-brightness-als.h\
        modules/powersavemode.h\
        tklock.h\
        tools/mcetool.h\
@@ -654,6 +687,7 @@
 tools/mcetool.pic.o:\
        tools/mcetool.c\
        modules/display.h\
+       modules/filter-brightness-als.h\
        modules/powersavemode.h\
        tklock.h\
        tools/mcetool.h\
--- Makefile
+++ Makefile
@@ -43,7 +43,7 @@
 # CONFIGURATION
 # ----------------------------------------------------------------------------
 
-VERSION := 1.12.7
+VERSION := 1.12.8
 
 INSTALL_BIN := install --mode=755
 INSTALL_DIR := install -d
@@ -147,6 +147,7 @@
 MODULES += $(MODULE_DIR)/callstate.so
 MODULES += $(MODULE_DIR)/audiorouting.so
 MODULES += $(MODULE_DIR)/powersavemode.so
+MODULES += $(MODULE_DIR)/cpu-keepalive.so
 
 # Tools to build
 TOOLS   += $(TOOLDIR)/mcetool
@@ -275,6 +276,7 @@
 MCE_CORE += mce-lib.c
 MCE_CORE += median_filter.c
 MCE_CORE += evdev.c
+MCE_CORE += filewatcher.c
 
 # HACK: do not link against libgconf-2
 ifeq ($(strip $(ENABLE_BUILTIN_GCONF)),y)
--- builtin-gconf.c
+++ builtin-gconf.c
@@ -1272,6 +1272,12 @@
     .def  = "1",
   },
   {
+    // MCE_GCONF_TK_AUTO_BLANK_DISABLE_PATH @ tklock.h
+    .key  = "/system/osso/dsm/locks/tklock_blank_disable",
+    .type = "i",
+    .def  = "0",
+  },
+  {
     .key = NULL,
   }
 };
--- debian/changelog
+++ debian/changelog
@@ -1,3 +1,11 @@
+mce (1.12.8) unstable; urgency=low
+
+  * Allow disabling of automatic screen blanking from lock screen
+  * System startup ready detection is based on init-done flag file
+  * Implement cpu-keepalive plugin for mce
+
+ -- Simo Piiroinen <[email protected]>  Wed, 06 Mar 2013 14:47:35 
+0200
+
 mce (1.12.7) unstable; urgency=low
 
   * Allow user processes to make get_config and set_config method calls
--- filewatcher.c
+++ filewatcher.c
@@ -0,0 +1,533 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright (C) 2013 Jolla Ltd.
+ * Contact: Simo Piiroinen <[email protected]>
+ * License: LGPLv2
+ * ------------------------------------------------------------------------- */
+
+#include "filewatcher.h"
+#include "mce-log.h"
+
+#include <sys/inotify.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#define DEBUG_INOTIFY_EVENTS 0
+
+static inline void *lea(const void *base, int offs)
+{
+  return ((char *)base)+offs;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Inotify event debugging helpers
+ * ------------------------------------------------------------------------- */
+
+#if DEBUG_INOTIFY_EVENTS
+/** Convert inotify event bitmask to human readable string
+ *
+ * @param mask event bit mask
+ * @param buff where to construct the string
+ * @param size how much space buff has (must be > 0)
+ *
+ * @return string with names of set bits separated with '+'
+ */
+static
+const char *
+inotify_mask_repr(uint32_t mask, char *buff, size_t size)
+{
+  static const struct
+  {
+    uint32_t    mask;
+    const char *name;
+  } lut[] =
+  {
+# define X(tag) { .mask = IN_##tag, .name = #tag },
+    X(ACCESS)
+    X(MODIFY)
+    X(ATTRIB)
+    X(CLOSE_WRITE)
+    X(CLOSE_NOWRITE)
+    X(OPEN)
+    X(MOVED_FROM)
+    X(MOVED_TO)
+    X(CREATE)
+    X(DELETE)
+    X(DELETE_SELF)
+    X(MOVE_SELF)
+    X(UNMOUNT)
+    X(Q_OVERFLOW)
+    X(IGNORED)
+    X(ONLYDIR)
+    X(DONT_FOLLOW)
+    X(EXCL_UNLINK)
+    X(MASK_ADD)
+    X(ISDIR)
+    X(ONESHOT)
+# undef X
+    { .mask = 0, .name = 0 }
+  };
+
+  char *pos = buff;
+  char *end = buff + size - 1;
+
+  auto void adds(const char *s)
+  {
+    while( *s && pos < end) *pos++ = *s++;
+  }
+
+  for( size_t i = 0; lut[i].mask; ++i )
+  {
+    if( mask & lut[i].mask )
+    {
+      mask ^= lut[i].mask;
+      if( pos > buff ) adds("+");
+      adds(lut[i].name);
+    }
+  }
+  if( mask )
+  {
+    char hex[32];
+    snprintf(hex, sizeof hex, "0x%"PRIx32, mask);
+    if( pos > buff ) adds("+");
+    adds(hex);
+  }
+
+  return *pos = 0, buff;
+}
+
+/** Emit inotify event details
+ *
+ * @param eve inotify_event pointer
+ */
+static
+void
+inotify_event_debug(const struct inotify_event *eve)
+{
+  char temp[256];
+  printf("wd=%d\n", eve->wd);
+  printf("mask=%s\n", inotify_mask_repr(eve->mask, temp, sizeof temp));
+  if( eve->len )
+  {
+    printf("name=\"%s\"\n", eve->name);
+  }
+  printf("\n");
+}
+#endif /* DEBUG_INOTIFY_EVENTS */
+
+/* ------------------------------------------------------------------------- *
+ * File content change tracking
+ * ------------------------------------------------------------------------- */
+
+/** Object for tracking file content in a directory */
+struct filewatcher_t
+{
+  /** inotify file descriptor */
+  int inotify_fd;
+
+  /** inotify watch descriptor */
+  int inotify_wd;
+
+  /** glib input watch for inotify_fd */
+  guint watch_id;
+
+  /** the directory to watch over */
+  char *watch_path;
+
+  /** the file in the watch_path to track */
+  char *watch_file;
+
+  /** function to call when watch_path/watch_file changes */
+  filewatcher_changed_fn changed_cb;
+
+  /** user data to pass to changed_cb */
+  gpointer user_data;
+
+  /** how to delete user_data when filewatcher_t is deleted */
+  GDestroyNotify delete_cb;
+};
+
+/* Initialize filewatcher_t object to a sane state
+ *
+ * @param self pointer to uninitialized filewatcher_t object
+ */
+static
+void
+filewatcher_ctor(filewatcher_t *self)
+{
+  self->inotify_fd = -1;
+  self->inotify_wd = -1;
+  self->watch_path = 0;
+  self->watch_file = 0;
+
+  self->watch_id   = 0;
+
+  self->changed_cb = 0;
+
+  self->delete_cb  = 0;
+  self->user_data  = 0;
+}
+
+/* Release all dynamic data from filewatcher_t object
+ *
+ * @param self pointer to initialized filewatcher_t object
+ */
+static
+void
+filewatcher_dtor(filewatcher_t *self)
+{
+  /* detach user data */
+  if( self->delete_cb )
+  {
+    self->delete_cb(self->user_data);
+  }
+  self->user_data = 0;
+
+  /* detach glib io watch */
+  if( self->watch_id )
+  {
+    g_source_remove(self->watch_id), self->watch_id = 0;
+  }
+
+  /* detach inotify fd */
+  if( self->inotify_fd != -1 )
+  {
+    if( self->inotify_wd != -1 )
+    {
+      if( inotify_rm_watch(self->inotify_fd, self->inotify_wd) == -1 )
+      {
+        mce_log(LL_WARN, "inotify_rm_watch: %m");
+      }
+      self->inotify_wd = -1;
+    }
+    if( close(self->inotify_fd) == -1 )
+    {
+      mce_log(LL_WARN, "close inotify fd: %m");
+    }
+    self->inotify_fd = -1;
+  }
+
+  /* release strings */
+  g_free(self->watch_path), self->watch_path = 0;
+  g_free(self->watch_file), self->watch_file = 0;
+}
+
+/* Delete a filewatcher_t object
+ *
+ * @param self pointer to initialized filewatcher_t object, or NULL
+ */
+void
+filewatcher_delete(filewatcher_t *self)
+{
+  if( self != 0 )
+  {
+    filewatcher_dtor(self);
+    g_free(self);
+  }
+}
+
+/** Process inotify events
+ *
+ * @param self pointer to filewatcher_t object
+ *
+ * @return TRUE on success, or FALSE if further processing is not possible
+ */
+static
+gboolean
+filewatcher_process_events(filewatcher_t *self)
+{
+  gboolean res = FALSE;
+  gboolean flg = FALSE;
+
+  char buf[2048];
+  int todo, size;
+  struct inotify_event *eve;
+
+  if( !self || self->inotify_fd == -1 )
+  {
+    goto cleanup;
+  }
+
+  todo = read(self->inotify_fd, buf, sizeof buf);
+
+  if( todo < 0 )
+  {
+    switch( errno )
+    {
+    case EAGAIN:
+    case EINTR:
+      res = TRUE;
+      break;
+
+    default:
+      mce_log(LL_WARN, "read inotify events: %m");
+      break;
+    }
+    goto cleanup;
+  }
+
+  if( todo == 0 )
+  {
+    mce_log(LL_WARN, "read inotify events: EOF");
+    goto cleanup;
+  }
+
+#if DEBUG_INOTIFY_EVENTS
+  printf("----\n");
+#endif
+
+  for( eve = lea(buf, 0); todo; todo -= size, eve = lea(eve, size))
+  {
+    if( todo < (int)sizeof *eve )
+    {
+      mce_log(LL_WARN, "partial inotify event received");
+      goto cleanup;
+    }
+
+    size = sizeof *eve + eve->len;
+
+    if( todo < size )
+    {
+      mce_log(LL_WARN, "oversized inotify event received");
+      goto cleanup;
+    }
+
+#if DEBUG_INOTIFY_EVENTS
+    inotify_event_debug(eve);
+#endif
+
+    if( eve->len && !strcmp(self->watch_file, eve->name) )
+    {
+      flg = TRUE;
+    }
+
+    if( eve->mask & IN_IGNORED )
+    {
+      mce_log(LL_ERR, "inotify watch went defunct");
+      flg = TRUE;
+      goto cleanup;
+    }
+  }
+
+  res = TRUE;
+
+cleanup:
+
+  if( flg && self && self->changed_cb )
+  {
+    self->changed_cb(self->watch_path, self->watch_file, self->user_data);
+  }
+
+  return res;
+}
+
+/** Glib io glue for processing inotify event input
+ *
+ * @param source (not used)
+ * @param condition (not used)
+ * @param data pointer to filewatcher_t object (as void pointer)
+ *
+ * @return TRUE to keep the io watch alive, or
+ *         FALSE if the io watch must be released
+ */
+static
+gboolean
+filewatcher_input_cb(GIOChannel *source,
+                     GIOCondition condition,
+                     gpointer data)
+{
+  (void)source; (void)condition;
+
+  filewatcher_t *self = data;
+  gboolean keep_going = filewatcher_process_events(self);
+
+  if( !keep_going )
+  {
+    mce_log(LL_WARN, "stopping inotify event io watch");
+    self->watch_id = 0;
+  }
+
+  return keep_going;
+}
+
+/** Helper for setting up inotify file descriptor
+ *
+ * @note This function is meant to be called form
+ *       filewatcher_create() function only!
+ *
+ * @param self pointer to filewatcher_t object
+ *
+ * @return TRUE on success, or FALSE on failure
+ */
+static
+gboolean
+filewatcher_setup_inotify(filewatcher_t *self)
+{
+  gboolean success = FALSE;
+
+  uint32_t mask = (0
+                   | IN_CREATE
+                   | IN_DELETE
+                   | IN_CLOSE_WRITE
+                   | IN_MOVED_TO
+                   | IN_MOVED_FROM
+                   | IN_DONT_FOLLOW
+                   | IN_ONLYDIR);
+
+  self->inotify_fd = inotify_init1(IN_CLOEXEC);
+  if( self->inotify_fd == -1 )
+  {
+    mce_log(LL_WARN, "inotify_init: %m");
+    goto cleanup;
+  }
+
+  self->inotify_wd = inotify_add_watch(self->inotify_fd,
+                                       self->watch_path,  mask);
+  if( self->inotify_wd == -1 )
+  {
+    mce_log(LL_WARN, "%s: inotify_add_watch: %m", self->watch_path);
+    goto cleanup;
+  }
+
+  success = TRUE;
+
+cleanup:
+  return success;
+}
+
+/** Helper for setting up glib io watch for inotify file descriptor
+ *
+ * @note This function is meant to be called form
+ *       filewatcher_create() function only!
+ *
+ * @param self pointer to filewatcher_t object
+ *
+ * @return TRUE on success, or FALSE on failure
+ */
+static
+gboolean
+filewatcher_setup_iowatch(filewatcher_t *self)
+{
+  gboolean success = FALSE;
+
+  GIOChannel *chan  = 0;
+  GError     *err   = 0;
+
+  if( !(chan = g_io_channel_unix_new(self->inotify_fd)) )
+  {
+    mce_log(LL_WARN, "%s: %m", "g_io_channel_unix_new");
+    goto cleanup;
+  }
+
+  /* the channel does not own the fd  */
+  g_io_channel_set_close_on_unref(chan, FALSE);
+
+  /* Set to NULL encoding so that we can turn off the buffering */
+  if( g_io_channel_set_encoding(chan, NULL, &err) != G_IO_STATUS_NORMAL )
+  {
+    mce_log(LL_WARN, "%s: %s", "g_io_channel_set_encoding",
+            (err && err->message) ? err->message : "unknown");
+  }
+  g_io_channel_set_buffered(chan, FALSE);
+
+  self->watch_id = g_io_add_watch(chan, G_IO_IN, filewatcher_input_cb, self);
+
+  if( !self->watch_id )
+  {
+    mce_log(LL_WARN, "%s: %m", "g_io_add_watch");
+    goto cleanup;
+  }
+
+  success = TRUE;
+
+cleanup:
+
+  g_clear_error(&err);
+
+  if( chan ) g_io_channel_unref(chan);
+
+  return success;
+}
+
+/** Create an filewatcher_t object
+ *
+ * An inotify watcher is started for the given director/file.
+ * A glib io watch is used to process the inotify events.
+ * The change_cb is called when contents of the tracked file
+ * are assumed to have changed.
+ *
+ * @note The change_cb function will not be called during the
+ *       initialization. You can make initial state evaluation
+ *       to happen by calling filewatcher_force_trigger() after
+ *       succesfull filewatcher_create().
+ *
+ * @param dirpath directory to watch over
+ * @param filename file to watch in dirpath
+ * @param change_cb function to call when dirpath/filename changes
+ * @param user_data extra parameter to pass to change_cb
+ * @param delete_cb called on user_data when filewatcher_t itself is deleted
+ *
+ * @return pointer to filewatcher_t object, or NULL in case of errors
+ */
+filewatcher_t *
+filewatcher_create(const char *dirpath,
+                   const char *filename,
+                   filewatcher_changed_fn change_cb,
+                   gpointer user_data,
+                   GDestroyNotify delete_cb)
+{
+  gboolean success = FALSE;
+
+  filewatcher_t *self = g_malloc0(sizeof *self);
+  filewatcher_ctor(self);
+
+  self->watch_path = g_strdup(dirpath);
+  self->watch_file = g_strdup(filename);
+
+  self->changed_cb = change_cb;
+
+  self->user_data  = user_data;
+  self->delete_cb  = delete_cb;
+
+  if( !filewatcher_setup_inotify(self) )
+  {
+    goto cleanup;
+  }
+  if( !filewatcher_setup_iowatch(self) )
+  {
+    goto cleanup;
+  }
+
+  success = TRUE;
+
+cleanup:
+
+  if( !success )
+  {
+    filewatcher_delete(self), self = 0;
+  }
+
+  return self;
+}
+
+/** Force calling the change notification callback
+ *
+ * This can be useful for example to feed initial
+ * state of tracked file via the same mechanism as
+ * the later changes get reported
+ *
+ * @param self pointer to filewatcher_t object
+ */
+void
+filewatcher_force_trigger(filewatcher_t *self)
+{
+  if( self->changed_cb )
+  {
+    self->changed_cb(self->watch_path, self->watch_file, self->user_data);
+  }
+}
--- filewatcher.h
+++ filewatcher.h
@@ -0,0 +1,38 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright (C) 2013 Jolla Ltd.
+ * Contact: Simo Piiroinen <[email protected]>
+ * License: LGPLv2
+ * ------------------------------------------------------------------------- */
+
+#ifndef FILEWATCHER_H_
+# define FILEWATCHER_H_
+
+# include <glib.h>
+
+# ifdef __cplusplus
+extern "C" {
+# elif 0
+} /* fool JED indentation ... */
+# endif
+
+typedef void (*filewatcher_changed_fn)(const char *path,
+                                      const char *file,
+                                      gpointer user_data);
+
+typedef struct filewatcher_t filewatcher_t;
+
+filewatcher_t *filewatcher_create(const char *dirpath,
+                                  const char *filename,
+                                  filewatcher_changed_fn change_cb,
+                                  gpointer user_data,
+                                  GDestroyNotify delete_cb);
+
+void filewatcher_delete(filewatcher_t *self);
+
+void filewatcher_force_trigger(filewatcher_t *self);
+
+# ifdef __cplusplus
+};
+# endif
+
+#endif /* FILEWATCHER_H_ */
--- mce-dbus.c
+++ mce-dbus.c
@@ -52,6 +52,26 @@
 /** Pointer to the DBusConnection */
 static DBusConnection *dbus_connection = NULL;
 
+
+
+/** Return reference to dbus connection cached at mce-dbus module
+ *
+ * For use in situations where the abstraction provided by mce-dbus
+ * makes things too complicated.
+ *
+ * Caller must release non-null return values with dbus_connection_unref().
+ *
+ * @return DBusConnection, or NULL if mce has no dbus connection
+ */
+DBusConnection *dbus_connection_get(void)
+{
+       if( !dbus_connection ) {
+               mce_log(LL_WARN, "no dbus connection");
+               return NULL;
+       }
+       return dbus_connection_ref(dbus_connection);
+}
+
 /**
  * Create a new D-Bus signal, with proper error checking
  * will exit the mainloop if an error occurs
--- mce-dbus.h
+++ mce-dbus.h
@@ -26,6 +26,8 @@
 
 #include <mce/dbus-names.h>
 
+DBusConnection *dbus_connection_get(void);
+
 DBusMessage *dbus_new_signal(const gchar *const path,
                             const gchar *const interface,
                             const gchar *const name);
--- mce.conf
+++ mce.conf
@@ -104,5 +104,15 @@
                <allow send_destination="com.nokia.mce"
                       send_interface="com.nokia.mce.request"
                       send_member="req_led_disable"/>
+
+               <allow send_destination="com.nokia.mce"
+                      send_interface="com.nokia.mce.request"
+                      send_member="req_cpu_keepalive_period"/>
+               <allow send_destination="com.nokia.mce"
+                      send_interface="com.nokia.mce.request"
+                      send_member="req_cpu_keepalive_start"/>
+               <allow send_destination="com.nokia.mce"
+                      send_interface="com.nokia.mce.request"
+                      send_member="req_cpu_keepalive_stop"/>
        </policy>
 </busconfig>
--- mce.ini
+++ mce.ini
@@ -16,7 +16,7 @@
 # to avoid unnecessary brightness fluctuations on mce startup
 #
 # Note: the name should not include the "lib"-prefix
-Modules=radiostates;filter-brightness-als;display;keypad;led;battery;inactivity;alarm;callstate;audiorouting;proximity;powersavemode
+Modules=radiostates;filter-brightness-als;display;keypad;led;battery;inactivity;alarm;callstate;audiorouting;proximity;powersavemode;cpu-keepalive
 
 
 [HomeKey]
--- modules/cpu-keepalive.c
+++ modules/cpu-keepalive.c
@@ -0,0 +1,1002 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright (C) 2013 Jolla Ltd.
+ * Contact: Simo Piiroinen <[email protected]>
+ * License: LGPLv2
+ * ------------------------------------------------------------------------- */
+
+/**
+ * @file cpu-keepalive.c
+ * cpu-keepalive module -- this implements late suspend blocking for MCE
+ * <p>
+ * Copyright © 2013 Jolla Ltd.
+ * <p>
+ * @author Simo Piiroinen <[email protected]>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <gmodule.h>
+#include <time.h>
+#include <string.h>
+
+#include "mce-log.h"
+#include "mce-dbus.h"
+
+#ifdef ENABLE_WAKELOCKS
+# include "../libwakelock.h"
+#endif
+
+typedef struct client_t client_t;
+
+G_MODULE_EXPORT const gchar *g_module_check_init(GModule *module);
+G_MODULE_EXPORT void         g_module_unload    (GModule *module);
+
+/** The name of this module */
+static const char module_name[] = "cpu-keepalive";
+
+#ifdef ENABLE_WAKELOCKS
+/** RTC wakeup wakelock - acquired by dsme and released by mce */
+static const char rtc_wakelock[] = "mce_rtc_wakeup";
+
+/* CPU keepalive wakelock - held by mce while there are active clients */
+static const char cpu_wakelock[] = "mce_cpu_keepalive";
+#endif /* ENABLE_WAKELOCKS */
+
+static DBusConnection *systembus = 0;
+
+/** Clients we are tracking over D-Bus */
+static GHashTable *clients = 0;
+
+/** Timestamp of wakeup from dsme */
+static time_t wakeup_started  = 0;
+
+/** Timeout for "clients should have issued keep alive requests" */
+static time_t wakeup_timeout  = 0;
+
+/** Timer for releasing cpu-keepalive wakelock */
+static guint timer_id = 0;
+
+/** Maximum delay between MCE_CPU_KEEPALIVE_START_REQ method calls */
+#ifdef ENABLE_WAKELOCKS
+# define MCE_CPU_KEEPALIVE_PERIOD_SECONDS 60         // 1 minute
+#else
+# define MCE_CPU_KEEPALIVE_PERIOD_SECONDS (60*60*24) // 1 day
+#endif
+
+/** Maximum delay between rtc wakeup and the 1st keep alive request */
+#define MCE_RTC_WAKEUP_1ST_TIMEOUT_SECONDS   2
+
+/** Extend rtc wakeup timeout if at least one keep alive request is received */
+#define MCE_RTC_WAKEUP_2ND_TIMEOUT_SECONDS   4
+
+/* ========================================================================= *
+ *
+ * GENERIC UTILITIES
+ *
+ * ========================================================================= */
+
+/** Get monotonic timestamp not affected by system time / timezone changes
+ *
+ * @return seconds since some reference point in time
+ */
+static
+time_t
+cpu_keepalive_get_time(void)
+{
+  struct timespec ts;
+  clock_gettime(CLOCK_MONOTONIC, &ts);
+  return ts.tv_sec;
+}
+
+/* ========================================================================= *
+ *
+ * D-BUS UTILITIES
+ *
+ * ========================================================================= */
+
+/** Helper for sending boolean replies to dbus method calls
+ *
+ * Reply will not be sent if no_reply attribute is set
+ * in the method call message.
+ *
+ * @param msg method call message to reply
+ * @param value TRUE/FALSE to send
+ *
+ * @return TRUE on success, or FALSE if reply could not be sent
+ */
+static
+gboolean
+cpu_keepalive_reply_bool(DBusMessage *const msg, gboolean value)
+{
+  gboolean success = TRUE;
+
+  if( !dbus_message_get_no_reply(msg) )
+  {
+    dbus_bool_t  data  = value;
+    DBusMessage *reply = dbus_new_method_reply(msg);
+    dbus_message_append_args(reply,
+                            DBUS_TYPE_BOOLEAN, &data,
+                            DBUS_TYPE_INVALID);
+
+    /* dbus_send_message() unrefs the message */
+    success  = dbus_send_message(reply), reply = 0;
+
+    if( !success )
+    {
+      mce_log(LL_WARN, "failed to send reply to %s",
+             dbus_message_get_member(msg));
+    }
+  }
+
+  return success;
+}
+
+/** Helper for sending boolean replies to dbus method calls
+ *
+ * Reply will not be sent if no_reply attribute is set
+ * in the method call message.
+ *
+ * @param msg method call message to reply
+ * @param value integer number to send
+ *
+ * @return TRUE on success, or FALSE if reply could not be sent
+ */
+static
+gboolean
+cpu_keepalive_reply_int(DBusMessage *const msg, gint value)
+{
+  gboolean success = TRUE;
+
+  if( !dbus_message_get_no_reply(msg) )
+  {
+    dbus_int32_t data  = value;
+    DBusMessage *reply = dbus_new_method_reply(msg);
+    dbus_message_append_args(reply,
+                            DBUS_TYPE_INT32, &data,
+                            DBUS_TYPE_INVALID);
+
+    /* dbus_send_message() unrefs the message */
+    success  = dbus_send_message(reply), reply = 0;
+
+    if( !success )
+    {
+      mce_log(LL_WARN, "failed to send reply to %s",
+             dbus_message_get_member(msg));
+    }
+  }
+
+  return success;
+}
+
+/** Create a GetNameOwner method call message
+ *
+ * @param name the dbus name to query
+ *
+ * @return DBusMessage pointer
+ */
+static
+DBusMessage *
+cpu_keepalive_create_GetNameOwner_req(const char *name)
+{
+  DBusMessage *req = 0;
+
+  req = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
+                                     DBUS_PATH_DBUS,
+                                     DBUS_INTERFACE_DBUS,
+                                     "GetNameOwner");
+  dbus_message_append_args(req,
+                          DBUS_TYPE_STRING, &name,
+                          DBUS_TYPE_INVALID);
+
+  return req;
+}
+
+/** Parse a reply message to GetNameOwner method call
+ *
+ * @param rsp method call reply message
+ *
+ * @return dbus name of the name owner, or NULL in case of errors
+ */
+static
+gchar *
+cpu_keepalive_parse_GetNameOwner_rsp(DBusMessage *rsp)
+{
+  char     *res = 0;
+  DBusError err = DBUS_ERROR_INIT;
+  char     *dta = NULL;
+
+  if( dbus_set_error_from_message(&err, rsp) ||
+      !dbus_message_get_args(rsp, &err,
+                            DBUS_TYPE_STRING, &dta,
+                            DBUS_TYPE_INVALID) )
+  {
+      if( strcmp(err.name, DBUS_ERROR_NAME_HAS_NO_OWNER) ) {
+         mce_log(LL_WARN, "%s: %s", err.name, err.message);
+      }
+      goto EXIT;
+  }
+
+  res = g_strdup(dta);
+
+EXIT:
+  dbus_error_free(&err);
+  return res;
+}
+
+/* ========================================================================= *
+ *
+ * INFORMATION ABOUT ACTIVE CLIENTS
+ *
+ * ========================================================================= */
+
+/** Format string for constructing name owner lost match rules */
+static const char client_match_fmt[] =
+"type='signal'"
+",sender='"DBUS_SERVICE_DBUS"'"
+",interface='"DBUS_INTERFACE_DBUS"'"
+",member='NameOwnerChanged'"
+",path='"DBUS_PATH_DBUS"'"
+",arg0='%s'"
+",arg2=''"
+;
+
+/** Book keeping information for clients we are tracking */
+struct client_t
+{
+  /** The (private/sender) name of the dbus client */
+  gchar  *dbus_name;
+
+  /** NameOwnerChanged signal match used for tracking death of client */
+  char   *match_rule;
+
+  /** Upper bound for reneval of cpu keepalive for this client */
+  time_t  timeout;
+};
+
+/** Clear client cpu-keepalive timeout
+ *
+ * @param self pointer to client_t structure
+ */
+static
+void
+client_clear_timeout(client_t *self)
+{
+  self->timeout = 0;
+}
+
+
+/** Update client cpu-keepalive timeout
+ *
+ * @param self pointer to client_t structure
+ * @param when end of client cpu-keepalive period
+ */
+static
+void
+client_update_timeout(client_t *self, time_t when)
+{
+  if( self->timeout < when )
+  {
+    self->timeout = when;
+  }
+}
+
+
+/** Create bookkeeping information for a dbus client
+ *
+ * Note: Will also add signal matching rule so that we get notified
+ *       when the client loses dbus connection
+ *
+ * @param dbus_name name of the dbus client to track
+ *
+ * @return pointer to client_t structure
+ */
+static
+client_t *
+client_create(const char *dbus_name)
+{
+  client_t *self = g_malloc0(sizeof *self);
+
+  self->dbus_name  = g_strdup(dbus_name);
+  self->match_rule = g_strdup_printf(client_match_fmt, self->dbus_name);
+  self->timeout    = 0;
+
+  mce_log(LL_NOTICE, "added cpu-keepalive client %s", self->dbus_name);
+
+  /* NULL error -> match will be added asynchronously */
+  dbus_bus_add_match(systembus, self->match_rule, 0);
+
+  return self;
+}
+
+/** Destroy bookkeeping information about a dbus client
+ *
+ * Note: Will also remove the signal matching rule used for detecting
+ *       when the client loses dbus connection
+ *
+ * @param self pointer to client_t structure
+ */
+
+static
+void
+client_delete(client_t *self)
+{
+  if( self != 0 )
+  {
+    mce_log(LL_NOTICE, "removed cpu-keepalive client %s", self->dbus_name);
+
+    /* NULL error -> match will be removed asynchronously */
+    dbus_bus_remove_match(systembus, self->match_rule, 0);
+
+    g_free(self->dbus_name);
+    g_free(self->match_rule);
+    g_free(self);
+  }
+}
+
+/** Typeless helper function for use as destroy callback
+ *
+ * @param self pointer to client_t structure
+ */
+static
+void
+client_delete_cb(void *self)
+{
+  client_delete(self);
+}
+
+/* ========================================================================= *
+ *
+ * CPU-KEEPALIVE TIMER MANAGEMENT
+ *
+ * ========================================================================= */
+
+/** Handle triggering of cpu-keepalive timer
+ *
+ * Releases cpu keepalive wakelock and thus allows the system to
+ * enter late suspend according to other policies.
+ *
+ * @param data (not used)
+ *
+ * @return FALSE (to stop then timer from repeating)
+ */
+static
+gboolean
+cpu_keepalive_timer_cb(gpointer data)
+{
+  (void)data;
+
+  if( timer_id != 0 )
+  {
+    mce_log(LL_NOTICE, "cpu-keepalive ended");
+    timer_id = 0;
+
+#ifdef ENABLE_WAKELOCKS
+    wakelock_unlock(cpu_wakelock);
+#endif
+  }
+
+  return FALSE;
+}
+
+/** Cancel end of cpu-keepalive timer
+ */
+static
+void
+cpu_keepalive_cancel_timer(void)
+{
+  if( timer_id != 0 )
+  {
+    g_source_remove(timer_id), timer_id = 0;
+  }
+}
+
+/** Reset cpu-keepalive timer
+ *
+ * @param when monotonic time of the end of cpu-keepalive period
+ */
+static
+void
+cpu_keepalive_set_timer(time_t when)
+{
+  cpu_keepalive_cancel_timer();
+
+  time_t now = cpu_keepalive_get_time();
+
+  if( when < now ) when = now;
+
+  mce_log(LL_NOTICE, "cpu-keepalive ends at T%+d", (int)(now - when));
+
+  if( now < when )
+  {
+    timer_id = g_timeout_add_seconds(when - now,
+                                    cpu_keepalive_timer_cb, 0);
+  }
+  else
+  {
+    timer_id = g_idle_add(cpu_keepalive_timer_cb, 0);
+  }
+}
+
+/** Re-evaluate the end of cpu-keepalive period
+ *
+ * Calculates maximum of wakeup period and per client renew periods
+ * and uses it to reprogram the end of cpu-keepalive period
+ */
+static
+void
+cpu_keepalive_rethink(void)
+{
+  time_t maxtime = wakeup_timeout;
+
+  GHashTableIter iter;
+  gpointer key, val;
+
+#ifdef ENABLE_WAKELOCKS
+  wakelock_lock(cpu_wakelock, -1);
+#endif
+
+  g_hash_table_iter_init(&iter, clients);
+  while( g_hash_table_iter_next (&iter, &key, &val) )
+  {
+    client_t *client = val;
+    if( maxtime < client->timeout )
+    {
+      maxtime = client->timeout;
+    }
+  }
+  cpu_keepalive_set_timer(maxtime);
+}
+
+/* ========================================================================= *
+ *
+ * CLIENT MANAGEMENT
+ *
+ * ========================================================================= */
+
+/** Remove bookkeeping data for a client and re-evaluate cpu keepalive status
+ *
+ * @param dbus_name dbus name of the client
+ */
+static
+void
+cpu_keepalive_remove_client(const gchar *dbus_name)
+{
+  if( g_hash_table_remove(clients, dbus_name) )
+  {
+    cpu_keepalive_rethink();
+  }
+}
+
+/** Obtain bookkeeping data for a client
+ *
+ * @param dbus_name dbus name of the client
+ *
+ * @return client data, or NULL if dbus_name is not tracked
+ */
+static
+client_t *
+cpu_keepalive_get_client(const gchar *dbus_name)
+{
+  client_t *client = g_hash_table_lookup(clients, dbus_name);
+  return client;
+}
+
+/** Call back for handling asynchronous client verification via GetNameOwner
+ *
+ * @param pending   control structure for asynchronous d-bus methdod call
+ * @param user_data dbus_name of the client as void poiter
+ */
+static
+void
+cpu_keepalive_verify_name_cb(DBusPendingCall *pending, void *user_data)
+{
+  const gchar *name   = user_data;
+  gchar       *owner  = 0;
+  DBusMessage *rsp    = 0;
+  client_t    *client = 0;
+
+  if( !(rsp = dbus_pending_call_steal_reply(pending)) )
+  {
+    goto EXIT;
+  }
+
+  if( !(client = cpu_keepalive_get_client(name)) )
+  {
+    mce_log(LL_WARN, "untracked client %s", name);
+  }
+
+  if( !(owner = cpu_keepalive_parse_GetNameOwner_rsp(rsp)) || !*owner )
+  {
+    mce_log(LL_WARN, "dead client %s", name);
+    cpu_keepalive_remove_client(name), client = 0;
+  }
+  else
+  {
+    mce_log(LL_WARN, "live client %s, owner %s", name, owner);
+  }
+
+EXIT:
+  g_free(owner);
+
+  if( rsp ) dbus_message_unref(rsp);
+}
+
+/** Verify that a client exists via an asynchronous GetNameOwner method call
+ *
+ * @param name the dbus name who's owner we wish to know
+ *
+ * @return TRUE if the method call was initiated, or FALSE in case of errors
+ */
+static
+gboolean
+cpu_keepalive_verify_name(const char *name)
+{
+  gboolean         res = FALSE;
+  DBusMessage     *req = 0;
+  DBusPendingCall *pc  = 0;
+  gchar           *key = 0;
+
+  if( !(req = cpu_keepalive_create_GetNameOwner_req(name)) )
+  {
+    goto EXIT;
+  }
+
+  if( !dbus_connection_send_with_reply(systembus, req, &pc, -1) )
+  {
+    goto EXIT;
+  }
+
+  key = g_strdup(name);
+
+  if( !dbus_pending_call_set_notify(pc, cpu_keepalive_verify_name_cb,
+                                   key, g_free) )
+  {
+    goto EXIT;
+  }
+
+  // pending call should not be cancelled
+  pc = 0;
+
+  // key string is owned by pending call
+  key = 0;
+
+  // success
+  res = TRUE;
+
+EXIT:
+
+  g_free(key);
+
+  if( pc  ) dbus_pending_call_cancel(pc);
+  if( req ) dbus_message_unref(req);
+
+  return res;
+}
+
+/** Find existing / create new client data by dbus name
+ *
+ * @param dbus_name dbus name of the client
+ *
+ * @return client data
+ */
+static
+client_t *
+cpu_keepalive_add_client(const gchar *dbus_name)
+{
+  client_t *client = g_hash_table_lookup(clients, dbus_name);
+
+  if( !client )
+  {
+    /* The client_create() adds NameOwnerChanged signal match
+     * so that we know when/if the client exits, crashes or
+     * otherwise loses dbus connection. */
+
+    client = client_create(dbus_name);
+    g_hash_table_insert(clients, g_strdup(dbus_name), client);
+
+    /* Then make an explicit GetNameOwner request to verify that
+     * the client is still running when we have the signal
+     * listening in the place. */
+    cpu_keepalive_verify_name(dbus_name);
+  }
+
+  return client;
+}
+
+/** Register client and start minor keep-alive period
+ *
+ * If needed will add the dbus_name to list of tracked clients.
+ *
+ * @param dbus_name name of the tracked client
+ */
+static
+void
+cpu_keepalive_register(const gchar *dbus_name)
+{
+  client_t *client = cpu_keepalive_add_client(dbus_name);
+
+  time_t when = cpu_keepalive_get_time() + 2;
+
+  client_update_timeout(client, when);
+
+  cpu_keepalive_rethink();
+}
+
+/** Adjust the cpu-keepalive timeout for dbus client
+ *
+ * If needed will add the dbus_name to list of tracked clients.
+ *
+ * @param dbus_name name of the tracked client
+ */
+static
+void
+cpu_keepalive_start(const gchar *dbus_name)
+{
+  client_t *client = cpu_keepalive_add_client(dbus_name);
+
+  time_t when = cpu_keepalive_get_time() + MCE_CPU_KEEPALIVE_PERIOD_SECONDS;
+
+  client_update_timeout(client, when);
+
+  /* We got at least one keep alive request, extend the minimum
+   * alive time a bit to give other clients time to get scheduled */
+  wakeup_timeout = wakeup_started + MCE_RTC_WAKEUP_2ND_TIMEOUT_SECONDS;
+
+  cpu_keepalive_rethink();
+}
+
+/** Remove the cpu-keepalive timeout for dbus client
+ *
+ * @param dbus_name name of the tracked client
+ */
+static
+void
+cpu_keepalive_stop(const gchar *dbus_name)
+{
+  client_t *client = cpu_keepalive_get_client(dbus_name);
+
+  if( client )
+  {
+    client_clear_timeout(client);
+    cpu_keepalive_rethink();
+  }
+  else
+  {
+    mce_log(LL_WARN, "untracked client %s", dbus_name);
+  }
+}
+
+/** Transfer resume-due-to-rtc-input wakelock from dsme to mce
+ *
+ * @param dbus_name name of the client issuing the request
+ */
+static
+void
+cpu_keepalive_wakeup(const gchar *dbus_name)
+{
+  // FIXME: we should check that the dbus_name == DSME
+  (void)dbus_name;
+
+  /* Time of wakeup received */
+  wakeup_started = cpu_keepalive_get_time();
+
+  /* Timeout for the 1st keepalive message to come through */
+  wakeup_timeout = wakeup_started + MCE_RTC_WAKEUP_1ST_TIMEOUT_SECONDS;
+
+  cpu_keepalive_rethink();
+
+  mce_log(LL_NOTICE, "rtc wakeup finished");
+#ifdef ENABLE_WAKELOCKS
+  wakelock_unlock(rtc_wakelock);
+#endif
+}
+
+/* ========================================================================= *
+ *
+ * D-BUS METHOD CALL HANDLERS
+ *
+ * ========================================================================= */
+
+/** D-Bus callback for the MCE_CPU_KEEPALIVE_PERIOD_REQ method call
+ *
+ * @param msg The D-Bus message
+ *
+ * @return TRUE on success, FALSE on failure
+ */
+static
+gboolean
+cpu_keepalive_period_cb(DBusMessage *const msg)
+{
+  mce_log(LL_INFO, "got method call");
+
+  gboolean success = FALSE;
+
+  cpu_keepalive_register(dbus_message_get_sender(msg));
+
+  success = cpu_keepalive_reply_int(msg, MCE_CPU_KEEPALIVE_PERIOD_SECONDS);
+
+  return success;
+}
+
+/** D-Bus callback for the MCE_CPU_KEEPALIVE_START_REQ method call
+ *
+ * @param msg The D-Bus message
+ *
+ * @return TRUE on success, FALSE on failure
+ */
+static
+gboolean
+cpu_keepalive_start_cb(DBusMessage *const msg)
+{
+  mce_log(LL_INFO, "got method call");
+
+  gboolean success = FALSE;
+
+  cpu_keepalive_start(dbus_message_get_sender(msg));
+
+  success = cpu_keepalive_reply_bool(msg, TRUE);
+
+  return success;
+}
+
+/** D-Bus callback for the MCE_CPU_KEEPALIVE_STOP_REQ method call
+ *
+ * @param msg The D-Bus message
+ *
+ * @return TRUE on success, FALSE on failure
+ */
+static
+gboolean
+cpu_keepalive_stop_cb(DBusMessage *const msg)
+{
+  mce_log(LL_INFO, "got method call");
+
+  gboolean success = FALSE;
+
+  cpu_keepalive_stop(dbus_message_get_sender(msg));
+
+  success = cpu_keepalive_reply_bool(msg, TRUE);
+
+  return success;
+}
+
+/** D-Bus callback for the MCE_CPU_KEEPALIVE_WAKEUP_REQ method call
+ *
+ * @param msg The D-Bus message
+ *
+ * @return TRUE on success, FALSE on failure
+ */
+static
+gboolean
+cpu_keepalive_wakeup_cb(DBusMessage *const msg)
+{
+  mce_log(LL_INFO, "got method call");
+
+  gboolean success = FALSE;
+
+  cpu_keepalive_wakeup(dbus_message_get_sender(msg));
+
+  success = cpu_keepalive_reply_bool(msg, TRUE);
+
+  return success;
+}
+
+/* ========================================================================= *
+ *
+ * D-BUS SIGNAL HANDLERS
+ *
+ * ========================================================================= */
+
+/** D-Bus message filter for handling NameOwnerChanged signals
+ *
+ * @param con       dbus connection
+ * @param msg       message to be acted upon
+ * @param user_data (not used)
+ *
+ * @return DBUS_HANDLER_RESULT_NOT_YET_HANDLED (other filters see the msg too)
+ */
+static
+DBusHandlerResult
+cpu_keepalive_dbus_filter_cb(DBusConnection *con, DBusMessage *msg,
+                            void *user_data)
+{
+  (void)user_data;
+
+  DBusHandlerResult res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  const char *sender = 0;
+  const char *object = 0;
+
+  const char *name = 0;
+  const char *prev = 0;
+  const char *curr = 0;
+
+  DBusError err = DBUS_ERROR_INIT;
+
+  if( con != systembus )
+  {
+    goto EXIT;
+  }
+
+  if( !dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, "NameOwnerChanged") )
+  {
+    goto EXIT;
+  }
+
+  sender = dbus_message_get_sender(msg);
+  if( strcmp(sender, DBUS_SERVICE_DBUS) )
+  {
+    goto EXIT;
+  }
+
+  object = dbus_message_get_path(msg);
+  if( strcmp(object, DBUS_PATH_DBUS) )
+  {
+    goto EXIT;
+  }
+
+  if( !dbus_message_get_args(msg, &err,
+                            DBUS_TYPE_STRING, &name,
+                            DBUS_TYPE_STRING, &prev,
+                            DBUS_TYPE_STRING, &curr,
+                            DBUS_TYPE_INVALID) )
+  {
+    mce_log(LL_WARN, "%s: %s", err.name, err.message);
+    goto EXIT;
+  }
+
+  if( !*curr )
+  {
+    mce_log(LL_INFO, "name lost owner: %s", name);
+    cpu_keepalive_remove_client(name);
+  }
+
+EXIT:
+  dbus_error_free(&err);
+  return res;
+}
+
+/* ========================================================================= *
+ *
+ * MODULE INIT/QUIT
+ *
+ * ========================================================================= */
+
+typedef gboolean (*handler_t)(DBusMessage *const msg);
+
+/** Book keeping for dbus method call handler status */
+static struct
+{
+  const char      *member;
+  const handler_t  handler;
+  gconstpointer    cookie;
+} methods[] =
+{
+  { MCE_CPU_KEEPALIVE_PERIOD_REQ, cpu_keepalive_period_cb, 0 },
+  { MCE_CPU_KEEPALIVE_START_REQ,  cpu_keepalive_start_cb,  0 },
+  { MCE_CPU_KEEPALIVE_STOP_REQ,   cpu_keepalive_stop_cb,   0 },
+  { MCE_CPU_KEEPALIVE_WAKEUP_REQ, cpu_keepalive_wakeup_cb, 0 },
+  { 0, 0, 0 }
+};
+
+/** Install signal and method call message handlers
+ *
+ * @return TRUE on success, or FALSE on failure
+ */
+static gboolean cpu_keepalive_attach_to_dbus(void)
+{
+  gboolean success = TRUE;
+
+  /* Register signal handling filter */
+  dbus_connection_add_filter(systembus, cpu_keepalive_dbus_filter_cb, 0, 0);
+
+  /* Register dbus method call handlers */
+  for( size_t i = 0; methods[i].member; ++i )
+  {
+    mce_log(LL_INFO, "registering handler for: %s", methods[i].member);
+
+    methods[i].cookie = mce_dbus_handler_add(MCE_REQUEST_IF,
+                                            methods[i].member,
+                                            NULL,
+                                            DBUS_MESSAGE_TYPE_METHOD_CALL,
+                                            methods[i].handler);
+    if( !methods[i].cookie )
+    {
+      mce_log(LL_WARN, "failed to add dbus handler for: %s",
+             methods[i].member);
+      success = FALSE;
+    }
+  }
+
+  return success;
+}
+
+/** Remove signal and method call message handlers
+ */
+static void cpu_keepalive_detach_from_dbus(void)
+{
+  /* Remove signal handling filter */
+  dbus_connection_remove_filter(systembus, cpu_keepalive_dbus_filter_cb, 0);
+
+  /* Remove dbus method call handlers that we have registered */
+  for( size_t i = 0; methods[i].member; ++i )
+  {
+    if( methods[i].cookie )
+    {
+      mce_log(LL_INFO, "removing handler for: %s", methods[i].member);
+      mce_dbus_handler_remove(methods[i].cookie);
+      methods[i].cookie = 0;
+    }
+  }
+}
+
+/** Init function for the cpu-keepalive module
+ *
+ * @param module (not used)
+ *
+ * @return NULL on success, a string with an error message on failure
+ */
+const gchar *g_module_check_init(GModule *module)
+{
+  (void)module;
+
+  const gchar *status = NULL;
+
+  if( !(systembus = dbus_connection_get()) )
+  {
+    status = "mce has no dbus connection";
+    goto EXIT;
+  }
+
+  if( !cpu_keepalive_attach_to_dbus() )
+  {
+    status = "attaching to dbus connection failed";
+    goto EXIT;
+  }
+
+  clients = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                 g_free, client_delete_cb);
+
+EXIT:
+
+  mce_log(LL_NOTICE, "loaded %s, status: %s", module_name, status ?: "ok");
+
+  return status;
+}
+
+/** Exit function for the cpu-keepalive module
+ *
+ * @param module (not used)
+ */
+void g_module_unload(GModule *module)
+{
+  (void)module;
+
+  /* If we have active clients, removal expects a valid dbus
+   * connection -> purge clients first */
+  if( clients )
+  {
+    g_hash_table_unref(clients), clients = 0;
+  }
+
+  if( systembus )
+  {
+    cpu_keepalive_detach_from_dbus();
+    dbus_connection_unref(systembus), systembus = 0;
+  }
+
+  mce_log(LL_NOTICE, "unloaded %s", module_name);
+
+  return;
+}
--- modules/display.c
+++ modules/display.c
@@ -120,6 +120,7 @@
 
 #ifdef ENABLE_WAKELOCKS
 # include "../libwakelock.h"           /* API for wakelocks */
+# include "../filewatcher.h"
 #endif
 
 /* These defines are taken from devicelock.h, but slightly modified */
@@ -2802,6 +2803,12 @@
 /** Still waiting for desktop ready ?*/
 static guint suspend_timer_id = 0;
 
+/** Content change watcher for the init-done flag file */
+static filewatcher_t *init_done_watcher = 0;
+
+/** Is the init-done flag file present in the file system */
+static gboolean init_done = FALSE;
+
 /** Automatic suspend policy modes */
 enum
 {
@@ -2860,9 +2867,9 @@
                break;
        }
 
-       /* do not suspend mid bootup */
-       if( suspend_timer_id ) {
-               suspend_want = 0;
+       /* no late suspend during bootup */
+       if( suspend_timer_id || !init_done ) {
+               wakelock_want = 1;
        }
 
        /* no late suspend during shutdown */
@@ -2969,12 +2976,40 @@
        return FALSE;
 }
 
+/** Content of init-done flag file has changed
+ *
+ * @param path directory where flag file is
+ * @param file name of the flag file
+ * @param data (not used)
+ */
+static void init_done_changed_cb(const char *path,
+                                const char *file,
+                                gpointer data)
+{
+       (void)data;
+
+       char full[256];
+       snprintf(full, sizeof full, "%s/%s", path, file);
+
+       gboolean flag = access(full, F_OK) ? FALSE : TRUE;
+
+       if( init_done != flag ) {
+               init_done = flag;
+               mce_log(LL_NOTICE, "init_done -> %s",
+                       init_done ? "true" : "false");
+               suspend_rethink();
+       }
+}
+
+
 /** Cleanup suspend policy
  */
 static void suspend_quit(void)
 {
        suspend_unload = TRUE;
 
+       filewatcher_delete(init_done_watcher), init_done_watcher = 0;
+
        if( suspend_timer_id ) {
                g_source_remove(suspend_timer_id);
                suspend_timer_id = 0;
@@ -2987,29 +3022,37 @@
  */
 static void suspend_init(void)
 {
-       /* FIXME: While we do not have a proper desktop-is-ready
-        *        signal/state we are guestimating that we can
-        *        allow suspend after uptime is large enough */
-
        time_t uptime = 0;  // uptime in seconds
        time_t ready  = 60; // desktop ready at
        time_t delay  = 10; // default wait time
 
-       struct timespec ts;
+       /* wait for flag file to appear */
+       init_done_watcher = filewatcher_create("/run/systemd/boot-status",
+                                              "init-done",
+                                              init_done_changed_cb, 0, 0);
+
+       /* or fall back to waiting for uptime to reach some minimum value */
+       if( !init_done_watcher ) {
+               struct timespec ts;
+
+               /* Assume that monotonic clock == uptime */
+               if( clock_gettime(CLOCK_MONOTONIC, &ts) == 0 )
+                       uptime = ts.tv_sec;
 
-       /* Assume that monotonic clock == uptime */
-       if( clock_gettime(CLOCK_MONOTONIC, &ts) == 0 )
-               uptime = ts.tv_sec;
+               if( uptime + delay < ready )
+                       delay = ready - uptime;
 
-       if( uptime + delay < ready )
-               delay = ready - uptime;
+               /* do not wait for the init-done flag file */
+               init_done = TRUE;
+       }
 
        mce_log(LL_NOTICE, "suspend delay %d seconds", (int)delay);
-
-       if( suspend_timer_id )
-               g_source_remove(suspend_timer_id);
-
        suspend_timer_id = g_timeout_add_seconds(delay, suspend_timer_cb, 0);
+
+       if( init_done_watcher ) {
+               /* evaluate the initial state of init-done flag file */
+               filewatcher_force_trigger(init_done_watcher);
+       }
 }
 #endif /* ENABLE_WAKELOCKS */
 
--- tklock.c
+++ tklock.c
@@ -171,6 +171,15 @@
   .close_on_exit = TRUE,
 };
 
+/** Disable automatic dim/blank from tklock */
+static gint tklock_blank_disable = FALSE;
+
+/** GConf notifier id for tracking tklock_blank_disable changes */
+static guint tklock_blank_disable_id = 0;
+
+/** Pseudo-forever dim/blank delay to use when tklock_blank_disable is set */
+#define DISABLED_VISUAL_BLANK_DELAY    (24*60*60) // 24h
+
 /** Touchscreen double tap gesture policy */
 static gint doubletap_gesture_policy = DEFAULT_DOUBLETAP_GESTURE_POLICY;
 
@@ -1308,6 +1317,12 @@
 {
        (void)data;
 
+       /* Keep the timeout alive while tklock_blank_disable is set */
+       if( tklock_blank_disable ) {
+               mce_log(LL_INFO, "renew fake visual_blank_timeout");
+               return TRUE;
+       }
+
        cancel_tklock_visual_blank_timeout();
 
        if (saved_tklock_state == MCE_TKLOCK_VISUAL_STATE)
@@ -1330,6 +1345,8 @@
                                datapipe_get_gint(alarm_ui_state_pipe);
        call_state_t call_state = datapipe_get_gint(call_state_pipe);
 
+       gint delay = DEFAULT_VISUAL_BLANK_DELAY;
+
        cancel_tklock_dim_timeout();
        cancel_tklock_visual_blank_timeout();
 
@@ -1340,9 +1357,14 @@
            (alarm_ui_state == MCE_ALARM_UI_RINGING_INT32))
                goto EXIT;
 
+       if( tklock_blank_disable ) {
+               mce_log(LL_INFO, "set up fake visual_blank_timeout");
+               delay = DISABLED_VISUAL_BLANK_DELAY;
+       }
+
        /* Setup blank timeout */
        tklock_visual_blank_timeout_cb_id =
-               g_timeout_add_seconds(DEFAULT_VISUAL_BLANK_DELAY, 
tklock_visual_blank_timeout_cb, NULL);
+               g_timeout_add_seconds(delay, tklock_visual_blank_timeout_cb, 
NULL);
 
 EXIT:
        return;
@@ -1358,6 +1380,12 @@
 {
        (void)data;
 
+       /* Keep the timeout alive while tklock_blank_disable is set */
+       if( tklock_blank_disable ) {
+               mce_log(LL_INFO, "renew fake visual_dim_timeout");
+               return TRUE;
+       }
+
        tklock_dim_timeout_cb_id = 0;
 
        if (blank_immediately == TRUE) {
@@ -1392,11 +1420,18 @@
  */
 static void setup_tklock_dim_timeout(void)
 {
+       gint delay = dim_delay;
+
        cancel_tklock_dim_timeout();
 
+       if( tklock_blank_disable ) {
+               mce_log(LL_INFO, "set up fake visual_dim_timeout");
+               delay = DISABLED_VISUAL_BLANK_DELAY;
+       }
+
        /* Setup new timeout */
        tklock_dim_timeout_cb_id =
-               g_timeout_add_seconds(dim_delay, tklock_dim_timeout_cb, NULL);
+               g_timeout_add_seconds(delay, tklock_dim_timeout_cb, NULL);
 }
 
 /**
@@ -2069,6 +2104,23 @@
                        doubletap_gesture_policy =
                                DEFAULT_DOUBLETAP_GESTURE_POLICY;
                }
+       } else if(id == tklock_blank_disable_id) {
+               gint old = tklock_blank_disable;
+
+               tklock_blank_disable = gconf_value_get_int(gcv);
+
+               mce_log(LL_NOTICE, "tklock_blank_disable: %d -> %d",
+                       old, tklock_blank_disable);
+
+               if( tklock_blank_disable == old ) {
+                       // no need to change the timers
+               }
+               else if( tklock_visual_blank_timeout_cb_id ) {
+                       setup_tklock_visual_blank_timeout();
+               }
+               else if( tklock_dim_timeout_cb_id ) {
+                       setup_tklock_dim_timeout();
+               }
        } else {
                mce_log(LL_WARN, "Spurious GConf value received; confused!");
        }
@@ -3203,6 +3255,17 @@
        append_output_trigger_to_datapipe(&heartbeat_pipe,
                                          heartbeat_trigger);
 
+       /* Config tracking for disabling automatic screen dimming/blanking
+        * while showing lockscreen. This is demo/debugging feature, so sane
+        * defaults must be used and no error checking is needed. */
+       mce_gconf_notifier_add(MCE_GCONF_LOCK_PATH,
+                              MCE_GCONF_TK_AUTO_BLANK_DISABLE_PATH,
+                              tklock_gconf_cb,
+                              &tklock_blank_disable_id);
+
+       mce_gconf_get_int(MCE_GCONF_TK_AUTO_BLANK_DISABLE_PATH,
+                         &tklock_blank_disable);
+
        /* Touchscreen/keypad autolock */
        /* Since we've set a default, error handling is unnecessary */
        /*(void)mce_gconf_get_bool(MCE_GCONF_TK_AUTOLOCK_ENABLED_PATH,
@@ -3341,6 +3404,11 @@
  */
 void mce_tklock_exit(void)
 {
+       /* Remove gconf change notifiers */
+       if( tklock_blank_disable_id ) {
+               
mce_gconf_notifier_remove(GINT_TO_POINTER(tklock_blank_disable_id), 0);
+       }
+
        /* Remove triggers/filters from datapipes */
        remove_output_trigger_from_datapipe(&heartbeat_pipe,
                                            heartbeat_trigger);
--- tklock.h
+++ tklock.h
@@ -85,6 +85,9 @@
 /** Path to the touchscreen/keypad double tap gesture GConf setting */
 #define MCE_GCONF_TK_DOUBLE_TAP_GESTURE_PATH   MCE_GCONF_LOCK_PATH 
"/tklock_double_tap_gesture"
 
+/** Path to the automatic tklock dim/blank disable GConf setting */
+#define MCE_GCONF_TK_AUTO_BLANK_DISABLE_PATH   MCE_GCONF_LOCK_PATH 
"/tklock_blank_disable"
+
 /** Name of D-Bus callback to provide to Touchscreen/Keypad Lock SystemUI */
 #define MCE_TKLOCK_CB_REQ              "tklock_callback"
 /** Delay before the touchscreen/keypad is unlocked */
--- tools/mcetool.c
+++ tools/mcetool.c
@@ -225,6 +225,10 @@
                "                                  set the threshold for the 
power saving mode;\n"
                "                                    valid values are:\n"
                "                                    10, 20, 30, 40, 50\n"
+               "  -t, --set-tklock-noblank=MODE\n"
+               "                                  set the touchscreen/keypad 
autoblank mode;\n"
+               "                                    valid modes are: 
\"enabled\" and \"disabled\"\n"
+
                "  -k, --set-tklock-mode=MODE\n"
                "                                  set the touchscreen/keypad 
lock mode;\n"
                "                                    valid modes are:\n"
@@ -1985,6 +1989,16 @@
        { NULL, -1 }
 };
 
+/** Lookup table for tklock autoblank policy values
+ *
+ * @note These must match the hardcoded values in mce itself.
+ */
+static const symbol_t tklockblank_values[] = {
+       { "disabled",  1 },
+       { "enabled",   0 },
+       { NULL, -1 }
+};
+
 /**
  * Print mce related information
  *
@@ -2416,9 +2430,18 @@
                gint policy;
                retval = mcetool_gconf_get_int(MCE_GCONF_USE_AUTOSUSPEND_PATH,
                                               &policy);
-               fprintf(stdout, " %-40s %s", "Autosuspend policy",
+               fprintf(stdout, " %-40s %s\n", "Autosuspend policy",
                        !retval ? "<unset>" : rlookup(suspendpol_values, 
policy) ?: "<unknown>");
        }
+
+       /* Get tklock blank policy */
+       {
+               gint policy;
+               retval = 
mcetool_gconf_get_int(MCE_GCONF_TK_AUTO_BLANK_DISABLE_PATH,
+                                              &policy);
+               fprintf(stdout, " %-40s %s\n", "tklock autoblank policy",
+                       !retval ? "<unset>" : rlookup(tklockblank_values, 
policy) ?: "<unknown>");
+       }
 EXIT:
        fprintf(stdout, "\n");
 
@@ -2489,6 +2512,7 @@
 
        gint powerkeyevent = INVALID_EVENT;
        gint newinhibitmode = -1;
+       gint newtklockblank = -1;
 #if MCETOOL_USE_DEMOMODE_HACK
        gint demomode = -1;
 #endif
@@ -2533,7 +2557,7 @@
        DBusBusType bus_type = DBUS_BUS_SYSTEM;
 
        // Unused short options left ....
-       // - - - - - - - - i j - - m - o - q - s t u - w x - z
+       // - - - - - - - - i j - - m - o - q - - - u - w x - z
        // - - - - - - - - - - - - - - - - Q - - - - - W X - Z
 
        const char optline[] =
@@ -2575,6 +2599,7 @@
                "M:" // --set-doubletap-mode
                "O:" // --set-dim-timeouts
                "s:" // --set-suspend-policy
+               "t:" // --set-tklock-noblank
                ;
 
        struct option const options[] = {
@@ -2597,6 +2622,7 @@
                 { "set-forced-psm",         required_argument, 0, 'F' },
                 { "set-psm-threshold",      required_argument, 0, 'T' },
                 { "set-tklock-mode",        required_argument, 0, 'k' },
+                { "set-tklock-noblank",     required_argument, 0, 't' },
                 { "enable-led",             no_argument,       0, 'l' },
                 { "disable-led",            no_argument,       0, 'L' },
                 { "activate-led-pattern",   required_argument, 0, 'y' },
@@ -2910,6 +2936,15 @@
                        get_mce_status = FALSE;
                        break;
 
+               case 't':
+                       if( (newtklockblank = lookup(tklockblank_values, 
optarg)) < 0 ) {
+                               fprintf(stderr, "invalid lockscreen blanking 
policy: %s\n",
+                                       optarg);
+                               goto EXIT;
+                       }
+                       get_mce_status = FALSE;
+                       break;
+
                case 'I':
                        if (!strcmp(optarg, BLANKING_INHIBIT_DISABLED)) {
                                newinhibitmode = 0;
@@ -3167,6 +3202,13 @@
                        goto EXIT;
        }
 
+       if (newtklockblank != -1) {
+               if (mcetool_gconf_set_int(MCE_GCONF_TK_AUTO_BLANK_DISABLE_PATH,
+                                         newtklockblank) == FALSE)
+                       goto EXIT;
+       }
+
+
 #if MCETOOL_USE_DEMOMODE_HACK
        if (demomode != -1) {
                if (dbus_send(MCE_SERVICE, MCE_REQUEST_PATH,



Reply via email to