From 4cd03b40ca5790e31f1cdacc2fa13c906bacf86c Mon Sep 17 00:00:00 2001 From: Yasin Karaaslan Date: Wed, 4 Jun 2025 01:57:38 +0300 Subject: [PATCH 1/7] Disable server-side decorations on Linux X11 --- src/os/gfx/linux/os_gfx_linux.c | 25 ++++++++++++++++++++++++- src/os/gfx/linux/os_gfx_linux.h | 1 + 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 12a9aa569..cb3c25fe8 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -35,7 +35,8 @@ os_gfx_init(void) os_lnx_gfx_state->wm_delete_window_atom = XInternAtom(os_lnx_gfx_state->display, "WM_DELETE_WINDOW", 0); os_lnx_gfx_state->wm_sync_request_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST", 0); os_lnx_gfx_state->wm_sync_request_counter_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST_COUNTER", 0); - + os_lnx_gfx_state->wm_motif_hints_atom = XInternAtom(os_lnx_gfx_state->display, "_MOTIF_WM_HINTS", 0); + //- rjf: open im os_lnx_gfx_state->xim = XOpenIM(os_lnx_gfx_state->display, 0, 0, 0); @@ -95,12 +96,27 @@ os_get_clipboard_text(Arena *arena) return result; } + +//////////////////////////////// +// Motif hints structure and constants for managing decorations +typedef struct { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; +} MotifWmHints; + +#define MWM_HINTS_DECORATIONS (1L << 1) + //////////////////////////////// //~ rjf: @os_hooks Windows (Implemented Per-OS) internal OS_Handle os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) { + B32 custom_border = !!(flags & OS_WindowFlag_CustomBorder); + Vec2F32 resolution = dim_2f32(rect); //- rjf: allocate window @@ -147,6 +163,13 @@ os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) } XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1); + if (custom_border) { + MotifWmHints hints; + hints.flags = MWM_HINTS_DECORATIONS; + hints.decorations = 0; // 0 means no decorations + XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_motif_hints_atom, os_lnx_gfx_state->wm_motif_hints_atom, 32, PropModeReplace, (U8 *)&hints, 5); + } + //- rjf: create xic w->xic = XCreateIC(os_lnx_gfx_state->xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 58e51f27b..2db429479 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -43,6 +43,7 @@ struct OS_LNX_GfxState Atom wm_delete_window_atom; Atom wm_sync_request_atom; Atom wm_sync_request_counter_atom; + Atom wm_motif_hints_atom; Cursor cursors[OS_Cursor_COUNT]; OS_Cursor last_set_cursor; OS_GfxInfo gfx_info; From fa27196ba5ed9099b8a582df21cfa0f65832ee37 Mon Sep 17 00:00:00 2001 From: Yasin Karaaslan Date: Wed, 4 Jun 2025 04:29:43 +0300 Subject: [PATCH 2/7] X11: Minimize and maximize https://specifications.freedesktop.org/wm-spec/latest/ar01s05.html --- src/os/gfx/linux/os_gfx_linux.c | 96 +++++++++++++++++++++++++-------- src/os/gfx/linux/os_gfx_linux.h | 5 ++ 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index cb3c25fe8..ce99f2c7c 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -19,6 +19,47 @@ os_lnx_window_from_x11window(Window window) return result; } +internal B32 +os_lnx_check_x11window_atoms(Window window, Atom atoms[], U64 num_atoms) +{ + Atom actual_type, *props; + int actual_format; + unsigned long nitems, bytes_after; + int status = XGetWindowProperty(os_lnx_gfx_state->display, window, os_lnx_gfx_state->wm_state_atom, 0, 32, 0, XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, (U8**)&props); + if (status != Success || actual_type != XA_ATOM) + { + if (props) XFree(props); + return 0; // couldn't read or not an atom list + } + S32 num_found = 0; + if (props) + { + for (U64 i = 0; i < nitems; i++) + { + for (U64 j = 0; j < num_atoms; j++) + { + if (props[i] == atoms[j]) + { + num_found++; + } + } + } + XFree(props); + } + return num_found == num_atoms; +} + +// Motif hints structure and constants for managing decorations +typedef struct { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; +} MotifWmHints; + +#define MWM_HINTS_DECORATIONS (1L << 1) + //////////////////////////////// //~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) @@ -35,8 +76,12 @@ os_gfx_init(void) os_lnx_gfx_state->wm_delete_window_atom = XInternAtom(os_lnx_gfx_state->display, "WM_DELETE_WINDOW", 0); os_lnx_gfx_state->wm_sync_request_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST", 0); os_lnx_gfx_state->wm_sync_request_counter_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST_COUNTER", 0); + os_lnx_gfx_state->wm_move_resize_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_MOVERESIZE", 0); + os_lnx_gfx_state->wm_state_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE", 0); + os_lnx_gfx_state->wm_state_hidden_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_HIDDEN", 0); + os_lnx_gfx_state->wm_state_maximized_horz_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0); + os_lnx_gfx_state->wm_state_maximized_vert_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_MAXIMIZED_VERT", 0); os_lnx_gfx_state->wm_motif_hints_atom = XInternAtom(os_lnx_gfx_state->display, "_MOTIF_WM_HINTS", 0); - //- rjf: open im os_lnx_gfx_state->xim = XOpenIM(os_lnx_gfx_state->display, 0, 0, 0); @@ -97,26 +142,12 @@ os_get_clipboard_text(Arena *arena) } -//////////////////////////////// -// Motif hints structure and constants for managing decorations -typedef struct { - unsigned long flags; - unsigned long functions; - unsigned long decorations; - long inputMode; - unsigned long status; -} MotifWmHints; - -#define MWM_HINTS_DECORATIONS (1L << 1) - //////////////////////////////// //~ rjf: @os_hooks Windows (Implemented Per-OS) internal OS_Handle os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) { - B32 custom_border = !!(flags & OS_WindowFlag_CustomBorder); - Vec2F32 resolution = dim_2f32(rect); //- rjf: allocate window @@ -163,7 +194,7 @@ os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) } XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1); - if (custom_border) { + if (flags & OS_WindowFlag_CustomBorder) { MotifWmHints hints; hints.flags = MWM_HINTS_DECORATIONS; hints.decorations = 0; // 0 means no decorations @@ -254,30 +285,51 @@ internal B32 os_window_is_maximized(OS_Handle handle) { if(os_handle_match(handle, os_handle_zero())) {return 0;} - // TODO(rjf) - return 0; + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + Atom properties[] = {os_lnx_gfx_state->wm_state_maximized_horz_atom, os_lnx_gfx_state->wm_state_maximized_vert_atom}; + return os_lnx_check_x11window_atoms(w->window, properties, ArrayCount(properties)); } internal void os_window_set_maximized(OS_Handle handle, B32 maximized) { if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + XEvent evt = {0}; + evt.type = ClientMessage; + evt.xclient.window = w->window; + evt.xclient.message_type = os_lnx_gfx_state->wm_state_atom; + evt.xclient.format = 32; + evt.xclient.data.l[0] = (long) maximized; // 0: _NET_WM_STATE_REMOVE, 1: _NET_WM_STATE_ADD + evt.xclient.data.l[1] = os_lnx_gfx_state->wm_state_maximized_horz_atom; + evt.xclient.data.l[2] = os_lnx_gfx_state->wm_state_maximized_vert_atom; + evt.xclient.data.l[3] = 1; + XSendEvent(os_lnx_gfx_state->display, XDefaultRootWindow(os_lnx_gfx_state->display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &evt); } internal B32 os_window_is_minimized(OS_Handle handle) { if(os_handle_match(handle, os_handle_zero())) {return 0;} - // TODO(rjf) - return 0; + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + return os_lnx_check_x11window_atoms(w->window, &os_lnx_gfx_state->wm_state_hidden_atom, 1);; } internal void os_window_set_minimized(OS_Handle handle, B32 minimized) { if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + if (minimized) + { + XIconifyWindow(os_lnx_gfx_state->display, w->window, XDefaultScreen(os_lnx_gfx_state->display)); + } + else + { + // Sending _NET_WM_STATE_HIDDEN as a client does not work. + // So we can't 'unminimize'. + // Maybe use bring_to_front? + } } internal void diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 2db429479..5dd9f5d77 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -44,6 +44,10 @@ struct OS_LNX_GfxState Atom wm_sync_request_atom; Atom wm_sync_request_counter_atom; Atom wm_motif_hints_atom; + Atom wm_move_resize_atom; + Atom wm_state_atom; + Atom wm_state_hidden_atom; + Atom wm_state_maximized_vert_atom, wm_state_maximized_horz_atom; Cursor cursors[OS_Cursor_COUNT]; OS_Cursor last_set_cursor; OS_GfxInfo gfx_info; @@ -58,5 +62,6 @@ global OS_LNX_GfxState *os_lnx_gfx_state = 0; //~ rjf: Helpers internal OS_LNX_Window *os_lnx_window_from_x11window(Window window); +internal B32 os_lnx_check_x11window_atoms(Window window, Atom properties[], U64 num_properties); #endif // OS_GFX_LINUX_H From 22d719a6ec28c9cc617630a1052d6f7b5295e54a Mon Sep 17 00:00:00 2001 From: Yasin Karaaslan Date: Wed, 4 Jun 2025 06:15:39 +0300 Subject: [PATCH 3/7] X11: Custom resize/move --- src/os/gfx/linux/os_gfx_linux.c | 106 +++++++++++++++++++++++++++----- src/os/gfx/linux/os_gfx_linux.h | 3 + src/os/gfx/os_gfx.h | 2 + src/os/gfx/win32/os_gfx_win32.c | 2 + 4 files changed, 99 insertions(+), 14 deletions(-) diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index ce99f2c7c..91c09b858 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -25,25 +25,19 @@ os_lnx_check_x11window_atoms(Window window, Atom atoms[], U64 num_atoms) Atom actual_type, *props; int actual_format; unsigned long nitems, bytes_after; - int status = XGetWindowProperty(os_lnx_gfx_state->display, window, os_lnx_gfx_state->wm_state_atom, 0, 32, 0, XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, (U8**)&props); + int status = XGetWindowProperty(os_lnx_gfx_state->display, window, os_lnx_gfx_state->wm_state_atom, 0, (~0L), 0, XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, (U8**)&props); if (status != Success || actual_type != XA_ATOM) { if (props) XFree(props); return 0; // couldn't read or not an atom list } - S32 num_found = 0; + U64 num_found = 0; if (props) { for (U64 i = 0; i < nitems; i++) - { for (U64 j = 0; j < num_atoms; j++) - { - if (props[i] == atoms[j]) - { - num_found++; - } - } - } + if (props[i] == atoms[j]) num_found++; + XFree(props); } return num_found == num_atoms; @@ -105,6 +99,8 @@ os_gfx_init(void) {OS_Cursor_UpDown, XC_sb_v_double_arrow}, {OS_Cursor_DownRight, XC_bottom_right_corner}, {OS_Cursor_UpRight, XC_top_right_corner}, + {OS_Cursor_DownLeft, XC_bottom_left_corner}, + {OS_Cursor_UpLeft, XC_top_left_corner}, {OS_Cursor_UpDownLeftRight, XC_fleur}, {OS_Cursor_HandPoint, XC_hand1}, {OS_Cursor_Disabled, XC_X_cursor}, @@ -194,10 +190,12 @@ os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) } XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1); - if (flags & OS_WindowFlag_CustomBorder) { + if (flags & OS_WindowFlag_CustomBorder) + { + w->custom_border = 1; MotifWmHints hints; hints.flags = MWM_HINTS_DECORATIONS; - hints.decorations = 0; // 0 means no decorations + hints.decorations = 0; // no decorations XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_motif_hints_atom, os_lnx_gfx_state->wm_motif_hints_atom, 32, PropModeReplace, (U8 *)&hints, 5); } @@ -357,6 +355,8 @@ internal void os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) { if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + w->custom_border_title_thickness = thickness; // TODO(rjf) } @@ -364,6 +364,8 @@ internal void os_window_push_custom_edges(OS_Handle handle, F32 thickness) { if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + w->custom_border_edge_thickness = thickness; // TODO(rjf) } @@ -611,6 +613,54 @@ os_get_events(Arena *arena, B32 wait) // rjf: push event OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xbutton.window); + + if (window->custom_border && evt.type == ButtonPress) + { + F32 x = (F32)evt.xbutton.x; + F32 y = (F32)evt.xbutton.y; + OS_Handle handle = {(U64)window}; + Rng2F32 rect = os_client_rect_from_window(handle); + F32 win_width = rect.x1 - rect.x0; + F32 win_height = rect.y1 - rect.y0; + F32 edge_size = window->custom_border_edge_thickness; + int detail = -1; + if (x < edge_size && y < edge_size) + detail = 0; // top-left + else if (y < edge_size && x >= (rect.x1 - edge_size)) + detail = 2; // top-right + else if (x < edge_size && y >= (rect.y1 - edge_size)) + detail = 6; // bottom-left + else if (y >= (rect.y1 - edge_size) && x >= (rect.x1 - edge_size)) + detail = 4; // bottom-right + else if (y < edge_size) + detail = 1; // top + else if (x >= (rect.x1 - edge_size)) + detail = 2; // right + else if (y >= (rect.y1 - edge_size)) + detail = 5; // bottom + else if (x < edge_size) + detail = 7; // left + else + if (y <= window->custom_border_title_thickness) detail = 8; // move + + + if (detail != -1) { + XEvent send_evt = {0}; + send_evt.xclient.type = ClientMessage; + send_evt.xclient.window = window->window; + send_evt.xclient.message_type = os_lnx_gfx_state->wm_move_resize_atom; + send_evt.xclient.format = 32; + + send_evt.xclient.data.l[0] = evt.xbutton.x_root; + send_evt.xclient.data.l[1] = evt.xbutton.y_root; + send_evt.xclient.data.l[2] = detail; + send_evt.xclient.data.l[3] = evt.xbutton.button; + send_evt.xclient.data.l[4] = 1; // source indication (1 = normal application) + + XSendEvent(os_lnx_gfx_state->display, XDefaultRootWindow(os_lnx_gfx_state->display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &send_evt); + // At this point, the WM will take over pointer‐grabbing until ButtonRelease. + } + } if(key != OS_Key_Null) { OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == ButtonPress ? OS_EventKind_Press : OS_EventKind_Release); @@ -634,10 +684,38 @@ os_get_events(Arena *arena, B32 wait) case MotionNotify: { OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); + + // cutnpaste from above + F32 x = (F32)evt.xmotion.x; + F32 y = (F32)evt.xmotion.y; + if (window->custom_border) + { + OS_Handle handle = {(U64)window}; + Rng2F32 rect = os_client_rect_from_window(handle); + F32 edge_size = window->custom_border_edge_thickness; + B32 on_border_x = (x <= window->custom_border_edge_thickness || rect.x1-window->custom_border_edge_thickness <= x); + B32 on_border_y = (y <= window->custom_border_edge_thickness || rect.y1-window->custom_border_edge_thickness <= y); + OS_Cursor cursor = OS_Cursor_Pointer; + if (x < edge_size && y < edge_size) + cursor = OS_Cursor_UpLeft; + else if (y < edge_size && x >= (rect.x1 - edge_size)) + cursor = OS_Cursor_UpRight; + else if (x < edge_size && y >= (rect.y1 - edge_size)) + cursor = OS_Cursor_DownLeft; + else if (y >= (rect.y1 - edge_size) && x >= (rect.x1 - edge_size)) + cursor = OS_Cursor_DownRight; + else if (y < edge_size || y >= (rect.y1 - edge_size)) + cursor = OS_Cursor_UpDown; + else if (x < edge_size || x >= (rect.x1 - edge_size)) + cursor = OS_Cursor_LeftRight; + + os_set_cursor(cursor); + } + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_MouseMove); e->window.u64[0] = (U64)window; - e->pos.x = (F32)evt.xmotion.x; - e->pos.y = (F32)evt.xmotion.y; + e->pos.x = x; + e->pos.y = y; set_mouse_cursor = 1; }break; diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 5dd9f5d77..09431864b 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -26,6 +26,9 @@ struct OS_LNX_Window XIC xic; XID counter_xid; U64 counter_value; + B32 custom_border; + F32 custom_border_title_thickness; + F32 custom_border_edge_thickness; }; //////////////////////////////// diff --git a/src/os/gfx/os_gfx.h b/src/os/gfx/os_gfx.h index d56a71ca0..a7c2ee34d 100644 --- a/src/os/gfx/os_gfx.h +++ b/src/os/gfx/os_gfx.h @@ -36,6 +36,8 @@ typedef enum OS_Cursor OS_Cursor_UpDown, OS_Cursor_DownRight, OS_Cursor_UpRight, + OS_Cursor_DownLeft, + OS_Cursor_UpLeft, OS_Cursor_UpDownLeftRight, OS_Cursor_HandPoint, OS_Cursor_Disabled, diff --git a/src/os/gfx/win32/os_gfx_win32.c b/src/os/gfx/win32/os_gfx_win32.c index 93ad7dcf4..5a2b20c1a 100644 --- a/src/os/gfx/win32/os_gfx_win32.c +++ b/src/os/gfx/win32/os_gfx_win32.c @@ -1540,6 +1540,8 @@ X(LeftRight, IDC_SIZEWE) \ X(UpDown, IDC_SIZENS) \ X(DownRight, IDC_SIZENWSE) \ X(UpRight, IDC_SIZENESW) \ +X(DownLeft, IDC_SIZENESW) \ +X(UpLeft, IDC_SIZENWSE) \ X(UpDownLeftRight, IDC_SIZEALL) \ X(HandPoint, IDC_HAND)\ X(Disabled, IDC_NO) From 805f1725fbdfa20d52b20a10b193313fd857bd6c Mon Sep 17 00:00:00 2001 From: Yasin Karaaslan Date: Wed, 4 Jun 2025 06:25:01 +0300 Subject: [PATCH 4/7] X11: Refactoring --- src/os/gfx/linux/os_gfx_linux.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 91c09b858..b418dd170 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -43,16 +43,6 @@ os_lnx_check_x11window_atoms(Window window, Atom atoms[], U64 num_atoms) return num_found == num_atoms; } -// Motif hints structure and constants for managing decorations -typedef struct { - unsigned long flags; - unsigned long functions; - unsigned long decorations; - long inputMode; - unsigned long status; -} MotifWmHints; - -#define MWM_HINTS_DECORATIONS (1L << 1) //////////////////////////////// //~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) @@ -193,8 +183,15 @@ os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) if (flags & OS_WindowFlag_CustomBorder) { w->custom_border = 1; - MotifWmHints hints; - hints.flags = MWM_HINTS_DECORATIONS; + + struct { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; + } hints; // MotifWmHints + hints.flags = 3; // MWM_HINTS_DECORATIONS hints.decorations = 0; // no decorations XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_motif_hints_atom, os_lnx_gfx_state->wm_motif_hints_atom, 32, PropModeReplace, (U8 *)&hints, 5); } From 00d5240e8bb9f0807c9af5dd1696225770ac3257 Mon Sep 17 00:00:00 2001 From: Yasin Karaaslan Date: Wed, 4 Jun 2025 06:39:22 +0300 Subject: [PATCH 5/7] X11: Fullscreen --- src/os/gfx/linux/os_gfx_linux.c | 29 ++++++++++++++++++++--------- src/os/gfx/linux/os_gfx_linux.h | 1 + 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index b418dd170..7494ef48d 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -20,7 +20,7 @@ os_lnx_window_from_x11window(Window window) } internal B32 -os_lnx_check_x11window_atoms(Window window, Atom atoms[], U64 num_atoms) +os_lnx_check_x11window_states(Window window, Atom states[], U64 num_states) { Atom actual_type, *props; int actual_format; @@ -35,12 +35,12 @@ os_lnx_check_x11window_atoms(Window window, Atom atoms[], U64 num_atoms) if (props) { for (U64 i = 0; i < nitems; i++) - for (U64 j = 0; j < num_atoms; j++) - if (props[i] == atoms[j]) num_found++; + for (U64 j = 0; j < num_states; j++) + if (props[i] == states[j]) num_found++; XFree(props); } - return num_found == num_atoms; + return num_found == num_states; } @@ -65,6 +65,7 @@ os_gfx_init(void) os_lnx_gfx_state->wm_state_hidden_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_HIDDEN", 0); os_lnx_gfx_state->wm_state_maximized_horz_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0); os_lnx_gfx_state->wm_state_maximized_vert_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_MAXIMIZED_VERT", 0); + os_lnx_gfx_state->wm_state_fullscreen_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_FULLSCREEN", 0); os_lnx_gfx_state->wm_motif_hints_atom = XInternAtom(os_lnx_gfx_state->display, "_MOTIF_WM_HINTS", 0); //- rjf: open im os_lnx_gfx_state->xim = XOpenIM(os_lnx_gfx_state->display, 0, 0, 0); @@ -265,15 +266,25 @@ internal B32 os_window_is_fullscreen(OS_Handle handle) { if(os_handle_match(handle, os_handle_zero())) {return 0;} - // TODO(rjf) - return 0; + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + return os_lnx_check_x11window_states(w->window, &os_lnx_gfx_state->wm_state_fullscreen_atom, 1); } internal void os_window_set_fullscreen(OS_Handle handle, B32 fullscreen) { if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + XEvent evt = {0}; + evt.type = ClientMessage; + evt.xclient.window = w->window; + evt.xclient.message_type = os_lnx_gfx_state->wm_state_atom; + evt.xclient.format = 32; + evt.xclient.data.l[0] = (long) fullscreen; + evt.xclient.data.l[1] = os_lnx_gfx_state->wm_state_fullscreen_atom; + evt.xclient.data.l[2] = 0; + evt.xclient.data.l[3] = 1; + XSendEvent(os_lnx_gfx_state->display, XDefaultRootWindow(os_lnx_gfx_state->display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &evt); } internal B32 @@ -282,7 +293,7 @@ os_window_is_maximized(OS_Handle handle) if(os_handle_match(handle, os_handle_zero())) {return 0;} OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; Atom properties[] = {os_lnx_gfx_state->wm_state_maximized_horz_atom, os_lnx_gfx_state->wm_state_maximized_vert_atom}; - return os_lnx_check_x11window_atoms(w->window, properties, ArrayCount(properties)); + return os_lnx_check_x11window_states(w->window, properties, ArrayCount(properties)); } internal void @@ -307,7 +318,7 @@ os_window_is_minimized(OS_Handle handle) { if(os_handle_match(handle, os_handle_zero())) {return 0;} OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - return os_lnx_check_x11window_atoms(w->window, &os_lnx_gfx_state->wm_state_hidden_atom, 1);; + return os_lnx_check_x11window_states(w->window, &os_lnx_gfx_state->wm_state_hidden_atom, 1);; } internal void diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 09431864b..bab7fa27a 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -51,6 +51,7 @@ struct OS_LNX_GfxState Atom wm_state_atom; Atom wm_state_hidden_atom; Atom wm_state_maximized_vert_atom, wm_state_maximized_horz_atom; + Atom wm_state_fullscreen_atom; Cursor cursors[OS_Cursor_COUNT]; OS_Cursor last_set_cursor; OS_GfxInfo gfx_info; From 1858955335e808fbef2d0636f84753542c875404 Mon Sep 17 00:00:00 2001 From: Yasin Karaaslan Date: Wed, 4 Jun 2025 06:46:04 +0300 Subject: [PATCH 6/7] Oversight --- src/os/gfx/linux/os_gfx_linux.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index bab7fa27a..a1a187310 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -66,6 +66,6 @@ global OS_LNX_GfxState *os_lnx_gfx_state = 0; //~ rjf: Helpers internal OS_LNX_Window *os_lnx_window_from_x11window(Window window); -internal B32 os_lnx_check_x11window_atoms(Window window, Atom properties[], U64 num_properties); +internal B32 os_lnx_check_x11window_states(Window window, Atom properties[], U64 num_properties); #endif // OS_GFX_LINUX_H From 16e8bdc200faa51fd05aff0f45a686f68cbd71ef Mon Sep 17 00:00:00 2001 From: Yasin Karaaslan Date: Wed, 4 Jun 2025 06:54:50 +0300 Subject: [PATCH 7/7] X11: Pixel perfect resize/move --- src/os/gfx/linux/os_gfx_linux.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 7494ef48d..5d1cc3735 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -628,25 +628,23 @@ os_get_events(Arena *arena, B32 wait) F32 y = (F32)evt.xbutton.y; OS_Handle handle = {(U64)window}; Rng2F32 rect = os_client_rect_from_window(handle); - F32 win_width = rect.x1 - rect.x0; - F32 win_height = rect.y1 - rect.y0; F32 edge_size = window->custom_border_edge_thickness; int detail = -1; - if (x < edge_size && y < edge_size) + if (x <= edge_size && y <= edge_size) detail = 0; // top-left - else if (y < edge_size && x >= (rect.x1 - edge_size)) + else if (y <= edge_size && x >= (rect.x1 - edge_size)) detail = 2; // top-right - else if (x < edge_size && y >= (rect.y1 - edge_size)) + else if (x <= edge_size && y >= (rect.y1 - edge_size)) detail = 6; // bottom-left else if (y >= (rect.y1 - edge_size) && x >= (rect.x1 - edge_size)) detail = 4; // bottom-right - else if (y < edge_size) + else if (y <= edge_size) detail = 1; // top else if (x >= (rect.x1 - edge_size)) detail = 2; // right else if (y >= (rect.y1 - edge_size)) detail = 5; // bottom - else if (x < edge_size) + else if (x <= edge_size) detail = 7; // left else if (y <= window->custom_border_title_thickness) detail = 8; // move @@ -701,20 +699,18 @@ os_get_events(Arena *arena, B32 wait) OS_Handle handle = {(U64)window}; Rng2F32 rect = os_client_rect_from_window(handle); F32 edge_size = window->custom_border_edge_thickness; - B32 on_border_x = (x <= window->custom_border_edge_thickness || rect.x1-window->custom_border_edge_thickness <= x); - B32 on_border_y = (y <= window->custom_border_edge_thickness || rect.y1-window->custom_border_edge_thickness <= y); OS_Cursor cursor = OS_Cursor_Pointer; - if (x < edge_size && y < edge_size) + if (x <= edge_size && y <= edge_size) cursor = OS_Cursor_UpLeft; - else if (y < edge_size && x >= (rect.x1 - edge_size)) + else if (y <= edge_size && x >= (rect.x1 - edge_size)) cursor = OS_Cursor_UpRight; - else if (x < edge_size && y >= (rect.y1 - edge_size)) + else if (x <= edge_size && y >= (rect.y1 - edge_size)) cursor = OS_Cursor_DownLeft; else if (y >= (rect.y1 - edge_size) && x >= (rect.x1 - edge_size)) cursor = OS_Cursor_DownRight; - else if (y < edge_size || y >= (rect.y1 - edge_size)) + else if (y <= edge_size || y >= (rect.y1 - edge_size)) cursor = OS_Cursor_UpDown; - else if (x < edge_size || x >= (rect.x1 - edge_size)) + else if (x <= edge_size || x >= (rect.x1 - edge_size)) cursor = OS_Cursor_LeftRight; os_set_cursor(cursor);