Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.

Commit

Permalink
Implement support for the WM_Sn selection (#4374)
Browse files Browse the repository at this point in the history
Closes #536

When the WM_Sn selection is already owned at startup, this now either
errors out or waits for the old selection owner to exit.
  • Loading branch information
psychon committed Aug 27, 2021
1 parent 36ba104 commit 7b6e864
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 2 deletions.
2 changes: 2 additions & 0 deletions RELEASE-NOTES-next
Expand Up @@ -43,6 +43,8 @@ option is enabled and only then sets a screenshot as background.
• Allow multiple output names in 'move container|workspace to output'
• Add 'move container|workspace to output next'
• Add 'all' window matching criterion
• Acquire the WM_Sn selection when starting as required by ICCCM
• Add --replace command line argument to replace an existing WM

┌────────────────────────────┐
│ Bugfixes │
Expand Down
3 changes: 2 additions & 1 deletion include/i3-atoms_rest.xmacro.h
Expand Up @@ -21,4 +21,5 @@ xmacro(I3_FLOATING_WINDOW) \
xmacro(_NET_REQUEST_FRAME_EXTENTS) \
xmacro(_NET_FRAME_EXTENTS) \
xmacro(_MOTIF_WM_HINTS) \
xmacro(WM_CHANGE_STATE)
xmacro(WM_CHANGE_STATE) \
xmacro(MANAGER)
1 change: 1 addition & 0 deletions include/i3.h
Expand Up @@ -39,6 +39,7 @@ extern bool debug_build;
/** The number of file descriptors passed via socket activation. */
extern int listen_fds;
extern int conn_screen;
extern xcb_atom_t wm_sn;
/**
* The EWMH support window that is used to indicate that an EWMH-compliant
* window manager is present. This window is created when i3 starts and
Expand Down
3 changes: 3 additions & 0 deletions man/i3.man
Expand Up @@ -44,6 +44,9 @@ Retrieve the i3 IPC socket path from X11, print it, then exit.
Limits the size of the i3 SHM log to <limit> bytes. Setting this to 0 disables
SHM logging entirely. The default is 0 bytes.

--replace::
Replace an existing window manager.

== DESCRIPTION

=== INTRODUCTION
Expand Down
19 changes: 19 additions & 0 deletions src/handlers.c
Expand Up @@ -1076,6 +1076,21 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) {
randr_query_outputs();
}

/*
* Handles SelectionClear events for the root window, which are generated when
* we lose ownership of a selection.
*/
static void handle_selection_clear(xcb_selection_clear_event_t *event) {
if (event->selection != wm_sn) {
DLOG("SelectionClear for unknown selection %d, ignoring\n", event->selection);
return;
}
LOG("Lost WM_Sn selection, exiting.\n");
exit(EXIT_SUCCESS);

/* unreachable */
}

/*
* Handles the WM_CLASS property for assignments and criteria selection.
*
Expand Down Expand Up @@ -1428,6 +1443,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
handle_configure_notify((xcb_configure_notify_event_t *)event);
break;

case XCB_SELECTION_CLEAR:
handle_selection_clear((xcb_selection_clear_event_t *)event);
break;

default:
//DLOG("Unhandled event of type %d\n", type);
break;
Expand Down
131 changes: 130 additions & 1 deletion src/main.c
Expand Up @@ -24,6 +24,7 @@
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <xcb/xcb_atom.h>
#include <xcb/xinerama.h>
#include <xcb/bigreq.h>

Expand Down Expand Up @@ -65,6 +66,9 @@ xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME;
xcb_screen_t *root_screen;
xcb_window_t root;

xcb_window_t wm_sn_selection_owner;
xcb_atom_t wm_sn;

/* Color depth, visual id and colormap to use when creating windows and
* pixmaps. Will use 32 bit depth and an appropriate visual, if available,
* otherwise the root window’s default (usually 24 bit TrueColor). */
Expand Down Expand Up @@ -284,6 +288,7 @@ int main(int argc, char *argv[]) {
char *fake_outputs = NULL;
bool disable_signalhandler = false;
bool only_check_config = false;
bool replace_wm = false;
static struct option long_options[] = {
{"no-autostart", no_argument, 0, 'a'},
{"config", required_argument, 0, 'c'},
Expand All @@ -306,6 +311,7 @@ int main(int argc, char *argv[]) {
{"fake_outputs", required_argument, 0, 0},
{"fake-outputs", required_argument, 0, 0},
{"force-old-config-parser-v4.4-only", no_argument, 0, 0},
{"replace", no_argument, 0, 'r'},
{0, 0, 0, 0}};
int option_index = 0, opt;

Expand All @@ -330,7 +336,7 @@ int main(int argc, char *argv[]) {

start_argv = argv;

while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:V", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:Vr", long_options, &option_index)) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
Expand Down Expand Up @@ -368,6 +374,9 @@ int main(int argc, char *argv[]) {
case 'l':
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
break;
case 'r':
replace_wm = true;
break;
case 0:
if (strcmp(long_options[option_index].name, "force-xinerama") == 0 ||
strcmp(long_options[option_index].name, "force_xinerama") == 0) {
Expand Down Expand Up @@ -448,6 +457,9 @@ int main(int argc, char *argv[]) {
"\tThe default is %d bytes.\n",
shmlog_size);
fprintf(stderr, "\n");
fprintf(stderr, "\t--replace\n"
"\tReplace an existing window manager.\n");
fprintf(stderr, "\n");
fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n"
"to send to a currently running i3 (like i3-msg). This allows you to\n"
"use nice and logical commands, such as:\n"
Expand Down Expand Up @@ -586,6 +598,10 @@ int main(int argc, char *argv[]) {
xcb_prefetch_extension_data(conn, &xcb_randr_id);
}

/* Prepare for us to get a current timestamp as recommended by ICCCM */
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE});
xcb_change_property(conn, XCB_PROP_MODE_APPEND, root, XCB_ATOM_SUPERSCRIPT_X, XCB_ATOM_CARDINAL, 32, 0, "");

/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
Expand Down Expand Up @@ -627,6 +643,22 @@ int main(int argc, char *argv[]) {
xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);

/* Get the PropertyNotify event we caused above */
xcb_flush(conn);
{
xcb_generic_event_t *event;
DLOG("waiting for PropertyNotify event\n");
while ((event = xcb_wait_for_event(conn)) != NULL) {
if (event->response_type == XCB_PROPERTY_NOTIFY) {
last_timestamp = ((xcb_property_notify_event_t *)event)->time;
free(event);
break;
}
free(event);
}
DLOG("got timestamp %d\n", last_timestamp);
}

/* Setup NetWM atoms */
#define xmacro(name) \
do { \
Expand Down Expand Up @@ -656,6 +688,103 @@ int main(int argc, char *argv[]) {
force_xinerama = true;
}

/* Acquire the WM_Sn selection. */
{
/* Get the WM_Sn atom */
char *atom_name = xcb_atom_name_by_screen("WM_S", conn_screen);
wm_sn_selection_owner = xcb_generate_id(conn);

if (atom_name == NULL) {
ELOG("xcb_atom_name_by_screen(\"WM_S\", %d) failed, exiting\n", conn_screen);
return 1;
}

xcb_intern_atom_reply_t *atom_reply;
atom_reply = xcb_intern_atom_reply(conn,
xcb_intern_atom_unchecked(conn,
0,
strlen(atom_name),
atom_name),
NULL);
free(atom_name);
if (atom_reply == NULL) {
ELOG("Failed to intern the WM_Sn atom, exiting\n");
return 1;
}
wm_sn = atom_reply->atom;
free(atom_reply);

/* Check if the selection is already owned */
xcb_get_selection_owner_reply_t *selection_reply =
xcb_get_selection_owner_reply(conn,
xcb_get_selection_owner(conn, wm_sn),
NULL);
if (selection_reply && selection_reply->owner != XCB_NONE && !replace_wm) {
ELOG("Another window manager is already running (WM_Sn is owned)");
return 1;
}

/* Become the selection owner */
xcb_create_window(conn,
root_screen->root_depth,
wm_sn_selection_owner, /* window id */
root_screen->root, /* parent */
-1, -1, 1, 1, /* geometry */
0, /* border width */
XCB_WINDOW_CLASS_INPUT_OUTPUT,
root_screen->root_visual,
0, NULL);
xcb_change_property(conn,
XCB_PROP_MODE_REPLACE,
wm_sn_selection_owner,
XCB_ATOM_WM_CLASS,
XCB_ATOM_STRING,
8,
(strlen("i3-WM_Sn") + 1) * 2,
"i3-WM_Sn\0i3-WM_Sn\0");

xcb_set_selection_owner(conn, wm_sn_selection_owner, wm_sn, last_timestamp);

if (selection_reply && selection_reply->owner != XCB_NONE) {
unsigned int usleep_time = 100000; /* 0.1 seconds */
int check_rounds = 150; /* Wait for a maximum of 15 seconds */
xcb_get_geometry_reply_t *geom_reply = NULL;

DLOG("waiting for old WM_Sn selection owner to exit");
do {
free(geom_reply);
usleep(usleep_time);
if (check_rounds-- == 0) {
ELOG("The old window manager is not exiting");
return 1;
}
geom_reply = xcb_get_geometry_reply(conn,
xcb_get_geometry(conn, selection_reply->owner),
NULL);
} while (geom_reply != NULL);
}
free(selection_reply);

/* Announce that we are the new owner */
/* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
* In order to properly initialize these bytes, we allocate 32 bytes even
* though we only need less for an xcb_client_message_event_t */
union {
xcb_client_message_event_t message;
char storage[32];
} event;
memset(&event, 0, sizeof(event));
event.message.response_type = XCB_CLIENT_MESSAGE;
event.message.window = root_screen->root;
event.message.format = 32;
event.message.type = A_MANAGER;
event.message.data.data32[0] = last_timestamp;
event.message.data.data32[1] = wm_sn;
event.message.data.data32[2] = wm_sn_selection_owner;

xcb_send_event(conn, 0, root_screen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, event.storage);
}

xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
Expand Down

0 comments on commit 7b6e864

Please sign in to comment.