Compare commits

..

4 Commits

Author SHA1 Message Date
df2aa925cb Add support for system cursors
When a client creates a cursor from the default cursor font, we try to
detect which cursor it is and create a native system cursor based on
that.
2026-05-30 16:07:43 +03:00
feb194f416 Allow sending enter/leave events between different native windows
This fixes hovering on popups
2026-05-30 16:07:43 +03:00
62fc8c8a14 Add initial support for window types
This makes popup windows work much better
2026-05-30 04:06:13 +03:00
c6d3e55e7b Start work on making xbanan portable
This will allow usage of xbanan on non banan-os platforms. I added a
"native" SDL2 port so it can be used without the window manager
2026-05-30 04:06:13 +03:00
8 changed files with 141 additions and 132 deletions

View File

@@ -374,9 +374,6 @@ void invalidate_window(WINDOW wid, int32_t x, int32_t y, int32_t w, int32_t h)
for (;;)
{
if (wid == g_root.windowId)
return;
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
@@ -509,26 +506,17 @@ transitient_for_done:
static BAN::ErrorOr<void> map_window(Client& client_info, WINDOW wid)
{
static const CARD32 WM_CLASS = g_atoms_name_to_id["WM_CLASS"_sv];
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
if (window.mapped)
return {};
window.mapped = true;
if (window.parent == g_root.windowId)
{
ASSERT(!window.platform_window);
// NOTE: This is not perfect but seems to work.
// This is used to not display "dummy" event windows
// that should not be visible.
if (!window.properties.contains(WM_CLASS))
return {};
auto info = get_plaform_window_info(window);
window.platform_window = TRY(g_platform_ops.create_window(
info.transient_for,
@@ -541,6 +529,8 @@ static BAN::ErrorOr<void> map_window(Client& client_info, WINDOW wid)
));
}
window.mapped = true;
for (auto& pixel : window.pixels)
pixel = window.background;
invalidate_window(wid, 0, 0, window.width, window.height);
@@ -725,7 +715,7 @@ static PlatformWindow* get_platform_window(const Object::Window& window)
{
if (window.platform_window)
return const_cast<PlatformWindow*>(window.platform_window.ptr());
if (window.parent == None)
if (window.parent == g_root.windowId)
return nullptr;
auto& object = *g_objects[window.parent];
@@ -738,28 +728,30 @@ void update_cursor(WINDOW wid, int32_t x, int32_t y)
if (g_platform_ops.set_cursor == nullptr)
return;
wid = find_child_window(wid, x, y);
if (wid == None)
const auto child_wid = find_child_window(wid, x, y);
if (child_wid == None)
return;
const CURSOR cid = [&wid]() -> CURSOR {
for (;;)
{
const auto& window = g_objects[wid]->object.get<Object::Window>();
if (window.cursor != None || window.parent == None)
return window.cursor;
wid = window.parent;
}
}();
CURSOR cursor = None;
static CURSOR active_cid = None;
if (cid == active_cid)
if (child_wid != None)
{
auto& object = *g_objects[child_wid];
ASSERT(object.type == Object::Type::Window);
cursor = object.object.get<Object::Window>().cursor;
}
static CURSOR active_cursor = None;
if (cursor == active_cursor)
return;
const auto& window = g_objects[wid]->object.get<Object::Window>();
g_platform_ops.set_cursor(get_platform_window(window), get_cursor_safe(cid));
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
active_cid = cid;
g_platform_ops.set_cursor(get_platform_window(window), get_cursor_safe(cursor));
active_cursor = window.cursor;
}
static void on_root_client_message(const xEvent& event)
@@ -1018,11 +1010,9 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
}
if (cursor_id.has_value())
{
window.cursor = cursor_id.value();
if (window.hovered)
update_cursor(request.window, window.cursor_x, window.cursor_y);
}
if (window.hovered)
update_cursor(request.window, window.cursor_x, window.cursor_y);
if (background.has_value())
window.background = background.value();
@@ -1942,15 +1932,12 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
const auto& window = TRY_REF(get_window(client_info, wid, opcode));
WINDOW child_wid { None };
for (auto wid_ : window.children)
{
const auto& child_window = g_objects[wid_]->object.get<Object::Window>();
if (!child_window.hovered)
continue;
child_wid = wid_;
break;
}
int32_t root_x, root_y;
int32_t event_x, event_y;
root_x = event_x = window.cursor_x;
root_y = event_y = window.cursor_y;
const auto child_wid = find_child_window(wid, event_x, event_y);
xQueryPointerReply reply {
.type = X_Reply,
@@ -1958,11 +1945,11 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
.sequenceNumber = client_info.sequence,
.length = 0,
.root = g_root.windowId,
.child = static_cast<CARD32>(child_wid),
.rootX = static_cast<INT16>(window.cursor_x), // FIXME
.rootY = static_cast<INT16>(window.cursor_y), // FIXME
.winX = static_cast<INT16>(window.cursor_x),
.winY = static_cast<INT16>(window.cursor_y),
.child = static_cast<CARD32>(child_wid == wid ? None : child_wid),
.rootX = static_cast<INT16>(root_x),
.rootY = static_cast<INT16>(root_y),
.winX = static_cast<INT16>(event_x),
.winY = static_cast<INT16>(event_y),
.mask = static_cast<KeyButMask>(g_keymask | g_butmask),
};
TRY(encode(client_info.output_buffer, reply));
@@ -2065,7 +2052,8 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
.depth = request.depth,
.width = request.width,
.height = request.height,
.data = BAN::move(data),
.data = data.span(),
.owned_data = BAN::move(data),
}
}))
));
@@ -2709,7 +2697,7 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
{
auto request = decode<xQueryBestSizeReq>(packet).value();
dprintln("QueryBestSize");
dprintln("FreeCursor");
dprintln(" class: {}", request.c_class);
dprintln(" drawable: {}", request.drawable);
dprintln(" width: {}", request.width);

View File

@@ -81,7 +81,8 @@ struct Object
CARD8 depth;
CARD32 width;
CARD32 height;
BAN::Vector<uint8_t> data;
BAN::ByteSpan data;
BAN::Vector<uint8_t> owned_data;
};
struct GraphicsContext

View File

@@ -1,6 +1,8 @@
#include "Base.h"
#include "Definitions.h"
#include "Keymap.h"
#include "Platform.h"
#include "SafeGetters.h"
#include "Utils.h"
#include <X11/X.h>
@@ -449,26 +451,14 @@ static void send_enter_and_leave_events(WINDOW old_wid, int32_t old_x, int32_t o
MUST(window.send_event(event, EnterWindowMask));
}
for (const auto wid : old_child_path)
g_objects[wid]->object.get<Object::Window>().hovered = false;
for (const auto wid : new_child_path)
g_objects[wid]->object.get<Object::Window>().hovered = true;
}
const auto set_last_entry_hovered = [](const BAN::Vector<WINDOW>& path, bool value) {
if (path.empty())
return;
g_objects[path.back()]->object.get<Object::Window>().hovered = value;
};
static WINDOW s_current_top_level_window = None;
void on_window_leave_event(WINDOW wid)
{
if (s_current_top_level_window != wid)
return;
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
send_enter_and_leave_events(wid, window.cursor_x, window.cursor_y, wid, -1, -1);
s_current_top_level_window = None;
set_last_entry_hovered(old_child_path, false);
set_last_entry_hovered(new_child_path, true);
}
void on_mouse_move_event(WINDOW wid, int32_t x, int32_t y)
@@ -479,16 +469,20 @@ void on_mouse_move_event(WINDOW wid, int32_t x, int32_t y)
update_cursor(wid, x, y);
if (auto it = g_objects.find(s_current_top_level_window); it == g_objects.end())
send_enter_and_leave_events(wid, -1, -1, wid, x, y);
else
{
static WINDOW old_wid = g_root.windowId;
auto it = g_objects.find(old_wid);
if (it == g_objects.end())
it = g_objects.find(g_root.windowId);
ASSERT(it->value->type == Object::Type::Window);
auto& old_window = it->value->object.get<Object::Window>();
send_enter_and_leave_events(s_current_top_level_window, old_window.cursor_x, old_window.cursor_y, wid, x, y);
}
s_current_top_level_window = wid;
send_enter_and_leave_events(old_wid, old_window.cursor_x, old_window.cursor_y, wid, x, y);
old_wid = wid;
}
update_cursor_position_recursive(wid, x, y);

View File

@@ -9,7 +9,6 @@ void on_window_close_event(WINDOW wid);
void on_window_resize_event(WINDOW wid, uint32_t width, uint32_t height);
void on_window_focus_event(WINDOW wid, bool focused);
void on_window_fullscreen_event(WINDOW wid, bool is_fullscreen);
void on_window_leave_event(WINDOW wid);
void on_mouse_move_event(WINDOW wid, int32_t x, int32_t y);
void on_mouse_button_event(WINDOW wid, uint8_t xbutton, bool pressed);

View File

@@ -7,13 +7,42 @@
#include <X11/X.h>
#include <X11/extensions/shmproto.h>
#include <netinet/in.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <unistd.h>
static BYTE s_shm_event_base;
static BYTE s_shm_error_base;
static BYTE s_shm_major_opcode;
static bool is_local_socket(int socket)
{
sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
if (getpeername(socket, reinterpret_cast<sockaddr*>(&addr), &addr_len) == -1)
return false;
switch (addr.ss_family)
{
case AF_UNIX:
return true;
case AF_INET:
{
const auto* addr_in = reinterpret_cast<const sockaddr_in*>(&addr);
const auto ipv4 = ntohl(addr_in->sin_addr.s_addr);
return (ipv4 & IN_CLASSA_NET) == IN_LOOPBACKNET;
}
case AF_INET6:
{
const auto* addr_in6 = reinterpret_cast<const sockaddr_in6*>(&addr);
return IN6_IS_ADDR_LOOPBACK(&addr_in6->sin6_addr);
}
}
return false;
}
static BAN::ErrorOr<void*> get_shmseg(Client& client_info, CARD32 shmseg, BYTE op_major, BYTE op_minor)
{
auto it = g_objects.find(shmseg);
@@ -45,11 +74,11 @@ static BAN::ErrorOr<void> extension_shm(Client& client_info, BAN::ConstByteSpan
{
case X_ShmQueryVersion:
{
dprintln("ShmQueryVersion");
dwarnln("ShmQueryVersion");
xShmQueryVersionReply reply {
.type = X_Reply,
.sharedPixmaps = false,
.sharedPixmaps = is_local_socket(client_info.fd),
.sequenceNumber = client_info.sequence,
.length = 0,
.majorVersion = 1,
@@ -66,7 +95,7 @@ static BAN::ErrorOr<void> extension_shm(Client& client_info, BAN::ConstByteSpan
{
auto request = decode<xShmAttachReq>(packet).value();
dprintln("ShmAttach");
dwarnln("ShmAttach");
dprintln(" shmseg: {}", request.shmseg);
dprintln(" shmid: {}", request.shmid);
dprintln(" readOnly: {}", request.readOnly);
@@ -115,7 +144,7 @@ static BAN::ErrorOr<void> extension_shm(Client& client_info, BAN::ConstByteSpan
{
auto request = decode<xShmDetachReq>(packet).value();
dprintln("ShmDetach");
dwarnln("ShmDetach");
dprintln(" shmseg: {}", request.shmseg);
void* shm_segment = TRY(get_shmseg(client_info, request.shmseg, op_major, op_minor));
@@ -130,7 +159,7 @@ static BAN::ErrorOr<void> extension_shm(Client& client_info, BAN::ConstByteSpan
{
auto request = decode<xShmPutImageReq>(packet).value();
dprintln("ShmPutImage");
dwarnln("ShmPutImage");
dprintln(" drawable: {}", request.drawable);
dprintln(" gc: {}", request.gc);
dprintln(" totalWidth: {}", request.totalWidth);
@@ -194,7 +223,7 @@ static BAN::ErrorOr<void> extension_shm(Client& client_info, BAN::ConstByteSpan
{
auto request = decode<xShmGetImageReq>(packet).value();
dprintln("ShmGetImage");
dwarnln("ShmGetImage");
dprintln(" drawable: {}", request.drawable);
dprintln(" x: {}", request.x);
dprintln(" y: {}", request.y);
@@ -236,6 +265,44 @@ static BAN::ErrorOr<void> extension_shm(Client& client_info, BAN::ConstByteSpan
break;
}
case X_ShmCreatePixmap:
{
auto request = decode<xShmCreatePixmapReq>(packet).value();
dwarnln("ShmCreatePixmap");
dprintln(" depth: {}", request.depth);
dprintln(" pid: {}", request.pid);
dprintln(" drawable: {}", request.drawable);
dprintln(" width: {}", request.width);
dprintln(" height: {}", request.height);
dprintln(" shmseg: {}", request.shmseg);
dprintln(" offset: {}", request.offset);
uint8_t bpp = 0;
for (auto format : g_formats)
if (format.depth == request.depth)
bpp = format.bitsPerPixel;
ASSERT(bpp == 32);
void* shm_segment = TRY(get_shmseg(client_info, request.shmseg, op_major, op_minor));
TRY(client_info.objects.insert(request.pid));
TRY(g_objects.insert(
request.pid,
TRY(BAN::UniqPtr<Object>::create(Object {
.type = Object::Type::Pixmap,
.object = Object::Pixmap {
.depth = request.depth,
.width = request.width,
.height = request.height,
.data = BAN::ByteSpan(static_cast<uint8_t*>(shm_segment) + request.offset, request.width * request.height * 4),
.owned_data = {},
}
}))
));
break;
}
default:
dwarnln("unsupported shm minor opcode {}", packet[1]);
break;

View File

@@ -60,33 +60,8 @@ void put_image(const PutImageInfo& info)
}
case ZPixmap:
{
bool needs_clipping = true;
if (info.gc.clip_mask == None)
{
needs_clipping = false;
}
else if (info.gc.clip_mask == UINT32_MAX)
{
const uint32_t clip_min_x = min_x - info.gc.clip_origin_x;
const uint32_t clip_min_y = min_y - info.gc.clip_origin_y;
const uint32_t clip_max_x = max_x - info.gc.clip_origin_x;
const uint32_t clip_max_y = max_y - info.gc.clip_origin_y;
for (const auto& rect : info.gc.clip_rects)
{
if (clip_min_x < rect.x || clip_max_x > rect.x + rect.width)
continue;
if (clip_min_y < rect.y || clip_max_y > rect.y + rect.height)
continue;
needs_clipping = false;
break;
}
}
ASSERT(info.left_pad == 0);
if (in_bpp == 32 && !needs_clipping)
if (in_bpp == 32 && info.gc.clip_mask == None)
{
const auto bytes_per_row = (max_x - min_x) * 4;
for (int32_t y = min_y; y < max_y; y++)

View File

@@ -115,7 +115,7 @@ static BAN::ErrorOr<BAN::UniqPtr<PlatformWindow>> sdl2_create_window(PlatformWin
window->width = width;
window->height = height;
if (parent == nullptr)
if (parent == nullptr || type != WindowType::Popup)
x = y = SDL_WINDOWPOS_UNDEFINED;
else
{
@@ -215,9 +215,6 @@ static void sdl2_poll_events(void*)
case SDL_WINDOWEVENT_RESTORED:
on_window_fullscreen_event(window.wid, false);
break;
case SDL_WINDOWEVENT_LEAVE:
on_window_leave_event(window.wid);
break;
}
break;
}
@@ -328,10 +325,11 @@ static void sdl2_invalidate(PlatformWindow* window, const uint32_t* src_pixels,
static void sdl2_request_fullscreen(PlatformWindow* window, bool fullscreen)
{
auto& sdl_window = *static_cast<SDLWindow*>(window);
SDL_SetWindowFullscreen(sdl_window.window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
(void)sdl_window;
(void)fullscreen;
}
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_system_cursor(SystemCursorType type)
BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_system_cursor(SystemCursorType type)
{
SDL_SystemCursor sdl_type;
switch (type)
@@ -387,7 +385,7 @@ static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_system_cursor(Syst
return BAN::UniqPtr<PlatformCursor>(BAN::move(cursor));
}
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_bitmap_cursor(const uint32_t* pixels, uint32_t width, uint32_t height, int32_t origin_x, int32_t origin_y)
BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_bitmap_cursor(const uint32_t* pixels, uint32_t width, uint32_t height, int32_t origin_x, int32_t origin_y)
{
auto cursor = TRY(BAN::UniqPtr<SDLCursor>::create());
@@ -398,8 +396,6 @@ static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_bitmap_cursor(cons
return BAN::Error::from_errno(EFAULT);
}
origin_x = BAN::Math::clamp<int32_t>(origin_x, 0, width - 1);
origin_y = BAN::Math::clamp<int32_t>(origin_y, 0, height - 1);
cursor->cursor = SDL_CreateColorCursor(surface, origin_x, origin_y);
SDL_FreeSurface(surface);
@@ -413,7 +409,7 @@ static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_bitmap_cursor(cons
return BAN::UniqPtr<PlatformCursor>(BAN::move(cursor));
}
static void sdl2_set_cursor(PlatformWindow*, PlatformCursor* cursor)
void sdl2_set_cursor(PlatformWindow*, PlatformCursor* cursor)
{
if (cursor == nullptr)
SDL_SetCursor(s_default_cursor);

View File

@@ -290,18 +290,6 @@ int main()
window.event_masks.remove(&client_info);
}
for (auto it = client_info.objects.begin(); it != client_info.objects.end();)
{
auto obj_it = g_objects.find(*it);
if (obj_it == g_objects.end() || obj_it->value->type != Object::Type::Window)
it++;
else
{
(void)destroy_window(client_info, *it);
it = client_info.objects.begin();
}
}
for (auto id : client_info.objects)
{
auto it = g_objects.find(id);
@@ -316,13 +304,14 @@ int main()
case Object::Type::Pixmap:
case Object::Type::GraphicsContext:
case Object::Type::Font:
break;
case Object::Type::Window:
ASSERT_NOT_REACHED();
break;
case Object::Type::Extension:
{
auto& extension = object.object.get<Object::Extension>();
extension.destructor(extension);
break;
}
}
g_objects.remove(it);