Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
2025-12-18 Richard Frith-Macdonald <[email protected]>

* 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 <[email protected]>

* Source/x11/scale.c,
Expand Down
53 changes: 53 additions & 0 deletions Source/x11/XGServer.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down
67 changes: 51 additions & 16 deletions Source/x11/XGServerWindow.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Loading