From 050420402bb9a9aafd3a4118b13c7d357b03d571 Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Thu, 18 Dec 2025 13:06:39 +0000 Subject: [PATCH 1/2] Attempt to fix #62 --- ChangeLog | 11 ++++++ Source/x11/XGServer.m | 53 +++++++++++++++++++++++++++++ Source/x11/XGServerWindow.m | 67 ++++++++++++++++++++++++++++--------- 3 files changed, 115 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index 35551b1b..8e271759 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2025-12-18 Richard Frith-Macdonald + + * Source/x11/XGServer.m: + * Source/x11/XGServerWindow.m: + If DESKTOP_STARTUP_ID environment variable is present, set it as the + _NET_STARTUP_ID property of the first window we make visible (to tell + the WM we are starting up) and, when launch completes, send a message + to the root window to tell it that startup is over. + This is an attempt to fix issue #62 on github by implementing the + XDG startup notification mechanism. + 2025-05-21 Fred Kiefer * Source/x11/scale.c, diff --git a/Source/x11/XGServer.m b/Source/x11/XGServer.m index 82e4f12d..de52a908 100644 --- a/Source/x11/XGServer.m +++ b/Source/x11/XGServer.m @@ -382,6 +382,48 @@ + (Display *) xDisplay return [(XGServer*)GSCurrentServer() xDisplay]; } +static NSString *startupID = nil; + +- (void) _didFinishLaunching: (NSNotification*)n +{ + /* To tell the window manager that we have completed launching we must + * send a _NET_STARTUP_INFO_END message telling it that startup has ended + * for the DESKTOP_STARTUP_ID (issue #62 on github) + */ + if (startupID) + { + const char *startup_id = [startupID UTF8String]; + Window root = RootWindow(dpy, defScreen); + Atom atom = XInternAtom(dpy, "_NET_STARTUP_INFO_END", False); + int len = strlen(startup_id); + char *msg = malloc(len + 12); + int pos = 0; + + snprintf(msg, len+12, "remove: ID=%s", startup_id); + len = strlen(msg) + 1; + + while (pos < len) + { + int chunk; + XEvent ev = {0}; + + ev.xclient.type = ClientMessage; + ev.xclient.window = root; + ev.xclient.message_type = atom; + ev.xclient.format = 8; + + chunk = (len - pos > 20) ? 20 : (len - pos); + memcpy(ev.xclient.data.b, msg + pos, chunk); + + XSendEvent(dpy, root, False, PropertyChangeMask, &ev); + pos += chunk; + } + XFlush(dpy); + + DESTROY(startupID); + } +} + - (id) _initXContext { int screen_id, display_id; @@ -490,6 +532,17 @@ - (id) initWithAttributes: (NSDictionary *)info [super initWithAttributes: info]; [self _initXContext]; + ASSIGN(startupID, [[[NSProcessInfo processInfo] environment] + objectForKey: @"DESKTOP_STARTUP_ID"]); + if (startupID) + { + [[NSNotificationCenter defaultCenter] + addObserver: self + selector: @selector(_didFinishLaunching:) + name: NSApplicationDidFinishLaunchingNotification + object: nil]; + } + [self setupRunLoopInputSourcesForMode: NSDefaultRunLoopMode]; [self setupRunLoopInputSourcesForMode: NSConnectionReplyMode]; [self setupRunLoopInputSourcesForMode: NSModalPanelRunLoopMode]; diff --git a/Source/x11/XGServerWindow.m b/Source/x11/XGServerWindow.m index 1add4321..27604807 100644 --- a/Source/x11/XGServerWindow.m +++ b/Source/x11/XGServerWindow.m @@ -89,7 +89,7 @@ static NSMapTable *windowtags = NULL; /* Track used window numbers */ -static int last_win_num = 0; +static int last_win_num = 0; @interface NSCursor (BackendPrivate) - (void *)_cid; @@ -1439,14 +1439,16 @@ - (void) _setupRootWindow * Set up standard atoms. */ #ifdef HAVE_XINTERNATOMS - XInternAtoms(dpy, atom_names, sizeof(atom_names)/sizeof(char*), - False, generic.atoms); + XInternAtoms(dpy, atom_names, sizeof(atom_names)/sizeof(char*), + False, generic.atoms); #else - { - int atomCount; + { + int count; - for (atomCount = 0; atomCount < sizeof(atom_names)/sizeof(char*); atomCount++) - generic.atoms[atomCount] = XInternAtom(dpy, atom_names[atomCount], False); + for (count = 0; count < sizeof(atom_names)/sizeof(char*); count++) + { + generic.atoms[count] = XInternAtom(dpy, atom_names[count], False); + } } #endif @@ -1580,9 +1582,9 @@ - (void) _setupRootWindow * hold 64bit values. */ XChangeProperty(dpy, ROOT, - generic._GNUSTEP_WM_ATTR_ATOM, generic._GNUSTEP_WM_ATTR_ATOM, - 32, PropModeReplace, (unsigned char *)&win_attrs, - sizeof(GNUstepWMAttributes)/sizeof(CARD32)); + generic._GNUSTEP_WM_ATTR_ATOM, generic._GNUSTEP_WM_ATTR_ATOM, + 32, PropModeReplace, (unsigned char *)&win_attrs, + sizeof(GNUstepWMAttributes)/sizeof(CARD32)); } if ((generic.wm & XGWM_EWMH) != 0) @@ -1595,12 +1597,13 @@ - (void) _setupRootWindow * hold 64bit values. */ XChangeProperty(dpy, ROOT, - generic._NET_WM_PID_ATOM, XA_CARDINAL, - 32, PropModeReplace, - (unsigned char*)&pid, 1); + generic._NET_WM_PID_ATOM, XA_CARDINAL, + 32, PropModeReplace, + (unsigned char*)&pid, 1); // FIXME: Need to set WM_CLIENT_MACHINE as well. } + /* We need to determine the offsets between the actual decorated window * and the window we draw into. */ @@ -1632,9 +1635,9 @@ - (void) _setupRootWindow } else { - offsets = (uint16_t *)PropGetCheckProperty(dpy, DefaultRootWindow(dpy), - generic._GNUSTEP_FRAME_OFFSETS_ATOM, - XA_CARDINAL, 16, 60, &count); + offsets = (uint16_t *)PropGetCheckProperty(dpy, + DefaultRootWindow(dpy), generic._GNUSTEP_FRAME_OFFSETS_ATOM, + XA_CARDINAL, 16, 60, &count); } if (offsets == 0) @@ -1924,6 +1927,7 @@ - (int) window: (NSRect)frame : (NSBackingStoreType)type : (unsigned int)style * It could be done for popup windows, but at this point we don't know * about the usage of the window. */ + window->xwn_attrs.override_redirect = False; window->ident = XCreateWindow(dpy, window->root, @@ -2869,6 +2873,37 @@ - (void) orderwindow: (int)op : (int)otherWin : (int)winNum if (op != NSWindowOut) { + static BOOL beenHere = NO; + + /* To tell the window manager that our window corresponds to + * a particular task we must pass it the DESKTOP_STARTUP_ID + * by setting it as a property of the first toplevel window + * made visible. + * (issue #62 on github) + */ + if (NO == beenHere) + { + NSProcessInfo *p = [NSProcessInfo processInfo]; + NSDictionary *e = [p environment]; + NSString *s = [e objectForKey: @"DESKTOP_STARTUP_ID"]; + + beenHere = YES; + if ([s length] > 0) + { + const char *ptr = [s UTF8String]; + + XChangeProperty(dpy, window->ident, + XInternAtom(dpy, "_NET_STARTUP_ID", False), + generic.UTF8_STRING_ATOM, + 8, + PropModeReplace, + (unsigned char *)ptr, + strlen(ptr) + ); + [p setValue: nil inEnvironment: @"DESKTOP_STARTUP_ID"]; + } + } + /* * Some window managers ignore any hints and properties until the * window is actually mapped, so we need to set them all up From 076f0db6c84cf918ed09102f0c9d35b2ead52d2a Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Tue, 23 Dec 2025 09:10:16 +0000 Subject: [PATCH 2/2] Tweak to build with older versions of the base library --- Source/x11/XGServerWindow.m | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/x11/XGServerWindow.m b/Source/x11/XGServerWindow.m index 27604807..206cf46d 100644 --- a/Source/x11/XGServerWindow.m +++ b/Source/x11/XGServerWindow.m @@ -2900,7 +2900,17 @@ - (void) orderwindow: (int)op : (int)otherWin : (int)winNum (unsigned char *)ptr, strlen(ptr) ); - [p setValue: nil inEnvironment: @"DESKTOP_STARTUP_ID"]; + /* Ideally we should remove the value from the environment to + * prevent any other library from acting on it. There is no + * OpenStep/Cocoa APIU to do that, but we can use an extension + * from GNUstepBase if available. + */ + if ([p respondsToSelector: @selector(setValue:inEnvironment:)]) + { + [p performSelector: @selector(setValue:inEnvironment:) + withObject: nil + withObject: @"DESKTOP_STARTUP_ID"]; + } } }