mirror of
https://gitlab.com/mishakmak/pam-fprint-grosshack.git
synced 2026-04-09 04:13:33 +02:00
Implement suspend/resume handling
This commit is contained in:
75
src/device.c
75
src/device.c
@ -475,6 +475,81 @@ _fprint_device_get_id (FprintDevice *rdev)
|
||||
return priv->id;
|
||||
}
|
||||
|
||||
static void
|
||||
suspend_cb (GObject *source_obj,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
fp_device_suspend_finish (FP_DEVICE (source_obj), res, &error);
|
||||
if (error)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
resume_cb (GObject *source_obj,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
fp_device_resume_finish (FP_DEVICE (source_obj), res, &error);
|
||||
if (error)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
}
|
||||
|
||||
void
|
||||
fprint_device_suspend (FprintDevice *rdev,
|
||||
GAsyncReadyCallback callback,
|
||||
void *user_data)
|
||||
{
|
||||
GTask *task = NULL;
|
||||
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
|
||||
|
||||
/* Just forward to libfprint. */
|
||||
|
||||
task = g_task_new (rdev, NULL, callback, user_data);
|
||||
fp_device_suspend (priv->dev, NULL, suspend_cb, task);
|
||||
}
|
||||
|
||||
void
|
||||
fprint_device_resume (FprintDevice *rdev,
|
||||
GAsyncReadyCallback callback,
|
||||
void *user_data)
|
||||
{
|
||||
GTask *task = NULL;
|
||||
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
|
||||
|
||||
/* Just forward to libfprint. */
|
||||
|
||||
task = g_task_new (rdev, NULL, callback, user_data);
|
||||
fp_device_resume (priv->dev, NULL, resume_cb, task);
|
||||
}
|
||||
|
||||
void
|
||||
fprint_device_suspend_finish (FprintDevice *rdev,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
g_task_propagate_boolean (G_TASK (res), error);
|
||||
}
|
||||
|
||||
void
|
||||
fprint_device_resume_finish (FprintDevice *rdev,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
g_task_propagate_boolean (G_TASK (res), error);
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
fp_finger_to_name (FpFinger finger)
|
||||
{
|
||||
|
||||
@ -95,6 +95,22 @@ struct _FprintDevice
|
||||
|
||||
FprintDevice *fprint_device_new (FpDevice *dev);
|
||||
guint32 _fprint_device_get_id (FprintDevice *rdev);
|
||||
|
||||
void fprint_device_suspend (FprintDevice *rdev,
|
||||
GAsyncReadyCallback callback,
|
||||
void *user_data);
|
||||
void fprint_device_resume (FprintDevice *rdev,
|
||||
GAsyncReadyCallback callback,
|
||||
void *user_data);
|
||||
|
||||
void fprint_device_suspend_finish (FprintDevice *rdev,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
void fprint_device_resume_finish (FprintDevice *rdev,
|
||||
GAsyncResult *res,
|
||||
GError **error);
|
||||
|
||||
|
||||
/* Print */
|
||||
/* TODO */
|
||||
|
||||
|
||||
174
src/manager.c
174
src/manager.c
@ -24,9 +24,14 @@
|
||||
#include <glib/gi18n.h>
|
||||
#include <fprint.h>
|
||||
#include <glib-object.h>
|
||||
#include <gio/gunixfdlist.h>
|
||||
|
||||
#include "fprintd.h"
|
||||
|
||||
#define LOGIND_BUS_NAME "org.freedesktop.login1"
|
||||
#define LOGIND_IFACE_NAME "org.freedesktop.login1.Manager"
|
||||
#define LOGIND_OBJ_PATH "/org/freedesktop/login1"
|
||||
|
||||
static void fprint_manager_constructed (GObject *object);
|
||||
static gboolean fprint_manager_get_devices (FprintManager *manager,
|
||||
GPtrArray **devices,
|
||||
@ -43,6 +48,9 @@ typedef struct
|
||||
FpContext *context;
|
||||
gboolean no_timeout;
|
||||
guint timeout_id;
|
||||
gint prepare_for_sleep_pending;
|
||||
guint prepare_for_sleep_id;
|
||||
gint sleep_inhibit_fd;
|
||||
} FprintManagerPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (FprintManager, fprint_manager, G_TYPE_OBJECT, G_ADD_PRIVATE (FprintManager))
|
||||
@ -60,6 +68,10 @@ fprint_manager_finalize (GObject *object)
|
||||
{
|
||||
FprintManagerPrivate *priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object));
|
||||
|
||||
if (priv->prepare_for_sleep_id)
|
||||
g_dbus_connection_signal_unsubscribe (priv->connection,
|
||||
priv->prepare_for_sleep_id);
|
||||
|
||||
g_clear_object (&priv->object_manager);
|
||||
g_clear_object (&priv->dbus_manager);
|
||||
g_clear_object (&priv->connection);
|
||||
@ -227,6 +239,153 @@ handle_get_default_device (FprintManager *manager,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fprint_device_suspend_cb (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
FprintManager *manager = FPRINT_MANAGER (user_data);
|
||||
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
|
||||
|
||||
/* Fetch the result (except for the NULL dummy call). */
|
||||
if (source_object != NULL)
|
||||
{
|
||||
fprint_device_suspend_finish (FPRINT_DEVICE (source_object), res, &error);
|
||||
if (error)
|
||||
{
|
||||
if (!g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_OPEN) &&
|
||||
!g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED))
|
||||
g_message ("Unexpected error while suspending device: %s", error->message);
|
||||
}
|
||||
}
|
||||
|
||||
priv->prepare_for_sleep_pending -= 1;
|
||||
|
||||
/* Close FD when all devices are prepared for sleeping. */
|
||||
if (priv->prepare_for_sleep_pending == 0)
|
||||
{
|
||||
if (priv->sleep_inhibit_fd >= 0)
|
||||
close (priv->sleep_inhibit_fd);
|
||||
priv->sleep_inhibit_fd = -1;
|
||||
g_debug ("Released delay inhibitor for sleep.");
|
||||
}
|
||||
|
||||
g_object_unref (manager);
|
||||
}
|
||||
|
||||
static void
|
||||
logind_sleep_inhibit_cb (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GVariant) data = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(GUnixFDList) out_fd_list = NULL;
|
||||
g_autoptr(FprintManager) manager = FPRINT_MANAGER (user_data);
|
||||
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
|
||||
gint fd_offset;
|
||||
|
||||
data = g_dbus_connection_call_with_unix_fd_list_finish (priv->connection, &out_fd_list, res, &error);
|
||||
|
||||
if (!data)
|
||||
{
|
||||
g_warning ("Failed to install a sleep delay inhibitor: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (priv->sleep_inhibit_fd >= 0)
|
||||
close (priv->sleep_inhibit_fd);
|
||||
|
||||
g_debug ("Got delay inhibitor for sleep.");
|
||||
|
||||
g_variant_get (data, "(h)", &fd_offset);
|
||||
priv->sleep_inhibit_fd = g_unix_fd_list_get (out_fd_list, fd_offset, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_prepare_for_sleep_signal (GDBusConnection *connection,
|
||||
const gchar *sender_name,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *signal_name,
|
||||
GVariant *parameters,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autolist (GDBusObject) devices = NULL;
|
||||
FprintManager *manager = FPRINT_MANAGER (user_data);
|
||||
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
|
||||
gboolean prepare_for_sleep;
|
||||
GList *l;
|
||||
|
||||
if (!g_variant_check_format_string (parameters, "(b)", FALSE))
|
||||
{
|
||||
g_warning ("Received incorrect parameter for PrepareForSleep signal");
|
||||
return;
|
||||
}
|
||||
|
||||
g_variant_get (parameters, "(b)", &prepare_for_sleep);
|
||||
|
||||
/* called one more time to handle the case of no devices */
|
||||
if (prepare_for_sleep)
|
||||
priv->prepare_for_sleep_pending = 1;
|
||||
|
||||
devices = g_dbus_object_manager_get_objects (priv->object_manager);
|
||||
|
||||
g_debug ("Preparing devices for %s", prepare_for_sleep ? "sleep" : "resume");
|
||||
|
||||
for (l = devices; l != NULL; l = l->next)
|
||||
{
|
||||
g_autoptr(FprintDevice) dev = NULL;
|
||||
FprintDBusObjectSkeleton *object = l->data;
|
||||
|
||||
dev = fprint_dbus_object_skeleton_get_device (object);
|
||||
|
||||
if (prepare_for_sleep)
|
||||
{
|
||||
priv->prepare_for_sleep_pending += 1;
|
||||
g_object_ref (manager);
|
||||
fprint_device_suspend (dev, fprint_device_suspend_cb, manager);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprint_device_resume (dev, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (prepare_for_sleep)
|
||||
{
|
||||
/* "Notify" the initial dummy device we added, handling no devices that suspending */
|
||||
g_object_ref (manager);
|
||||
fprint_device_suspend_cb (NULL, NULL, manager);
|
||||
}
|
||||
else
|
||||
{
|
||||
GVariant *arg = NULL;
|
||||
|
||||
arg = g_variant_new ("(ssss)",
|
||||
"sleep",
|
||||
"net.reactivated.Fprint",
|
||||
"Suspend fingerprint readers",
|
||||
"delay");
|
||||
|
||||
/* Grab a sleep inhibitor. */
|
||||
g_dbus_connection_call_with_unix_fd_list (priv->connection,
|
||||
LOGIND_BUS_NAME,
|
||||
LOGIND_OBJ_PATH,
|
||||
LOGIND_IFACE_NAME,
|
||||
"Inhibit",
|
||||
arg,
|
||||
G_VARIANT_TYPE ("(h)"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
NULL,
|
||||
logind_sleep_inhibit_cb,
|
||||
g_object_ref (manager));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
device_added_cb (FprintManager *manager, FpDevice *device, FpContext *context)
|
||||
{
|
||||
@ -290,6 +449,7 @@ device_removed_cb (FprintManager *manager, FpDevice *device, FpContext *context)
|
||||
static void
|
||||
fprint_manager_constructed (GObject *object)
|
||||
{
|
||||
g_autoptr(GVariant) param_false = NULL;
|
||||
FprintManager *manager = FPRINT_MANAGER (object);
|
||||
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
|
||||
GDBusObjectManagerServer *object_manager_server;
|
||||
@ -319,6 +479,20 @@ fprint_manager_constructed (GObject *object)
|
||||
g_dbus_object_manager_server_set_connection (object_manager_server,
|
||||
priv->connection);
|
||||
|
||||
priv->prepare_for_sleep_id = g_dbus_connection_signal_subscribe (priv->connection,
|
||||
LOGIND_BUS_NAME,
|
||||
LOGIND_IFACE_NAME,
|
||||
"PrepareForSleep",
|
||||
LOGIND_OBJ_PATH,
|
||||
NULL,
|
||||
G_DBUS_SIGNAL_FLAGS_NONE,
|
||||
handle_prepare_for_sleep_signal,
|
||||
manager,
|
||||
NULL);
|
||||
/* Fake a resume as that triggers the inhibitor to be taken. */
|
||||
param_false = g_variant_new ("(b)", FALSE);
|
||||
handle_prepare_for_sleep_signal (priv->connection, NULL, NULL, NULL, NULL, param_false, manager);
|
||||
|
||||
/* And register the signals for initial enumeration and hotplug. */
|
||||
g_signal_connect_object (priv->context,
|
||||
"device-added",
|
||||
|
||||
108
tests/fprintd.py
108
tests/fprintd.py
@ -247,10 +247,15 @@ class FPrintdTest(dbusmock.DBusTestCase):
|
||||
argv.insert(2, '--suppressions=%s' % valgrind)
|
||||
self.valgrind = True
|
||||
self.kill_daemon = False
|
||||
self.daemon_log = OutputChecker()
|
||||
self.daemon = subprocess.Popen(argv,
|
||||
env=env,
|
||||
stdout=None,
|
||||
stdout=self.daemon_log.fd,
|
||||
stderr=subprocess.STDOUT)
|
||||
self.daemon_log.writer_attached()
|
||||
|
||||
#subprocess.Popen(['/usr/bin/dbus-monitor', '--system'])
|
||||
|
||||
self.addCleanup(self.daemon_stop)
|
||||
|
||||
timeout_count = timeout * 10
|
||||
@ -310,6 +315,8 @@ class FPrintdTest(dbusmock.DBusTestCase):
|
||||
else:
|
||||
raise(e)
|
||||
|
||||
self.daemon_log.assert_closed()
|
||||
|
||||
if not self.kill_daemon:
|
||||
self.assertLess(self.daemon.returncode, 128)
|
||||
self.assertGreaterEqual(self.daemon.returncode, 0)
|
||||
@ -591,8 +598,22 @@ class FPrintdVirtualDeviceBaseTest(FPrintdVirtualImageDeviceBaseTests):
|
||||
self.manager = None
|
||||
self.device = None
|
||||
self.polkitd_start()
|
||||
|
||||
fifo_path = os.path.join(self.tmpdir, 'logind_inhibit_fifo')
|
||||
os.mkfifo(fifo_path)
|
||||
self.logind_inhibit_fifo = os.open(fifo_path, os.O_RDONLY | os.O_NONBLOCK | os.O_CLOEXEC)
|
||||
# EOF without a writer, BlockingIOError with a writer
|
||||
self.assertFalse(self.holds_inhibitor())
|
||||
|
||||
self.logind, self.logind_obj = self.spawn_server_template('logind', { })
|
||||
self.logind_obj.AddMethod('org.freedesktop.login1.Manager', 'Inhibit', 'ssss', 'h',
|
||||
'ret = os.open("%s", os.O_WRONLY)\n' % fifo_path +
|
||||
'from gi.repository import GLib\n' +
|
||||
'GLib.idle_add(lambda fd: os.close(fd), ret)')
|
||||
self.daemon_start(self.driver_name)
|
||||
|
||||
self.wait_got_delay_inhibitor()
|
||||
|
||||
if self.device is None:
|
||||
self.skipTest("Need {} device to run the test".format(self.device_driver))
|
||||
|
||||
@ -641,6 +662,8 @@ class FPrintdVirtualDeviceBaseTest(FPrintdVirtualImageDeviceBaseTests):
|
||||
self.device = None
|
||||
self.manager = None
|
||||
|
||||
os.close(self.logind_inhibit_fifo)
|
||||
|
||||
super().tearDown()
|
||||
|
||||
def try_release(self):
|
||||
@ -672,6 +695,23 @@ class FPrintdVirtualDeviceBaseTest(FPrintdVirtualImageDeviceBaseTests):
|
||||
if expected is not None:
|
||||
self.assertEqual(self._last_result, expected)
|
||||
|
||||
def holds_inhibitor(self):
|
||||
try:
|
||||
if os.read(self.logind_inhibit_fifo, 1) == b'':
|
||||
return False
|
||||
except BlockingIOError:
|
||||
return True
|
||||
|
||||
raise AssertionError("logind inhibitor fifo in unexpected state")
|
||||
|
||||
def wait_got_delay_inhibitor(self, timeout=0):
|
||||
self.daemon_log.check_line('Got delay inhibitor for sleep', timeout=timeout)
|
||||
self.assertTrue(self.holds_inhibitor())
|
||||
|
||||
def wait_released_delay_inhibitor(self, timeout=0):
|
||||
self.daemon_log.check_line('Released delay inhibitor for sleep', timeout=timeout)
|
||||
self.assertFalse(self.holds_inhibitor())
|
||||
|
||||
def enroll_image(self, img, device=None, finger='right-index-finger',
|
||||
expected_result='enroll-completed', claim_user=None,
|
||||
start=True, stop=True):
|
||||
@ -1538,6 +1578,72 @@ class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest):
|
||||
|
||||
self.assertIn(GLib.Variant('()', ()), self.get_all_async_replies())
|
||||
|
||||
def test_suspend_inhibit_unclaimed(self):
|
||||
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [True])
|
||||
|
||||
self.daemon_log.check_line('Preparing devices for sleep', timeout=1)
|
||||
self.wait_released_delay_inhibitor(timeout=1)
|
||||
|
||||
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [False])
|
||||
|
||||
self.daemon_log.check_line('Preparing devices for resume', timeout=1)
|
||||
self.wait_got_delay_inhibitor(timeout=1)
|
||||
|
||||
def test_suspend_inhibit_claimed(self):
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
|
||||
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [True])
|
||||
|
||||
self.daemon_log.check_line('Preparing devices for sleep', timeout=1)
|
||||
self.wait_released_delay_inhibitor(timeout=1)
|
||||
|
||||
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [False])
|
||||
|
||||
self.daemon_log.check_line('Preparing devices for resume', timeout=1)
|
||||
self.wait_got_delay_inhibitor(timeout=1)
|
||||
|
||||
self.device.Release()
|
||||
|
||||
def test_suspend_inhibit_cancels_enroll(self):
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
|
||||
self.device.EnrollStart('(s)', 'right-thumb')
|
||||
|
||||
# Now prepare for sleep, which will trigger an internal cancellation
|
||||
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [True])
|
||||
|
||||
self.daemon_log.check_line('Preparing devices for sleep', timeout=1)
|
||||
self.wait_for_result(expected='enroll-unknown-error')
|
||||
self.wait_released_delay_inhibitor(timeout=1)
|
||||
|
||||
self.assertEqual(os.read(self.logind_inhibit_fifo, 1), b'')
|
||||
|
||||
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [False])
|
||||
|
||||
self.daemon_log.check_line('Preparing devices for resume', timeout=1)
|
||||
self.wait_got_delay_inhibitor(timeout=1)
|
||||
|
||||
self.device.Release()
|
||||
|
||||
def test_suspend_prevents_enroll(self):
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
|
||||
# Now prepare for sleep, which will trigger an internal cancellation
|
||||
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [True])
|
||||
|
||||
self.daemon_log.check_line('Preparing devices for sleep', timeout=1)
|
||||
self.wait_released_delay_inhibitor(timeout=1)
|
||||
|
||||
self.device.EnrollStart('(s)', 'right-thumb')
|
||||
self.wait_for_result(expected='enroll-unknown-error')
|
||||
|
||||
self.logind_obj.EmitSignal("", "PrepareForSleep", "b", [False])
|
||||
|
||||
self.daemon_log.check_line('Preparing devices for resume', timeout=1)
|
||||
self.wait_got_delay_inhibitor(timeout=1)
|
||||
|
||||
self.device.Release()
|
||||
|
||||
|
||||
class FPrintdVirtualDeviceStorageTest(FPrintdVirtualStorageDeviceBaseTest,
|
||||
FPrintdVirtualDeviceTest):
|
||||
|
||||
Reference in New Issue
Block a user