x11: Support desktop startup notification

This lets the window manager know when a program has finished launching, to stop showing a spinning cursor

Can be disabled with `x-send-startup-notification false`
This commit is contained in:
rdb 2022-12-01 15:45:21 +01:00
parent f593026061
commit 60acf82f04
6 changed files with 152 additions and 16 deletions

View File

@ -84,6 +84,13 @@ ConfigVariableString x_wm_class
PRC_DESC("Specify the value to use for the res_class field of the window's "
"WM_CLASS property."));
ConfigVariableBool x_send_startup_notification
("x-send-startup-notification", true,
PRC_DESC("Set this to true to send a startup notification to the window "
"manager automatically after the first window is opened. This "
"lets the window manager know that an application has launched, so "
"that it no longer needs to display a spinning mouse cursor."));
/**
* Initializes the library. This must be called at least once before any of
* the functions or classes in this library can be used. Normally it will be

View File

@ -36,5 +36,6 @@ extern ConfigVariableInt x_wheel_right_button;
extern ConfigVariableInt x_cursor_size;
extern ConfigVariableString x_wm_class_name;
extern ConfigVariableString x_wm_class;
extern ConfigVariableBool x_send_startup_notification;
#endif

View File

@ -57,6 +57,15 @@ get_hidden_cursor() {
return _hidden_cursor;
}
/**
* Returns the startup id that may have been passed by the environment variable
* DESKTOP_STARTUP_ID.
*/
INLINE const std::string &x11GraphicsPipe::
get_startup_id() const {
return _startup_id;
}
/**
* Returns true if a form of relative mouse mode is supported on this display.
*/

View File

@ -66,6 +66,20 @@ x11GraphicsPipe(const std::string &display) :
// point to mean a decimal point.
setlocale(LC_NUMERIC, "C");
// Also save the startup ID. We are required to unset it by the FreeDesktop
// specification so that it is not propagated to child processes.
{
char *startup_id = getenv("DESKTOP_STARTUP_ID");
if (startup_id != nullptr) {
_startup_id.assign(startup_id);
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Got desktop startup ID " << _startup_id << "\n";
}
unsetenv("DESKTOP_STARTUP_ID");
}
}
_is_valid = false;
_supported_types = OT_window | OT_buffer | OT_texture_buffer;
_display = nullptr;
@ -375,21 +389,25 @@ x11GraphicsPipe(const std::string &display) :
}
// Get some X atom numbers.
_utf8_string = XInternAtom(_display, "UTF8_STRING", false);
_wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", false);
_net_startup_id = XInternAtom(_display, "_NET_STARTUP_ID", false);
_net_startup_info = XInternAtom(_display, "_NET_STARTUP_INFO", false);
_net_startup_info_begin = XInternAtom(_display, "_NET_STARTUP_INFO_BEGIN", false);
_net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false);
_net_wm_pid = XInternAtom(_display, "_NET_WM_PID", false);
_net_wm_ping = XInternAtom(_display, "_NET_WM_PING", false);
_net_wm_window_type = XInternAtom(_display, "_NET_WM_WINDOW_TYPE", false);
_net_wm_window_type_splash = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_SPLASH", false);
_net_wm_window_type_fullscreen = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_FULLSCREEN", false);
_net_wm_state = XInternAtom(_display, "_NET_WM_STATE", false);
_net_wm_state_fullscreen = XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", false);
_net_wm_state_above = XInternAtom(_display, "_NET_WM_STATE_ABOVE", false);
_net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false);
_net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false);
_net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false);
_net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false);
_net_wm_state_maximized_vert = XInternAtom(_display, "_NET_WM_STATE_MAXIMIZED_VERT", false);
_net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false);
_net_wm_state_fullscreen = XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", false);
_net_wm_state_maximized_horz = XInternAtom(_display, "_NET_WM_STATE_MAXIMIZED_HORZ", false);
_net_wm_state_maximized_vert = XInternAtom(_display, "_NET_WM_STATE_MAXIMIZED_VERT", false);
_net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false);
_net_wm_window_type = XInternAtom(_display, "_NET_WM_WINDOW_TYPE", false);
_net_wm_window_type_fullscreen = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_FULLSCREEN", false);
_net_wm_window_type_splash = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_SPLASH", false);
}
/**
@ -406,6 +424,82 @@ x11GraphicsPipe::
}
}
/**
* Tells the window manager that the launch sequence with the indicated startup
* ID has finished. Will only send it once, and only if DESKTOP_STARTUP_ID was
* passed in as an environment variable.
*/
void x11GraphicsPipe::
send_startup_notification() {
if (_sent_startup_notification || _startup_id.empty()) {
return;
}
// Allocate enough room for the message, with room for escape characters.
char *message = (char *)alloca(_startup_id.size() * 2 + 14);
strcpy(message, "remove: ID=\"");
char *p = message + 12;
for (char c : _startup_id) {
if (c == '"' || c == '\\') {
*p++ = '\\';
}
*p++ = c;
}
*p++ = '\"';
*p++ = '\0';
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Sending startup info message: " << message << "\n";
}
// It doesn't *strictly* seem to be necessary to create a window for this
// (just passing in the root window works too) but the spec says we should
// and GTK does it too, so why not?
XSetWindowAttributes attrs;
attrs.override_redirect = True;
attrs.event_mask = PropertyChangeMask | StructureNotifyMask;
X11_Window xwin = XCreateWindow(_display, _root, -100, -100, 1, 1, 0,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWEventMask, &attrs);
XEvent xevent;
xevent.xclient.type = ClientMessage;
xevent.xclient.message_type = _net_startup_info_begin;
xevent.xclient.display = _display;
xevent.xclient.window = xwin;
xevent.xclient.format = 8;
const char *src = message;
const char *src_end = message + strlen(message) + 1;
char *dest, *dest_end;
while (src != src_end) {
dest = &xevent.xclient.data.b[0];
dest_end = dest + 20;
while (dest != dest_end && src != src_end) {
*dest = *src;
++dest;
++src;
}
while (dest != dest_end) {
*dest = '\0';
++dest;
}
XSendEvent(_display, _root, False, PropertyChangeMask, &xevent);
xevent.xclient.message_type = _net_startup_info;
}
XDestroyWindow(_display, xwin);
XFlush(_display);
_sent_startup_notification = true;
}
/**
* Enables raw mouse mode for this display. Returns false if unsupported.
*/

View File

@ -144,6 +144,9 @@ public:
INLINE X11_Cursor get_hidden_cursor();
INLINE const std::string &get_startup_id() const;
void send_startup_notification();
INLINE bool supports_relative_mouse() const;
INLINE bool enable_dga_mouse();
INLINE void disable_dga_mouse();
@ -165,21 +168,25 @@ public:
public:
// Atom specifications.
Atom _utf8_string;
Atom _wm_delete_window;
Atom _net_startup_id;
Atom _net_startup_info;
Atom _net_startup_info_begin;
Atom _net_wm_bypass_compositor;
Atom _net_wm_pid;
Atom _net_wm_ping;
Atom _net_wm_window_type;
Atom _net_wm_window_type_splash;
Atom _net_wm_window_type_fullscreen;
Atom _net_wm_state;
Atom _net_wm_state_fullscreen;
Atom _net_wm_state_above;
Atom _net_wm_state_below;
Atom _net_wm_state_add;
Atom _net_wm_state_remove;
Atom _net_wm_bypass_compositor;
Atom _net_wm_state_maximized_vert;
Atom _net_wm_state_below;
Atom _net_wm_state_fullscreen;
Atom _net_wm_state_maximized_horz;
Atom _net_wm_state_maximized_vert;
Atom _net_wm_state_remove;
Atom _net_wm_window_type;
Atom _net_wm_window_type_fullscreen;
Atom _net_wm_window_type_splash;
// Extension functions.
typedef int (*pfn_XcursorGetDefaultSize)(X11_Display *);
@ -224,6 +231,9 @@ protected:
X11_Cursor _hidden_cursor;
std::string _startup_id;
bool _sent_startup_notification = false;
typedef Bool (*pfn_XF86DGAQueryVersion)(X11_Display *, int*, int*);
typedef Status (*pfn_XF86DGADirectVideo)(X11_Display *, int, int);
pfn_XF86DGADirectVideo _XF86DGADirectVideo;

View File

@ -1260,6 +1260,15 @@ open_window() {
XDefineCursor(_display, _xwindow, cursor);
}
// Set _NET_STARTUP_ID if we've got it, so that the window manager knows
// this window belongs to a particular launch request.
const std::string &startup_id = x11_pipe->get_startup_id();
if (!startup_id.empty() && _parent_window_handle == nullptr) {
XChangeProperty(_display, _xwindow, x11_pipe->_net_startup_id,
x11_pipe->_utf8_string, 8, PropModeReplace,
(unsigned char *)startup_id.c_str(), startup_id.size());
}
XMapWindow(_display, _xwindow);
if (_properties.get_raw_mice()) {
@ -1279,6 +1288,12 @@ open_window() {
_parent_window_handle->attach_child(_window_handle);
}
// Now that we've opened a window, tell the window manager that the
// application has finished starting up.
if (!startup_id.empty() && x_send_startup_notification) {
x11_pipe->send_startup_notification();
}
return true;
}