port from xcb to x11

This commit is contained in:
Matthew 2025-11-30 16:00:25 +11:00
parent 2ba1ae480c
commit a221c1d5ce
3 changed files with 286 additions and 187 deletions

View File

@ -1,20 +1,22 @@
//#pragma attribute(push, nogc, nothrow)
#ifdef __linux__
# include <xcb/xcb.h>
# include <xcb/xfixes.h>
# define XLIB_ILLEGAL_ACCESS
# include <X11/Xlib.h>
# include <X11/XKBlib.h>
# include <X11/Xlib-xcb.h>
# include <X11/Xlib.h>
# include <X11/keysym.h>
# include <X11/extensions/Xfixes.h>
# include <X11/Xatom.h>
# include <X11/Xutil.h>
# include <ft2build.h>
# include <GL/glx.h>
# include <GL/glxext.h>
# include FT_FREETYPE_H
# include FT_GLYPH_H
#endif
#include <xmmintrin.h>
#include "external/stb/stb_image.h"
#include "external/stb/stb_image_write.h"

View File

@ -410,8 +410,8 @@ struct Selection
{
bool owned;
u8[] data;
xcb_atom_t target;
xcb_atom_t xmode;
Atom target;
Atom xmode;
}
enum Atoms
@ -429,15 +429,15 @@ enum Atoms
}
const char[][] ATOM_STRS = [
CastStr!(char)("TARGETS"),
CastStr!(char)("MULTIPLE"),
CastStr!(char)("TIMESTAMP"),
CastStr!(char)("INCR"),
CastStr!(char)("CLIPBOARD"),
CastStr!(char)("UTF8_STRING"),
CastStr!(char)("WM_PROTOCOLS"),
CastStr!(char)("WM_DELETE_WINDOW"),
CastStr!(char)("_NET_WM_STATE_HIDDEN"),
Atoms.Targets: CastStr!(char)("TARGETS"),
Atoms.Multiple: CastStr!(char)("MULTIPLE"),
Atoms.Timestamp: CastStr!(char)("TIMESTAMP"),
Atoms.Incr: CastStr!(char)("INCR"),
Atoms.Clipboard: CastStr!(char)("CLIPBOARD"),
Atoms.Utf8String: CastStr!(char)("UTF8_STRING"),
Atoms.WMProtocols: CastStr!(char)("WM_PROTOCOLS"),
Atoms.DeleteWindow: CastStr!(char)("WM_DELETE_WINDOW"),
Atoms.StateHidden: CastStr!(char)("_NET_WM_STATE_HIDDEN"),
];
alias PThreadProc = extern (C) void* function(void*);
@ -579,13 +579,16 @@ PushMotion(Inputs* inputs, i32 rel_x, i32 rel_y, i32 x, i32 y)
struct PlatformWindow
{
xcb_atom_t[Atoms.max] atoms;
Atom[Atoms.max] atoms;
Display* display;
xcb_connection_t* conn;
xcb_screen_t* screen;
xcb_window_t window;
u16 w;
u16 h;
Window window;
Window root_window;
i32 screen_id;
// xcb_connection_t* conn;
// xcb_screen_t* screen;
// xcb_window_t window;
u32 w;
u32 h;
i32 mouse_prev_x;
i32 mouse_prev_y;
bool locked_cursor;
@ -614,25 +617,18 @@ struct Function
void* ptr;
};
pragma(inline) bool
NilErr(PlatformWindow* window, xcb_void_cookie_t* cookie, xcb_generic_error_t* err)
__gshared string WINDOW_ERR_MSG = null;
string
WindowError()
{
bool result = err == null;
pureFree(err);
return result;
return WINDOW_ERR_MSG;
}
pragma(inline) void
CheckErr(PlatformWindow *window, xcb_void_cookie_t *cookie, xcb_generic_error_t *err, string msg)
bool
CreateWindow(PlatformWindow* window, string name, u32 width, u32 height, XVisualInfo* visual_info = null)
{
assert(err == null, msg);
pureFree(err);
};
PlatformWindow
CreateWindow(string name, u16 width, u16 height)
{
PlatformWindow window = {
PlatformWindow wnd = {
w: width,
h: height,
input_mutex: CreateTicketMut(),
@ -645,16 +641,116 @@ CreateWindow(string name, u16 width, u16 height)
],
};
version(linux)
{
window.cb_transfer_size = X11_CB_TRANSFER_SIZE_DEFAULT;
}
*window = wnd;
assert(width > 0 && height > 0, "CreateWindow error: width and height must be above 0");
window.cb_transfer_size = X11_CB_TRANSFER_SIZE_DEFAULT;
window.display = XOpenDisplay(null);
assert(window.display != null, "XOpenDisplay failure");
if(!window.display)
{
WINDOW_ERR_MSG = "Unable to open X11 display";
return false;
}
window.root_window = DefaultRootWindow(window.display);
if(window.root_window == None)
{
WINDOW_ERR_MSG = "Unable to retrieve X11 root window";
return false;
}
window.screen_id = XDefaultScreen(window.display);
XSetWindowAttributes attrs = {
background_pixmap: None,
background_pixel: XBlackPixel(window.display, window.screen_id),
};
i64 value_mask = CWBackPixmap | CWBackPixel;
if(visual_info)
{
attrs.colormap = XCreateColormap(window.display, window.root_window, visual_info.visual, AllocNone);
value_mask |= CWColormap;
}
auto copy = CopyFromParent;
window.window = XCreateWindow(
window.display,
window.root_window,
0,
0,
width,
height,
0,
(visual_info ? visual_info.depth : cast(i32)copy),
InputOutput,
(visual_info ? visual_info.visual : cast(Visual*)&copy),
value_mask,
&attrs
);
if(window.window == None)
{
WINDOW_ERR_MSG = "Failed to create X11 window";
return false;
}
if(visual_info)
{
XFree(visual_info);
}
i64 event_mask = KeyPressMask |
KeyReleaseMask |
ExposureMask |
ButtonPressMask |
ButtonReleaseMask |
PointerMotionMask |
StructureNotifyMask |
PropertyChangeMask;
XSelectInput(window.display, window.window, event_mask);
foreach(atom; Atoms.min .. Atoms.max)
{
window.atoms[atom] = XInternAtom(window.display, ATOM_STRS[atom].ptr, false);
}
XSetWMProtocols(window.display, window.window, window.atoms.ptr, window.atoms.length);
XChangeProperty(
window.display,
window.window,
XA_WM_NAME,
XA_STRING,
8,
PropModeReplace,
cast(const(u8)*)name.ptr,
cast(u32)name.length
);
XStoreName(window.display, window.window, name.ptr);
XChangeProperty(
window.display,
window.window,
window.atoms[Atoms.WMProtocols],
XA_ATOM,
32,
PropModeReplace,
cast(const(u8)*)&window.atoms[Atoms.DeleteWindow],
1
);
int major, minor;
XFixesQueryVersion(window.display, &major, &minor);
XMapWindow(window.display, window.window);
XFlush(window.display);
/*
window.conn = XGetXCBConnection(window.display);
assert(window.conn != null, "XGetXCBConnection failure");
@ -753,8 +849,9 @@ CreateWindow(string name, u16 width, u16 height)
assert(stream_result > 0, "xcb_flush failure");
xcb_xfixes_query_version(window.conn, 4, 0);
*/
return window;
return true;
};
void
@ -773,17 +870,9 @@ LockCursor(PlatformWindow* window)
u32 counter = 0;
for(;;)
{
xcb_generic_error_t *error;
xcb_grab_pointer_cookie_t grab_cookie = xcb_grab_pointer(window.conn, true, window.window, 0, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, window.window, XCB_NONE, XCB_CURRENT_TIME);
xcb_grab_pointer_reply_t* grab_reply = xcb_grab_pointer_reply(window.conn, grab_cookie, &error);
i32 grab_res = XGrabPointer(window.display, window.window, true, 0, GrabModeAsync, GrabModeAsync, window.window, None, CurrentTime);
scope(exit)
{
pureFree(error);
pureFree(grab_reply);
}
if(grab_reply.status == XCB_GRAB_STATUS_SUCCESS)
if(grab_res == None)
{
result = true;
break;
@ -810,7 +899,7 @@ UnlockCursor(PlatformWindow* window)
{
if(window.locked_cursor)
{
xcb_ungrab_pointer(window.conn, XCB_CURRENT_TIME);
XUngrabPointer(window.display, CurrentTime);
ShowCursor(window);
window.locked_cursor = false;
}
@ -819,71 +908,78 @@ UnlockCursor(PlatformWindow* window)
bool
HideCursor(PlatformWindow* window)
{
xcb_void_cookie_t hide_cursor_cookie = xcb_xfixes_hide_cursor_checked(window.conn, window.window);
xcb_generic_error_t *error = xcb_request_check(window.conn, hide_cursor_cookie);
return NilErr(window, &hide_cursor_cookie, error);
XFixesHideCursor(window.display, window.window);
return true;
}
bool
ShowCursor(PlatformWindow* window)
{
xcb_void_cookie_t show_cursor_cookie = xcb_xfixes_show_cursor_checked(window.conn, window.window);
xcb_generic_error_t *error = xcb_request_check(window.conn, show_cursor_cookie);
return NilErr(window, &show_cursor_cookie, error);
XFixesShowCursor(window.display, window.window);
return true;
}
void
FlushEvents(PlatformWindow* window)
ClearEvents(PlatformWindow* window)
{
xcb_generic_event_t* e;
do
for(;;)
{
e = xcb_poll_for_event(window.conn);
} while (e);
XEvent ev;
u32 count = XPending(window.display);
for(u32 i = 0; i < count; i += 1)
{
XNextEvent(window.display, &ev);
}
if(count == 0)
{
break;
}
}
}
bool
TransmitSelection(PlatformWindow* w, xcb_selection_request_event_t* ev)
TransmitSelection(PlatformWindow* w, XSelectionRequestEvent* ev)
{
bool result;
if(ev.property == XCB_NONE)
if(ev.property == None)
{
ev.property = ev.target;
}
if(ev.target == w.atoms[Atoms.Targets])
{
xcb_atom_t[3] targets = [
Atom[3] targets = [
w.atoms[Atoms.Timestamp],
w.atoms[Atoms.Targets],
w.atoms[Atoms.Utf8String]
];
xcb_change_property(
w.conn,
XCB_PROP_MODE_REPLACE,
XChangeProperty(
w.display,
ev.requestor,
ev.property,
XCB_ATOM_ATOM,
xcb_atom_t.sizeof * 8,
targets.length,
targets.ptr
XA_ATOM,
Atom.sizeof * 8,
PropModeReplace,
cast(const(u8)*)targets.ptr,
targets.length
);
result = true;
}
else if(ev.target == w.atoms[Atoms.Timestamp])
{
xcb_timestamp_t cur = XCB_CURRENT_TIME;
xcb_change_property(
w.conn,
XCB_PROP_MODE_REPLACE,
Time cur = CurrentTime;
XChangeProperty(
w.display,
ev.requestor,
ev.property,
XCB_ATOM_INTEGER,
XA_INTEGER,
cur.sizeof * 8,
1,
&cur
PropModeReplace,
cast(const(u8)*)&cur,
1
);
result = true;
@ -904,15 +1000,15 @@ TransmitSelection(PlatformWindow* w, xcb_selection_request_event_t* ev)
if(sel != null && sel.owned && sel.data.length > 0 && sel.target == ev.target)
{
xcb_change_property(
w.conn,
XCB_PROP_MODE_REPLACE,
XChangeProperty(
w.display,
ev.requestor,
ev.property,
ev.target,
8,
cast(u32)sel.data.length,
sel.data.ptr
PropModeReplace,
cast(const(u8)*)sel.data.ptr,
cast(u32)sel.data.length
);
result = true;
@ -952,10 +1048,9 @@ ClipboardText(PlatformWindow* w, ClipboardMode mode)
}
else
{
auto owner = xcb_get_selection_owner_reply(w.conn, xcb_get_selection_owner(w.conn, sel.xmode), null);
scope(exit) pureFree(owner);
Window owner = XGetSelectionOwner(w.display, sel.xmode);
if(owner != null && owner.owner != 0)
if(owner == None)
{
Free(sel.data);
sel.data = [];
@ -965,8 +1060,8 @@ ClipboardText(PlatformWindow* w, ClipboardMode mode)
u64 ticket = w.cb_mut.next_ticket;
sel.target = w.atoms[Atoms.Utf8String];
xcb_convert_selection(w.conn, w.window, sel.xmode, sel.target, sel.xmode, XCB_CURRENT_TIME);
xcb_flush(w.conn);
XConvertSelection(w.display, sel.xmode, sel.target, sel.xmode, w.window, CurrentTime);
XFlush(w.display);
while(ticket == w.cb_mut.next_ticket) {}
@ -1009,8 +1104,8 @@ SetClipboard(PlatformWindow* w, u8[] data, ClipboardMode mode)
sel.owned = true;
sel.target = w.atoms[Atoms.Utf8String];
xcb_set_selection_owner(w.conn, w.window, sel.xmode, XCB_CURRENT_TIME);
xcb_flush(w.conn);
XSetSelectionOwner(w.display, sel.xmode, w.window, CurrentTime);
XFlush(w.display);
}
else
{
@ -1041,7 +1136,7 @@ GetClipboardSelection(PlatformWindow* w, Selection* sel)
}
void
RetrieveSelection(PlatformWindow* w, xcb_selection_notify_event_t* ev)
RetrieveSelection(PlatformWindow* w, XSelectionEvent* ev)
{
Lock(&w.cb_mut);
scope(exit) Unlock(&w.cb_mut);
@ -1049,41 +1144,32 @@ RetrieveSelection(PlatformWindow* w, xcb_selection_notify_event_t* ev)
u8[] buf;
u64 buf_size;
u64 bytes_after = 1;
xcb_get_property_reply_t* reply;
xcb_atom_t actual_type;
u8 actual_format;
Atom actual_type;
i32 actual_format;
u64 nitems;
u8* prop_data;
if(ev.property == XCB_ATOM_PRIMARY || ev.property == XCB_ATOM_SECONDARY || ev.property == w.atoms[Atoms.Clipboard])
if(ev.property == XA_PRIMARY || ev.property == XA_SECONDARY || ev.property == w.atoms[Atoms.Clipboard])
{
while(bytes_after > 0)
{
scope(exit) pureFree(reply);
scope(exit) XFree(prop_data);
xcb_get_property_cookie_t cookie = xcb_get_property(
w.conn,
true,
i32 get_res = XGetWindowProperty(
w.display,
w.window,
ev.property,
XCB_ATOM_ANY,
cast(u32)(buf_size/4),
cast(u32)(w.cb_transfer_size/4)
cast(u32)(w.cb_transfer_size/4),
true,
AnyPropertyType,
&actual_type,
&actual_format,
&nitems,
&bytes_after,
&prop_data
);
reply = xcb_get_property_reply(w.conn, cookie, null);
if(reply == null || (buf_size > 0 && (reply.format != actual_format || reply.type != actual_type)) || reply.format%8 != 0)
{
Errf("RetrieveSelection failure: Invalid return value from xcb_get_property_reply");
break;
}
if(buf_size == 0)
{
actual_type = reply.type;
actual_format = reply.format;
}
int nitems = xcb_get_property_value_length(reply);
if(nitems > 0)
{
if(buf_size%4 != 0)
@ -1092,14 +1178,13 @@ RetrieveSelection(PlatformWindow* w, xcb_selection_notify_event_t* ev)
break;
}
u64 unit_size = reply.format/8;
buf = Alloc!(u8)(unit_size * (buf_size + nitems));
u64 unit_size = actual_format/8;
u64 data_len = unit_size * nitems;
buf = Realloc!(u8)(buf, data_len + buf_size);
MemCpy(buf.ptr + buf_size, xcb_get_property_value(reply), nitems * unit_size);
buf_size += nitems * unit_size;
MemCpy(buf.ptr + buf_size, prop_data, data_len);
buf_size += data_len;
}
bytes_after = reply.bytes_after;
}
}
@ -1133,9 +1218,9 @@ RetrieveSelection(PlatformWindow* w, xcb_selection_notify_event_t* ev)
}
void
ClearSelection(PlatformWindow* w, xcb_selection_clear_event_t* ev)
ClearSelection(PlatformWindow* w, XSelectionClearEvent* ev)
{
if(ev.owner == w.window)
if(ev.window == w.window)
{
foreach(i; CBM.min .. CBM.max)
{
@ -1163,7 +1248,7 @@ HandleEvents(void* window_ptr)
PlatformWindow* w = cast(PlatformWindow*)window_ptr;
DNode!(SysMessage)* sys_msg = g_NIL_MSG;
xcb_generic_event_t* e;
XEvent e;
bool ignore_mouse_events = false;
@ -1225,34 +1310,33 @@ HandleEvents(void* window_ptr)
}
}
e = xcb_poll_for_event(w.conn);
if(e)
if(XPending(w.display))
{
XNextEvent(w.display, &e);
Inputs* inputs = GetInputs(w);
switch (e.response_type & ~0x80)
switch (e.type)
{
case XCB_CLIENT_MESSAGE:
case ClientMessage:
{
xcb_client_message_event_t* msg = cast(xcb_client_message_event_t*)e;
XClientMessageEvent* msg = &e.xclient;
if(msg.window != w.window)
{
break;
}
if(msg.data.data32[0] == w.atoms[Atoms.DeleteWindow])
if(msg.data.l[0] == w.atoms[Atoms.DeleteWindow])
{
w.close = true;
}
} break;
case XCB_KEY_RELEASE:
case XCB_KEY_PRESS:
case KeyRelease:
case KeyPress:
{
xcb_key_press_event_t* keyboard_event = cast(xcb_key_press_event_t*)e;
XKeyEvent* kb_ev = &e.xkey;
bool pressed = e.response_type == XCB_KEY_PRESS;
xcb_keycode_t code = keyboard_event.detail;
bool pressed = e.type == KeyPress;
u32 code = kb_ev.keycode;
KeySym key_sym = XkbKeycodeToKeysym(w.display, cast(KeyCode)code, 0, 0);
Input input = ConvertInput(key_sym);
@ -1269,37 +1353,38 @@ HandleEvents(void* window_ptr)
if(input != Input.None)
{
Push(inputs, input, keyboard_event.event_x, keyboard_event.event_y, pressed, w.modifier);
Push(inputs, input, kb_ev.x, kb_ev.y, pressed, w.modifier);
}
} break;
case XCB_BUTTON_PRESS:
case XCB_BUTTON_RELEASE:
case ButtonPress:
case ButtonRelease:
{
xcb_button_press_event_t* mouse_event = cast(xcb_button_press_event_t*)e;
bool pressed = e.response_type == XCB_BUTTON_PRESS;
XButtonEvent* mouse_event = &e.xbutton;
bool pressed = e.type == ButtonPress;
Input input = Input.None;
switch (mouse_event.detail)
switch (mouse_event.button)
{
case XCB_BUTTON_INDEX_1: input = Input.LeftClick; break;
case XCB_BUTTON_INDEX_2: input = Input.MiddleClick; break;
case XCB_BUTTON_INDEX_3: input = Input.RightClick; break;
case Button1: input = Input.LeftClick; break;
case Button2: input = Input.MiddleClick; break;
case Button3: input = Input.RightClick; break;
default: break;
}
if(input != Input.None)
{
Push(inputs, input, mouse_event.event_x, mouse_event.event_y, pressed, w.modifier);
Push(inputs, input, mouse_event.x, mouse_event.y, pressed, w.modifier);
}
} break;
case XCB_MOTION_NOTIFY:
case MotionNotify:
{
if(ignore_mouse_events) continue;
xcb_motion_notify_event_t* move_event = cast(xcb_motion_notify_event_t*)e;
XMotionEvent* move_event = &e.xmotion;
i16 x = move_event.event_x;
i16 y = move_event.event_y;
i32 x = move_event.x;
i32 y = move_event.y;
static bool first = true;
if(first)
@ -1320,45 +1405,48 @@ HandleEvents(void* window_ptr)
if(w.locked_cursor &&
(x < WINDOW_EDGE_BUFFER || y < WINDOW_EDGE_BUFFER || x > w.w - WINDOW_EDGE_BUFFER || y > w.h - WINDOW_EDGE_BUFFER))
{
i16 new_x = cast(i16)(w.w / 2);
i16 new_y = cast(i16)(w.h / 2);
xcb_warp_pointer(w.conn, w.window, w.window, 0, 0, cast(i16)w.w, cast(i16)w.h, new_x, new_y);
i32 new_x = cast(i32)(w.w / 2);
i32 new_y = cast(i32)(w.h / 2);
XWarpPointer(w.display, w.window, w.window, 0, 0, cast(i16)w.w, cast(i16)w.h, new_x, new_y);
w.mouse_prev_x = new_x;
w.mouse_prev_y = new_y;
ignore_mouse_events = true;
}
} break;
case XCB_CONFIGURE_NOTIFY:
case ConfigureNotify:
{
xcb_configure_notify_event_t* config_event = cast(xcb_configure_notify_event_t*)e;
XConfigureEvent* config_event = &e.xconfigure;
if(w.w != config_event.width || w.h != config_event.height)
{
w.w = config_event.width;
w.h = config_event.height;
}
} break;
case XCB_SELECTION_CLEAR:
case SelectionClear:
{
ClearSelection(w, cast(xcb_selection_clear_event_t*)e);
ClearSelection(w, &e.xselectionclear);
} break;
case XCB_SELECTION_NOTIFY:
case SelectionNotify:
{
RetrieveSelection(w, cast(xcb_selection_notify_event_t*)e);
RetrieveSelection(w, &e.xselection);
} break;
case XCB_SELECTION_REQUEST:
case SelectionRequest:
{
auto req = cast(xcb_selection_request_event_t*)e;
xcb_selection_notify_event_t notify = {
response_type: XCB_SELECTION_NOTIFY,
time: XCB_CURRENT_TIME,
auto req = &e.xselectionrequest;
XEvent notify = {
xselection: {
type: SelectionNotify,
time: CurrentTime,
display: w.display,
requestor: req.requestor,
selection: req.selection,
target: req.target,
property: TransmitSelection(w, req) ? req.property : XCB_NONE,
property: TransmitSelection(w, req) ? req.property : None,
},
};
xcb_send_event(w.conn, false, req.requestor, XCB_EVENT_MASK_PROPERTY_CHANGE, cast(char*)&notify);
xcb_flush(w.conn);
XSendEvent(w.display, req.requestor, false, PropertyChangeMask, &notify);
XFlush(w.display);
} break;
default: break;
}

9
util.d
View File

@ -33,6 +33,15 @@ Pause()
}
}
void
Assert(T)(T cond, string msg)
{
if(!cond)
{
assert(false, msg);
}
}
string
ConvToStr(T)(T[] arr)
{