For any attempting to help, God bless and here's version 2. It adds define switches to allow override-redirect (killing of them messing with configure), a per_icccm, and a compiz switch (ability to use EWMH). Other property attempts not included for fear of getting too messy. Using override is close to what wanted, but negates features window managers provide, like graphics, virtual desktop switching (which is great for non-compliant anyways). Objective is after-all to create headerbars, but receive full wm support, like real configure notices. The compiz switch can be tried on other non-gnome type managers, it may work? It's from the EWMH protocols.
Steve

/*
 * Move window demo
 * Steven J Abner 2025
 */
#include <xcb/xcb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <inttypes.h>
#include <limits.h>
#include <poll.h>

#define OVERRIDE 1
#define PER_ICCCM 0
#if PER_ICCCM
 #undef OVERRIDE
 #define OVERRIDE 0
#endif

#define USING_COMPIZ 0
#if USING_COMPIZ
 #undef OVERRIDE
 #define OVERRIDE 0
 #undef PER_ICCCM
 #define PER_ICCCM 0
#endif

/* gcc -ansi -pedantic -Wall -g -o xmove xmove.c -lxcb */

typedef struct _PhxInterface                             PhxInterface;
typedef struct _PhxRectangle { int16_t x, y, w, h; }     PhxRectangle;

xcb_connection_t     *connection = NULL;
xcb_atom_t WM_PROTOCOLS;
xcb_atom_t WM_DELETE_WINDOW;
int16_t xroot, yroot;

struct _PhxInterface {
 PhxRectangle  mete_box;
 xcb_window_t  window;
} iface0 = { {0}, 0 };

#if (__STDC_VERSION__ <= 199901L)
 #define RECTANGLE(a,b,c,d,e) \
  a.x = b, a.y = c, a.w = d, a.h = e
#else
 #define RECTANGLE(a,b,c,d,e) \
  a = (PhxRectangle){ b, c, d, e }
#endif

static bool
event_keyboard(xcb_generic_event_t *nvt) {

  xcb_key_press_event_t *kp = (xcb_key_press_event_t*)nvt;
  xcb_client_message_event_t *message;
  message = calloc(32, 1);
  message->response_type  = XCB_CLIENT_MESSAGE;
  message->format         = 32;
  message->window         = kp->event;
  message->type           = WM_PROTOCOLS;
  message->data.data32[0] = WM_DELETE_WINDOW;
  message->data.data32[1] = kp->time;
  xcb_send_event(connection, false, message->window,
                     XCB_EVENT_MASK_NO_EVENT, (char*)message);
  xcb_flush(connection);
  return true;
}

#if (!USING_COMPIZ)
static bool
event_mouse(xcb_generic_event_t *nvt) {

  bool locus = ((nvt->response_type & (uint8_t)~0x80) == XCB_BUTTON_PRESS);
  xcb_button_press_event_t *mouse
    = (xcb_button_press_event_t*)nvt;

  if (locus) {
    xcb_grab_pointer_cookie_t c0;
    xcb_grab_pointer_reply_t *r0;

    xroot = mouse->root_x;
    yroot = mouse->root_y;
    c0 = xcb_grab_pointer(connection, 1, mouse->event,
        XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE
           | XCB_EVENT_MASK_POINTER_MOTION,
        XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
        XCB_NONE, XCB_NONE, mouse->time);
    r0 = xcb_grab_pointer_reply(connection, c0, NULL);
    if ((r0 == NULL) || (r0->status != XCB_GRAB_STATUS_SUCCESS)) {
      puts("grab failed");
      return false;
    }
  } else {
    xcb_ungrab_pointer(connection, mouse->time);
  }
  return true;
}
#endif

static bool
event_motion(xcb_generic_event_t *nvt) {

  xcb_motion_notify_event_t *motion
    = (xcb_motion_notify_event_t*)nvt;

  if (!!(motion->state & XCB_BUTTON_MASK_1)) {
#if USING_COMPIZ
    xcb_screen_t *screen
      = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
    xcb_intern_atom_cookie_t c0;
    xcb_intern_atom_reply_t *r0;
    xcb_client_message_event_t *message;
  
    c0 = xcb_intern_atom(connection, 1, 18, "_NET_WM_MOVERESIZE");
    r0 = xcb_intern_atom_reply(connection, c0, NULL);
  
    xcb_ungrab_pointer(connection, motion->time);
    xcb_flush(connection);
  
    message = calloc(32, 1);
    message->response_type  = XCB_CLIENT_MESSAGE;
    message->format         = 32;
    message->window         = motion->event;
    message->type           = r0->atom;
    message->data.data32[0] = motion->root_x;
    message->data.data32[1] = motion->root_y;
    message->data.data32[2] = 8; /* movement only */
    message->data.data32[3] = XCB_BUTTON_MASK_1;
    message->data.data32[4] = 1; /* source indication normal application */
    xcb_send_event(connection, false, screen->root,
                       XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
                       XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, (char*)message);
    xcb_flush(connection);
    free(r0);
    puts(" started drag XCB_MOTION_NOTIFY");
#else
    int32_t values[2];
    xcb_query_pointer_cookie_t c0
      = xcb_query_pointer(connection, iface0.window);
    xcb_query_pointer_reply_t *r0
      = xcb_query_pointer_reply(connection, c0, NULL);
    xcb_flush(connection);
    values[0] = (iface0.mete_box.x += (r0->root_x - xroot));
    values[1] = (iface0.mete_box.y += (r0->root_y - yroot));
    xroot = r0->root_x;
    yroot = r0->root_y;
    free(r0);
  #if PER_ICCCM
  {
    uint32_t values[1] = { true };
    xcb_change_window_attributes(connection, motion->event,
                                 XCB_CW_OVERRIDE_REDIRECT, values);
  }
  #endif
    xcb_configure_window(connection, motion->event,
                 XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
    xcb_flush(connection);
  #if PER_ICCCM
  {
    uint32_t values[1] = { false };
    xcb_change_window_attributes(connection, motion->event,
                                 XCB_CW_OVERRIDE_REDIRECT, values);
  }
  #endif
#endif
  }
  return true;
}

static int32_t
process_timers(void) {
  return INT_MAX;
}

static bool
process_event(xcb_generic_event_t *nvt) {

  switch (nvt->response_type & (uint8_t)~0x80) {

    case XCB_KEY_PRESS: {         /* response_type 2 */
      event_keyboard(nvt);
      break;
    }
    case XCB_KEY_RELEASE: {       /* response_type 3 */
      break;
    }

    case XCB_BUTTON_PRESS:        /* response_type 4 */
    case XCB_BUTTON_RELEASE: {    /* response_type 5 */
#if (!USING_COMPIZ)
      event_mouse(nvt);
#endif
      break;
    }

    case XCB_MOTION_NOTIFY: {     /* response_type 6 */
      event_motion(nvt);
      break;
    }

    case XCB_ENTER_NOTIFY: {      /* response_type 7 */
#if USING_COMPIZ
      xcb_enter_notify_event_t *xing
        = (xcb_enter_notify_event_t*)nvt;
      if (xing->mode == XCB_NOTIFY_MODE_UNGRAB)
        puts(" finished drag XCB_ENTER_NOTIFY");
#endif
      break;
    }

    case XCB_LEAVE_NOTIFY: {      /* response_type 8 */
      break;
    }

    case XCB_FOCUS_IN: {          /* response_type 9 */
#if (!OVERRIDE || PER_ICCCM)
      xcb_set_input_focus(connection,
                          XCB_INPUT_FOCUS_PARENT,
                          iface0.window, XCB_CURRENT_TIME);
#endif
      break;
    }
    case XCB_FOCUS_OUT: {         /* response_type 10 */
      break;
    }

    case XCB_EXPOSE: {            /* response_type 12 */
      break;
    }

    case XCB_VISIBILITY_NOTIFY: { /* response_type 15 */
#if (OVERRIDE && (!PER_ICCCM))
      xcb_set_input_focus(connection,
                          XCB_INPUT_FOCUS_PARENT,
                          iface0.window, XCB_CURRENT_TIME);
#endif
      break;
    }

    case XCB_DESTROY_NOTIFY: {    /* response_type 17 */
        /* assume a kill, delete all windows (do their thing)
           send false to exit loop */
      free(nvt);
      return false;
    }
      /* these 4 because of XCB_EVENT_MASK_STRUCTURE_NOTIFY */
    case XCB_UNMAP_NOTIFY: {      /* response_type 18 */
      break;
    }
    case XCB_MAP_NOTIFY: {        /* response_type 19 */
      break;
    }
    case XCB_REPARENT_NOTIFY: {   /* response_type 21 */
        /* position after map, to design user requested of window
         * set once and when window manager asks to reparent
         * dont need during icon/deiconify
         * doesnt stop WM configures, but will be sent after map notify */
      uint32_t values[2];
      values[0] = iface0.mete_box.x;
      values[1] = iface0.mete_box.y;
      xcb_configure_window(connection, iface0.window,
                   XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
      break;
    }
 
   case XCB_CONFIGURE_NOTIFY: {  /* response_type 22 */
       break;
    }

    case XCB_PROPERTY_NOTIFY: {   /* response_type 28 */
    case XCB_SELECTION_CLEAR:     /* response_type 29 */
    case XCB_SELECTION_REQUEST:   /* response_type 30 */
    case XCB_SELECTION_NOTIFY:    /* response_type 31 */
      break;
    }

    case XCB_CLIENT_MESSAGE: {    /* response_type 33 */
      xcb_client_message_event_t *cm
        = (xcb_client_message_event_t*)nvt;
      if (cm->type != WM_PROTOCOLS)  goto report;
      xcb_unmap_window(connection, iface0.window);
      xcb_destroy_window(connection, iface0.window);
      break;
    }

    default:
      if (nvt->response_type == 0) {
        xcb_generic_error_t *err = (xcb_generic_error_t*)nvt;
        printf("error: %"PRIu8"\n", err->error_code);
        break;
      }
report:
        /* Unknown event type, use to track coding problems */
      printf("Unknown event: %"PRIu8"\n", nvt->response_type);
      break;

  } /* end switch(response_type) */
    /* nvt no longer needed */
  free(nvt);
    /* critical! otherwise wont process anything done */
  xcb_flush(connection);
  return true;
}

static void
xcb_main(void) {

    /* set all before through to Xserver */
  xcb_flush(connection);

  do {
    int timeout;
    xcb_generic_event_t *event;
    while ((event = xcb_poll_for_event(connection)) != NULL)
        /* _process_event() returns 'running' for main loop. */
      if (process_event(event) == false)  goto cleanup;
    do {
        /* _process_timers() runs any timers that are immediately pending,
           and returns the number of milliseconds until the next timer
           in the queue is ready.
           _process_timers() returns INT_MAX if there are no timers. */
        /* _process_timers() also removes from timeout its run time. */
      timeout = process_timers();
      if ((event = xcb_poll_for_queued_event(connection)) == NULL)  break;
        if (process_event(event) == false)  goto cleanup;
    } while (1);
    if (timeout > 6) {
      struct pollfd pfd[1];
      pfd->fd = xcb_get_file_descriptor(connection);
      pfd->events = POLLIN;
      pfd->revents = POLLOUT;
      poll(pfd, 1, timeout);
    }
  } while (1);
    /* received XCB_DESTROY_NOTIFY */
cleanup:
  return;
}

void
ui_window_name(xcb_window_t window) {

  xcb_intern_atom_cookie_t cookie
    = xcb_intern_atom(connection, 1, 7, "WM_NAME");
  xcb_intern_atom_reply_t *reply
    = xcb_intern_atom_reply(connection, cookie, NULL);
  xcb_intern_atom_cookie_t cookie2
    = xcb_intern_atom(connection, 0, 6, "STRING");
  xcb_intern_atom_reply_t *reply2
    = xcb_intern_atom_reply(connection, cookie2, NULL);

  char title[16];
  sprintf(title, "%d", window);
  xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window,
                       reply->atom, reply2->atom, 8,
                       strlen(title), title);
  free(reply);
  free(reply2);
}

static void
_window_event_delete(xcb_connection_t *connection, xcb_window_t window) {

    /* allow delete window so can quit application. Avoid ^C. */
  xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window,
                      WM_PROTOCOLS, XCB_ATOM_ATOM, 32, 1,
                      &WM_DELETE_WINDOW);
}

static xcb_window_t
_window_create(PhxRectangle configure) {

  xcb_screen_t *screen;
  xcb_window_t window;
  uint32_t mask;
  uint32_t values[2];

  if (connection == NULL) {
    xcb_intern_atom_cookie_t c0, c1;
    xcb_intern_atom_reply_t *r0, *r1;
    connection = xcb_connect(NULL, NULL);
    c0 = xcb_intern_atom(connection, 0, 12, "WM_PROTOCOLS");
    c1 = xcb_intern_atom(connection, 0, 16, "WM_DELETE_WINDOW");
    r0 = xcb_intern_atom_reply(connection, c0, NULL);
    r1 = xcb_intern_atom_reply(connection, c1, NULL);
    WM_PROTOCOLS     = r0->atom;  free((void*)r0);
    WM_DELETE_WINDOW = r1->atom;  free((void*)r1);
  }

  screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
    /* Create the window */
  window = xcb_generate_id(connection);

  mask      = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
  values[0] = screen->white_pixel;
  values[1] = XCB_EVENT_MASK_EXPOSURE       | XCB_EVENT_MASK_BUTTON_PRESS   |
              XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
              XCB_EVENT_MASK_ENTER_WINDOW   | XCB_EVENT_MASK_LEAVE_WINDOW   |
              XCB_EVENT_MASK_KEY_PRESS      | XCB_EVENT_MASK_KEY_RELEASE    |
              XCB_EVENT_MASK_FOCUS_CHANGE   | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
#if PER_ICCCM
              XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
              XCB_EVENT_MASK_RESIZE_REDIRECT |
#endif
              XCB_EVENT_MASK_VISIBILITY_CHANGE;

    /* ignores x, y, border_width (virus) */
  xcb_create_window(connection,
                    screen->root_depth,            /* depth          */
                    window,                        /* generated id   */
                    screen->root,                  /* parent window  */
                    configure.x,                   /* x, y           */
                    configure.y,
                    configure.w,                   /* width, height  */
                    configure.h,
                    0,                             /* border_width   */
                    XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class          */
                    screen->root_visual,           /* visual         */
                    mask, values);                 /* masks */

{
  uint32_t value_list;
  xcb_font_t font;
  xcb_cursor_t cursor;
  font = xcb_generate_id(connection);
  xcb_open_font(connection, font, strlen ("cursor"), "cursor");
  cursor = xcb_generate_id(connection);
  xcb_create_glyph_cursor(connection, cursor, font, font,
                           132, 132 + 1,
                           0, 0, 0,
                           0, 0, 0);
  value_list = cursor;
  xcb_change_window_attributes(connection, window,
                               XCB_CW_CURSOR, &value_list);
}

#if (OVERRIDE && (!PER_ICCCM))
{
  values[0] = true;
  xcb_change_window_attributes(connection, window,
                               XCB_CW_OVERRIDE_REDIRECT, values);
}
#endif

  _window_event_delete(connection, window);
  return window;
}

xcb_window_t
ui_window_create(PhxRectangle configure) {

  xcb_window_t window = _window_create(configure);
  if (window == 0)  return 0;

  iface0.mete_box = configure;
  iface0.window = window;
  return window;
}

int
main(int argc, char *argv[]) {

  xcb_window_t window;

    /* window size and position */
  PhxRectangle configure = { 800, 200, 300, 200 };
    /* A 'topmost' decorated window */
  window = ui_window_create(configure);
  if (window == 0)  exit(EXIT_FAILURE);

  ui_window_name(window);

    /* Map the window on the screen */
  xcb_map_window(connection, window);

    /* Run event loop */
  xcb_main();

  return EXIT_SUCCESS;
}

Reply via email to