Add support for wxEVT_POWER_{SUSPENDED,RESUME} under Linux

Generate these events on the systems running systemd, which provides
org.freedesktop.login1 D-Bus interface, but require acquiring system
power resource with the special new wxPOWER_DELAY "block mode" in order
to do it.

This means that existing applications need to be updated in order to
start getting these events, but it has the advantage of avoiding to have
to open the D-Bus connection on startup of every program, even if it's
never going to use it, and also forces the program to provide the
explanatory message, which is nice to have as it is shown by tools such
as systemd-inhibit to show what could be preventing system sleep.

Closes #23717.
This commit is contained in:
Vadim Zeitlin
2025-02-08 16:38:43 +01:00
parent 0dd60e51d1
commit e549a2b7e8
7 changed files with 228 additions and 29 deletions
+11 -3
View File
@@ -104,11 +104,18 @@ enum wxPowerResourceKind
wxPOWER_RESOURCE_SYSTEM
};
enum wxPowerBlockKind
{
wxPOWER_PREVENT,
wxPOWER_DELAY
};
class WXDLLIMPEXP_BASE wxPowerResource
{
public:
static bool Acquire(wxPowerResourceKind kind,
const wxString& reason = wxString());
const wxString& reason = wxString(),
wxPowerBlockKind blockKind = wxPOWER_PREVENT);
static void Release(wxPowerResourceKind kind);
};
@@ -116,9 +123,10 @@ class wxPowerResourceBlocker
{
public:
explicit wxPowerResourceBlocker(wxPowerResourceKind kind,
const wxString& reason = wxString())
const wxString& reason = wxString(),
wxPowerBlockKind blockKind = wxPOWER_PREVENT)
: m_kind(kind),
m_acquired(wxPowerResource::Acquire(kind, reason))
m_acquired(wxPowerResource::Acquire(kind, reason, blockKind))
{
}
+85 -10
View File
@@ -35,6 +35,31 @@ enum wxPowerResourceKind
wxPOWER_RESOURCE_SYSTEM
};
/**
Possible blocking behaviours for power resources.
@since 3.3.0
*/
enum wxPowerBlockKind
{
/**
Prevent the resource from disappearing.
When combined with ::wxPOWER_RESOURCE_SCREEN, this prevents the screen
from turning off automatically. When combined with
::wxPOWER_RESOURCE_SYSTEM, this prevents the system from suspending.
*/
wxPOWER_PREVENT,
/**
Delay suspend until the application handles the corresponding event.
This block kind can only be used with ::wxPOWER_RESOURCE_SYSTEM.
*/
wxPOWER_DELAY
};
/**
@class wxPowerEvent
@@ -42,12 +67,45 @@ enum wxPowerResourceKind
system is suspended, hibernated, plugged into or unplugged from the wall socket
and so on. wxPowerEvents are emitted by wxWindows.
Notice that currently only suspend and resume events are generated and only
under MS Windows platform. To avoid the need to change the code using this
event later when these events are implemented on the other platforms please
use the test <tt>ifdef wxHAS_POWER_EVENTS</tt> instead of directly testing for
the platform in your code: this symbol will be defined for all platforms
supporting the power events.
Notice that currently these events are generated only under MSW and Linux
and that under Linux you @e must use wxPowerResource::Acquire() to receive
them, e.g. typically an application interested in these events should
initialize wxPowerResourceBlocker for ::wxPOWER_RESOURCE_SYSTEM resource
using ::wxPOWER_DELAY block kind when initializing either the application
itself or its main window. E.g. for an application that wants to handle
suspend to gracefully close the open network connections and then reopen
them on resume you could do
@code
class MyMainFrame : public wxFrame
{
...
private:
wxPowerResourceBlocker m_powerDelaySleep;
};
MyMainFrame::MyMainFrame()
: wxFrame(...),
m_powerDelaySleep(wxPOWER_RESOURCE_SYSTEM,
"MyApp needs to close network connections",
wxPOWER_DELAY)
{
Bind(wxEVT_POWER_SUSPENDED, [](wxPowerEvent&) {
// Close network connections here.
});
Bind(wxEVT_POWER_RESUME, [](wxPowerEvent&) {
// Restore previously closed network connections here.
});
}
@endcode
To avoid the need to change the code using this event later when these
events are implemented on the other platforms please use the test <tt>ifdef
wxHAS_POWER_EVENTS</tt> instead of directly testing for the platform in
your code: this symbol will be defined for all platforms supporting the
power events.
@beginEventTable{wxPowerEvent}
@event{EVT_POWER_SUSPENDING(func)}
@@ -64,6 +122,8 @@ enum wxPowerResourceKind
connections here, possibly remembering them to reopen them later when
the system is resumed.
@event{EVT_POWER_SUSPEND_CANCEL(func)}
@warning This event is currently never generated.
System suspension was cancelled because some application vetoed it.
@event{EVT_POWER_RESUME(func)}
System resumed from suspend: normally the application should restore
@@ -125,8 +185,16 @@ public:
/**
Acquire a power resource for the application.
If successful, the system will not automatically power of the screen or
suspend until Release() is called.
The default behaviour, chosen by setting @a blockKind to
::wxPOWER_PREVENT, is to ensure that the resource of the corresponding
@a kind remains accessible, i.e. prevent the system from turning off the
screen or suspending.
Specifying ::wxPOWER_DELAY for @a blockKind doesn't actually prevent the
system from suspending but does delay it to allow the application to
handle the corresponding notifications. Note that under Linux systems
will *not* receive ::wxEVT_POWER_SUSPENDED without acquiring the system
resource using ::wxPOWER_DELAY.
Every call to Acquire @b must be matched by a corresponding call to
Release() or the system will not suspend until the application ends, use
@@ -138,12 +206,18 @@ public:
some platforms to inform the user what is preventing power saving.
It should usually describe the operation requiring the resource and
specifying it is strongly recommended.
@param blockKind The default value corresponds to the blocking
behaviour, the ::wxPOWER_DELAY value can be used to avoid blocking
the resource but just delay it to allow the application to handle
the corresponding notifications. This parameter is available in
wxWidgets 3.3.0 and later.
@return Returns true if the acquisition was successful.
@see Release()
*/
static bool Acquire(wxPowerResourceKind kind,
const wxString& reason = wxString());
const wxString& reason = wxString(),
wxPowerBlockKind blockKind = wxPOWER_PREVENT);
/**
Release a previously acquired power resource.
@@ -203,7 +277,8 @@ public:
Uses the same parameters as wxPowerResource::Acquire().
*/
explicit wxPowerResourceBlocker(wxPowerResourceKind kind,
const wxString& reason = wxString());
const wxString& reason = wxString(),
wxPowerBlockKind blockKind = wxPOWER_PREVENT);
/**
Returns whether the power resource could be acquired.
+10
View File
@@ -263,12 +263,22 @@ wxEND_EVENT_TABLE()
class MyApp : public wxApp
{
public:
MyApp()
: m_powerDelaySleep(wxPOWER_RESOURCE_SYSTEM,
"Sample needs to show sleep event",
wxPOWER_DELAY)
{
}
virtual bool OnInit() override
{
new MyFrame;
return true;
}
private:
wxPowerResourceBlocker m_powerDelaySleep;
};
wxIMPLEMENT_APP(MyApp);
+2 -1
View File
@@ -47,7 +47,8 @@
bool
wxPowerResource::Acquire(wxPowerResourceKind WXUNUSED(kind),
const wxString& WXUNUSED(reason))
const wxString& WXUNUSED(reason),
wxPowerBlockKind WXUNUSED(blockKind))
{
return false;
}
+102 -13
View File
@@ -28,6 +28,7 @@
#include "wx/intl.h"
#include "wx/log.h"
#include "wx/module.h"
#include "wx/window.h"
#endif
#include "wx/power.h"
@@ -71,7 +72,12 @@ public:
close(m_fdInhibit);
}
bool Inhibit(const wxString& reason);
// Start or stop blocking system sleep/shutdown.
bool StartInhibit(const wxString& reason, wxPowerBlockKind block);
void StopInhibit();
// Resume blocking with the same parameters as before.
void RestartInhibit();
private:
static GDBusProxy* CreateProxyLoginManager();
@@ -80,10 +86,14 @@ private:
int m_fdInhibit = INVALID_FD;
// Arguments passed to the last successful call to StartInhibit().
wxString m_reason;
wxPowerBlockKind m_blockKind;
wxDECLARE_NO_COPY_CLASS(wxGDBusLoginManagerProxy);
};
// The global login manager proxy, non-null if g_powerResourceAcquired != 0.
// The global login manager proxy.
std::unique_ptr<wxGDBusLoginManagerProxy> g_proxyLoginManager;
// Net number of times system power resource was acquired.
@@ -119,6 +129,43 @@ private:
// real implementation
// ----------------------------------------------------------------------------
extern "C" {
static void
wx_dbus_login_manager_cb(
GDBusProxy* WXUNUSED(proxy),
const gchar* WXUNUSED(sender),
const gchar* signalname,
GVariant* args,
gpointer WXUNUSED(data)
)
{
// We're only interested in a single signal.
if ( strcmp(signalname, "PrepareForSleep") != 0 )
return;
gboolean suspending = FALSE;
g_variant_get(args, "(b)", &suspending);
wxPowerEvent event(suspending ? wxEVT_POWER_SUSPENDED : wxEVT_POWER_RESUME);
for ( auto tlw : wxTopLevelWindows )
{
if ( tlw->IsShown() )
tlw->HandleWindowEvent(event);
}
if ( g_powerResourceAcquired )
{
// If we're suspending, we need to release the power resource to allow
// suspend to go ahead without waiting for the timeout -- and then we
// need to take it back again when we resume.
if ( suspending )
g_proxyLoginManager->StopInhibit();
else
g_proxyLoginManager->RestartInhibit();
}
}
}
// static
GDBusProxy* wxGDBusLoginManagerProxy::CreateProxyLoginManager()
{
@@ -142,11 +189,15 @@ GDBusProxy* wxGDBusLoginManagerProxy::CreateProxyLoginManager()
);
}
g_signal_connect(proxyLoginManager, "g-signal",
G_CALLBACK(wx_dbus_login_manager_cb), nullptr);
return proxyLoginManager;
}
// Start inhibiting system suspend.
bool wxGDBusLoginManagerProxy::Inhibit(const wxString& reason)
bool
wxGDBusLoginManagerProxy::StartInhibit(const wxString& reason,
wxPowerBlockKind blockKind)
{
if ( !m_proxyLoginManager )
return false;
@@ -163,8 +214,24 @@ bool wxGDBusLoginManagerProxy::Inhibit(const wxString& reason)
// And also some user-readable reason for doing this.
wxString why = reason;
if ( why.empty() )
why = _("Application needs to keep running");
const char* what = nullptr;
const char* mode = nullptr;
switch ( blockKind )
{
case wxPOWER_PREVENT:
mode = "block";
what = "sleep:shutdown:idle";
if ( why.empty() )
why = _("Application needs to keep running");
break;
case wxPOWER_DELAY:
mode = "delay";
what = "sleep";
if ( why.empty() )
why = _("Clean up before suspend");
break;
}
wxGtkObject<GUnixFDList> fd_list;
wxGtkError error;
@@ -173,10 +240,10 @@ bool wxGDBusLoginManagerProxy::Inhibit(const wxString& reason)
"Inhibit",
g_variant_new(
"(ssss)",
"sleep:shutdown:idle",
what,
static_cast<const char*>(who.utf8_str()),
static_cast<const char*>(why.utf8_str()),
"block"
mode
),
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT, // No timeout.
@@ -202,15 +269,36 @@ bool wxGDBusLoginManagerProxy::Inhibit(const wxString& reason)
m_fdInhibit = fds[0];
g_free(fds);
// Remember them so that we could call Inhibit() with the same arguments
// again later from RestartInhibit().
m_reason = reason;
m_blockKind = blockKind;
return true;
}
void wxGDBusLoginManagerProxy::StopInhibit()
{
wxCHECK_RET( m_fdInhibit != INVALID_FD, "Not inhibited" );
close(m_fdInhibit);
m_fdInhibit = INVALID_FD;
}
void wxGDBusLoginManagerProxy::RestartInhibit()
{
StartInhibit(m_reason, m_blockKind);
}
// ----------------------------------------------------------------------------
// public API
// ----------------------------------------------------------------------------
bool
wxPowerResource::Acquire(wxPowerResourceKind kind, const wxString& reason)
wxPowerResource::Acquire(wxPowerResourceKind kind,
const wxString& reason,
wxPowerBlockKind blockKind)
{
switch ( kind )
{
@@ -221,11 +309,12 @@ wxPowerResource::Acquire(wxPowerResourceKind kind, const wxString& reason)
case wxPOWER_RESOURCE_SYSTEM:
if ( !g_powerResourceAcquired++ )
{
g_proxyLoginManager.reset(new wxGDBusLoginManagerProxy);
if ( g_proxyLoginManager->Inhibit(reason) )
if ( !g_proxyLoginManager )
g_proxyLoginManager.reset(new wxGDBusLoginManagerProxy);
if ( g_proxyLoginManager->StartInhibit(reason, blockKind) )
return true;
g_proxyLoginManager.reset();
g_powerResourceAcquired--;
}
break;
@@ -243,7 +332,7 @@ void wxPowerResource::Release(wxPowerResourceKind kind)
case wxPOWER_RESOURCE_SYSTEM:
if ( !--g_powerResourceAcquired )
g_proxyLoginManager.reset();
g_proxyLoginManager->StopInhibit();
break;
}
}
+8 -1
View File
@@ -58,8 +58,15 @@ bool UpdatePowerResourceExecutionState()
bool
wxPowerResource::Acquire(wxPowerResourceKind kind,
const wxString& WXUNUSED(reason))
const wxString& WXUNUSED(reason),
wxPowerBlockKind blockKind)
{
if ( blockKind == wxPOWER_DELAY )
{
// We don't support this mode under MSW because it's not needed there.
return true;
}
switch ( kind )
{
case wxPOWER_RESOURCE_SCREEN:
+10 -1
View File
@@ -68,8 +68,17 @@ bool UpdatePowerResourceUsage(wxPowerResourceKind kind, const wxString& reason)
return true;
}
bool wxPowerResource::Acquire(wxPowerResourceKind kind, const wxString& reason)
bool
wxPowerResource::Acquire(wxPowerResourceKind kind,
const wxString& reason,
wxPowerBlockKind blockKind)
{
if ( blockKind == wxPOWER_DELAY )
{
// We don't support this mode under macOS because it's not needed there.
return true;
}
wxAtomicInc(g_powerResourceSystemRefCount);
bool success = UpdatePowerResourceUsage(kind, reason);