libobs: Make portal inhibitor asynchronous

Currently, libobs contains two inhibitors: the portal-based one, and the
regular one based on each desktop environment's session manager. The portal
inhibitor takes precedence over the session manager one, when available.

Like the session manager inhibitor, the portal inhibitor performs all D-Bus
calls synchronously in the calling thread, which can lead to stalls. This is
not a good practice.

Make the portal inhibitor asynchronous, by using g_dbus_connection_call()
instead of g_dbus_connection_call_sync(). If an uninhibit call is made
before the previous inhibit call finishes, cancel the inhibit call instead.

Fixes obsproject/obs-studio#5314
This commit is contained in:
Georges Basile Stavracas Neto 2021-09-21 13:52:10 -03:00 committed by Jim
parent 10810d9730
commit 62a2513369

View File

@ -24,6 +24,7 @@
struct portal_inhibit_info {
GDBusConnection *c;
GCancellable *cancellable;
unsigned int signal_id;
char *sender_name;
char *request_path;
@ -91,10 +92,30 @@ static void response_received(GDBusConnection *bus, const char *sender_name,
unsubscribe_from_request(info);
}
static void do_inhibit(struct portal_inhibit_info *info, const char *reason)
static void inhibited_cb(GObject *source_object, GAsyncResult *result,
gpointer user_data)
{
UNUSED_PARAMETER(source_object);
struct portal_inhibit_info *info = user_data;
g_autoptr(GVariant) reply = NULL;
g_autoptr(GError) error = NULL;
reply = g_dbus_connection_call_finish(info->c, result, &error);
if (error != NULL) {
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
blog(LOG_ERROR, "Failed to inhibit: %s",
error->message);
unsubscribe_from_request(info);
remove_inhibit_data(info);
}
g_clear_object(&info->cancellable);
}
static void do_inhibit(struct portal_inhibit_info *info, const char *reason)
{
GVariantBuilder options;
uint32_t flags = 0xC;
char *token;
@ -117,41 +138,58 @@ static void do_inhibit(struct portal_inhibit_info *info, const char *reason)
bfree(token);
reply = g_dbus_connection_call_sync(
info->c, PORTAL_NAME, "/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Inhibit", "Inhibit",
g_variant_new("(sua{sv})", "", flags, &options), NULL,
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
if (error != NULL) {
blog(LOG_ERROR, "Failed to inhibit: %s", error->message);
unsubscribe_from_request(info);
remove_inhibit_data(info);
return;
}
info->cancellable = g_cancellable_new();
g_dbus_connection_call(info->c, PORTAL_NAME,
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Inhibit", "Inhibit",
g_variant_new("(sua{sv})", "", flags, &options),
NULL, G_DBUS_CALL_FLAGS_NONE, -1,
info->cancellable, inhibited_cb, info);
}
static void do_uninhibit(struct portal_inhibit_info *info)
static void uninhibited_cb(GObject *source_object, GAsyncResult *result,
gpointer user_data)
{
UNUSED_PARAMETER(source_object);
struct portal_inhibit_info *info = user_data;
g_autoptr(GVariant) reply = NULL;
g_autoptr(GError) error = NULL;
g_dbus_connection_call_sync(info->c, PORTAL_NAME, info->request_path,
"org.freedesktop.portal.Request", "Close",
g_variant_new("()"), G_VARIANT_TYPE_UNIT,
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
remove_inhibit_data(info);
reply = g_dbus_connection_call_finish(info->c, result, &error);
if (error)
blog(LOG_WARNING, "Error uninhibiting: %s", error->message);
}
static void do_uninhibit(struct portal_inhibit_info *info)
{
if (info->cancellable) {
/* If uninhibit is called before the inhibit call is finished,
* cancel it instead.
*/
g_cancellable_cancel(info->cancellable);
g_clear_object(&info->cancellable);
} else {
g_dbus_connection_call(info->c, PORTAL_NAME, info->request_path,
"org.freedesktop.portal.Request",
"Close", g_variant_new("()"),
G_VARIANT_TYPE_UNIT,
G_DBUS_CALL_FLAGS_NONE, -1, NULL,
uninhibited_cb, info);
}
remove_inhibit_data(info);
}
void portal_inhibit_info_destroy(struct portal_inhibit_info *info)
{
if (info) {
g_cancellable_cancel(info->cancellable);
unsubscribe_from_request(info);
remove_inhibit_data(info);
g_clear_pointer(&info->sender_name, bfree);
g_clear_object(&info->cancellable);
g_clear_object(&info->c);
bfree(info);
}