1/*
2 * Copyright (C) 2017 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "SleepDisablerGLib.h"
28
29#include <gio/gio.h>
30#include <wtf/glib/GUniquePtr.h>
31
32namespace PAL {
33
34std::unique_ptr<SleepDisabler> SleepDisabler::create(const char* reason, Type type)
35{
36 return std::unique_ptr<SleepDisabler>(new SleepDisablerGLib(reason, type));
37}
38
39SleepDisablerGLib::SleepDisablerGLib(const char* reason, Type type)
40 : SleepDisabler(reason, type)
41 , m_cancellable(adoptGRef(g_cancellable_new()))
42 , m_reason(reason)
43{
44 // We ignore Type because we always want to inhibit both screen lock and
45 // suspend, but only when idle. There is no reason for WebKit to ever block
46 // a user from manually suspending the computer, so inhibiting idle
47 // suffices. There's also probably no good reason for code taking a sleep
48 // disabler to differentiate between lock and suspend on our platform. If we
49 // ever need this distinction, which seems unlikely, then we'll need to
50 // audit all use of SleepDisabler.
51
52 // First, try to use the ScreenSaver API.
53 g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
54 nullptr, "org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", m_cancellable.get(),
55 [](GObject*, GAsyncResult* result, gpointer userData) {
56 GUniqueOutPtr<GError> error;
57 GRefPtr<GDBusProxy> proxy = adoptGRef(g_dbus_proxy_new_for_bus_finish(result, &error.outPtr()));
58 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
59 return;
60
61 auto* self = static_cast<SleepDisablerGLib*>(userData);
62 if (proxy) {
63 // Under Flatpak, we'll get a useless proxy with no name owner.
64 GUniquePtr<char> nameOwner(g_dbus_proxy_get_name_owner(proxy.get()));
65 if (nameOwner) {
66 self->m_screenSaverProxy = WTFMove(proxy);
67 self->acquireInhibitor();
68 return;
69 }
70 }
71
72 // It failed. Try to use the Flatpak portal instead.
73 g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
74 nullptr, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Inhibit", self->m_cancellable.get(),
75 [](GObject*, GAsyncResult* result, gpointer userData) {
76 GUniqueOutPtr<GError> error;
77 GRefPtr<GDBusProxy> proxy = adoptGRef(g_dbus_proxy_new_for_bus_finish(result, &error.outPtr()));
78 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
79 return;
80
81 auto* self = static_cast<SleepDisablerGLib*>(userData);
82 if (proxy) {
83 GUniquePtr<char> nameOwner(g_dbus_proxy_get_name_owner(proxy.get()));
84 if (nameOwner) {
85 self->m_inhibitPortalProxy = WTFMove(proxy);
86 self->acquireInhibitor();
87 return;
88 }
89 }
90
91 // Give up. Don't warn the user: this is expected.
92 self->m_cancellable = nullptr;
93 }, self);
94 }, this);
95}
96
97SleepDisablerGLib::~SleepDisablerGLib()
98{
99 if (m_cancellable)
100 g_cancellable_cancel(m_cancellable.get());
101 else if (m_screenSaverCookie || m_inhibitPortalRequestObjectPath)
102 releaseInhibitor();
103}
104
105void SleepDisablerGLib::acquireInhibitor()
106{
107 if (m_screenSaverProxy) {
108 ASSERT(!m_inhibitPortalProxy);
109 acquireInhibitorViaScreenSaverProxy();
110 } else {
111 ASSERT(m_inhibitPortalProxy);
112 acquireInhibitorViaInhibitPortalProxy();
113 }
114}
115
116void SleepDisablerGLib::acquireInhibitorViaScreenSaverProxy()
117{
118 g_dbus_proxy_call(m_screenSaverProxy.get(), "Inhibit", g_variant_new("(ss)", g_get_prgname(), m_reason.data()),
119 G_DBUS_CALL_FLAGS_NONE, -1, m_cancellable.get(), [](GObject* proxy, GAsyncResult* result, gpointer userData) {
120 GUniqueOutPtr<GError> error;
121 GRefPtr<GVariant> returnValue = adoptGRef(g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), result, &error.outPtr()));
122 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
123 return;
124
125 auto* self = static_cast<SleepDisablerGLib*>(userData);
126 if (error)
127 g_warning("Calling %s.Inhibit failed: %s", g_dbus_proxy_get_interface_name(G_DBUS_PROXY(proxy)), error->message);
128 else {
129 ASSERT(returnValue);
130 g_variant_get(returnValue.get(), "(u)", &self->m_screenSaverCookie);
131 }
132 self->m_cancellable = nullptr;
133 }, this);
134}
135
136void SleepDisablerGLib::acquireInhibitorViaInhibitPortalProxy()
137{
138 GVariantBuilder builder;
139 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
140 g_variant_builder_add(&builder, "{sv}", "reason", g_variant_new_string(m_reason.data()));
141 g_dbus_proxy_call(m_inhibitPortalProxy.get(), "Inhibit", g_variant_new("(su@a{sv})", "" /* no window */, 8 /* idle */, g_variant_builder_end(&builder)),
142 G_DBUS_CALL_FLAGS_NONE, -1, m_cancellable.get(), [](GObject* proxy, GAsyncResult* result, gpointer userData) {
143 GUniqueOutPtr<GError> error;
144 GRefPtr<GVariant> returnValue = adoptGRef(g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), result, &error.outPtr()));
145 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
146 return;
147
148 auto* self = static_cast<SleepDisablerGLib*>(userData);
149 if (error)
150 g_warning("Calling %s.Inhibit failed: %s", g_dbus_proxy_get_interface_name(G_DBUS_PROXY(proxy)), error->message);
151 else {
152 ASSERT(returnValue);
153 g_variant_get(returnValue.get(), "(o)", &self->m_inhibitPortalRequestObjectPath.outPtr());
154 }
155 self->m_cancellable = nullptr;
156 }, this);
157}
158
159void SleepDisablerGLib::releaseInhibitor()
160{
161 if (m_screenSaverProxy) {
162 ASSERT(!m_inhibitPortalProxy);
163 releaseInhibitorViaScreenSaverProxy();
164 } else {
165 ASSERT(m_inhibitPortalProxy);
166 releaseInhibitorViaInhibitPortalProxy();
167 }
168}
169
170void SleepDisablerGLib::releaseInhibitorViaScreenSaverProxy()
171{
172 ASSERT(m_screenSaverCookie);
173
174 g_dbus_proxy_call(m_screenSaverProxy.get(), "UnInhibit", g_variant_new("(u)", m_screenSaverCookie),
175 G_DBUS_CALL_FLAGS_NONE, -1, nullptr, [](GObject* proxy, GAsyncResult* result, gpointer) {
176 GUniqueOutPtr<GError> error;
177 GRefPtr<GVariant> returnValue = adoptGRef(g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), result, &error.outPtr()));
178 if (error)
179 g_warning("Calling %s.UnInhibit failed: %s", g_dbus_proxy_get_interface_name(G_DBUS_PROXY(proxy)), error->message);
180 }, nullptr);
181}
182
183void SleepDisablerGLib::releaseInhibitorViaInhibitPortalProxy()
184{
185 ASSERT(m_inhibitPortalRequestObjectPath);
186
187 g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, static_cast<GDBusProxyFlags>(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
188 nullptr, "org.freedesktop.portal.Desktop", m_inhibitPortalRequestObjectPath.get(), "org.freedesktop.portal.Request", nullptr,
189 [](GObject*, GAsyncResult* result, gpointer) {
190 GUniqueOutPtr<GError> error;
191 GRefPtr<GDBusProxy> requestProxy = adoptGRef(g_dbus_proxy_new_for_bus_finish(result, &error.outPtr()));
192 if (error) {
193 g_warning("Failed to create org.freedesktop.portal.Request proxy: %s", error->message);
194 return;
195 }
196
197 ASSERT(requestProxy);
198 g_dbus_proxy_call(requestProxy.get(), "Close", g_variant_new("()"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr,
199 [](GObject* proxy, GAsyncResult* result, gpointer) {
200 GUniqueOutPtr<GError> error;
201 GRefPtr<GVariant> returnValue = adoptGRef(g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), result, &error.outPtr()));
202 if (error)
203 g_warning("Calling %s.Close failed: %s", g_dbus_proxy_get_interface_name(G_DBUS_PROXY(proxy)), error->message);
204 }, nullptr);
205 }, nullptr);
206}
207
208} // namespace PAL
209