Compare commits

...

9 Commits

Author SHA1 Message Date
aa70815f5f Implement very basic grab pointer
This is definitely not correct with child windows but works with single
window apps (minecraft)
2026-06-01 22:24:17 +03:00
19f043744a Allow querying global cursor position
If a platform implements this, e.g. xeyes can track cursor when the
window is not receiving mouse movement events
2026-06-01 22:04:01 +03:00
af2132315d Implement WarpPointer
This allows playing games that capture the cursor
2026-06-01 22:04:01 +03:00
83af554e8c Add support for global window position
This allows proper TranslateCoords and event root coordinate reporting
2026-06-01 20:39:47 +03:00
b41d979dcb Port to SDL3 instead of SDL2
Main reason is that SDL3 allows getting keyboard mappings with modifiers
2026-06-01 20:35:56 +03:00
11ebced96a Destroy client's windows on disconnect
This makes sure client's windows are removed from the root window and
all of the destroy events are sent.
2026-06-01 20:35:56 +03:00
504eb2716b Fix QueryPointer
QueryPointer is supposed to return first child of the window containing
the pointer, not the deepest child. This fixes gtk getting into an
invalid state when pressing a mouse button
2026-06-01 20:35:56 +03:00
82edc9c76b Handle external window leave events 2026-06-01 20:35:56 +03:00
92e02dcadf Don't create platform window for small windows
These are usually internal event windows which should not be visible.
I'm not sure what the correct check for this would be...
gtk and other toolkits create a lot of dummy windows that should not be
visible.
2026-06-01 20:35:56 +03:00
9 changed files with 443 additions and 237 deletions

View File

@@ -28,6 +28,8 @@ CARD16 g_keymask { 0 };
CARD16 g_butmask { 0 };
WINDOW g_focus_window { None };
static WINDOW s_pointer_grab_wid = None;
static const char* s_opcode_to_name[] {
[0] = "none",
[X_CreateWindow] = "X_CreateWindow",
@@ -374,6 +376,9 @@ 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);
@@ -447,7 +452,7 @@ void send_exposure_recursive(WINDOW wid)
struct PlatformWindowInfo
{
PlatformWindow* transient_for;
PlatformWindow* parent;
WindowType type;
};
@@ -463,7 +468,7 @@ static PlatformWindowInfo get_plaform_window_info(const Object::Window& window)
static const CARD32 _NET_WM_WINDOW_TYPE_UTILITY = g_atoms_name_to_id["_NET_WM_WINDOW_TYPE_UTILITY"_sv];
PlatformWindowInfo info {
.transient_for = nullptr,
.parent = nullptr,
.type = WindowType::Normal,
};
@@ -475,7 +480,7 @@ static PlatformWindowInfo get_plaform_window_info(const Object::Window& window)
const CARD32 type = *reinterpret_cast<const CARD32*>(it->value.data.data());
if (type == _NET_WM_WINDOW_TYPE_NORMAL)
info.type = WindowType::Normal;
else if (type == _NET_WM_WINDOW_TYPE_SPLASH || type == _NET_WM_WINDOW_TYPE_UTILITY || type == _NET_WM_WINDOW_TYPE_DIALOG)
else if (type == _NET_WM_WINDOW_TYPE_SPLASH || type == _NET_WM_WINDOW_TYPE_DIALOG)
info.type = WindowType::Utility;
else
info.type = WindowType::Popup;
@@ -497,7 +502,7 @@ wm_window_type_done:
// FIXME: support child windows
auto& window = it2->value->object.get<Object::Window>();
info.transient_for = window.platform_window.ptr();
info.parent = window.platform_window.ptr();
}
transitient_for_done:
@@ -512,14 +517,21 @@ static BAN::ErrorOr<void> map_window(Client& client_info, WINDOW wid)
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.width <= 10 || window.height <= 10)
return {};
auto info = get_plaform_window_info(window);
window.platform_window = TRY(g_platform_ops.create_window(
info.transient_for,
info.parent,
info.type,
wid,
window.x,
@@ -529,8 +541,6 @@ 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);
@@ -711,6 +721,21 @@ WINDOW find_child_window(WINDOW wid, int32_t& x, int32_t& y)
return wid;
}
xPoint get_window_position(WINDOW wid)
{
xPoint result { .x = 0, .y = 0 };
while (wid != None)
{
const auto& window = g_objects[wid]->object.get<Object::Window>();
result.x += window.x;
result.y += window.y;
wid = window.parent;
}
return result;
}
static PlatformWindow* get_platform_window(const Object::Window& window)
{
if (window.platform_window)
@@ -1231,18 +1256,21 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
}
}
bool window_changed = false;
const int32_t min_x = BAN::Math::min(window.x, new_x);
const int32_t min_y = BAN::Math::min(window.y, new_y);
const int32_t max_x = BAN::Math::max(window.x + window.width, new_x + new_width);
const int32_t max_y = BAN::Math::max(window.y + window.height, new_y + new_height);
bool send_configure = false;
if (new_x != window.x || new_y != window.y)
{
window.x = new_x;
window.y = new_y;
window_changed = true;
if (window.platform_window)
{
if (g_platform_ops.request_reposition)
g_platform_ops.request_reposition(window.platform_window.ptr(), new_x, new_y);
}
else
{
window.x = new_x;
window.y = new_y;
send_configure = true;
}
}
if (new_width != window.width || new_height != window.height)
@@ -1251,22 +1279,20 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
{
if (g_platform_ops.request_resize)
g_platform_ops.request_resize(window.platform_window.ptr(), new_width, new_height);
window_changed = false;
}
else
{
window.width = new_width;
window.height = new_height;
TRY(window.pixels.resize(new_width * new_height));
for (auto& pixel : window.pixels)
pixel = window.background;
window_changed = true;
window.width = new_width;
window.height = new_height;
send_configure = true;
}
}
if (!window_changed)
if (!send_configure)
break;
{
@@ -1828,13 +1854,37 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
dprintln(" cursor: {}", request.cursor);
dprintln(" time: {}", request.time);
xGrabPointerReply reply {
.type = X_Reply,
.status = Success,
.sequenceNumber = client_info.sequence,
.length = 0,
};
TRY(encode(client_info.output_buffer, reply));
auto& window = TRY_REF(get_window(client_info, request.grabWindow, X_GrabPointer));
if (s_pointer_grab_wid != None && !g_objects.contains(s_pointer_grab_wid))
s_pointer_grab_wid = None;
if (s_pointer_grab_wid == None)
{
xGrabPointerReply reply {
.type = X_Reply,
.status = Success,
.sequenceNumber = client_info.sequence,
.length = 0,
};
TRY(encode(client_info.output_buffer, reply));
if (g_platform_ops.set_pointer_grab)
if (auto* platform_window = get_platform_window(window))
g_platform_ops.set_pointer_grab(platform_window, true);
s_pointer_grab_wid = request.grabWindow;
}
else
{
xGrabPointerReply reply {
.type = X_Reply,
.status = AlreadyGrabbed,
.sequenceNumber = client_info.sequence,
.length = 0,
};
TRY(encode(client_info.output_buffer, reply));
}
break;
}
@@ -1845,6 +1895,16 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
dprintln("UngrabPointer");
dprintln(" time: {}", time);
if (s_pointer_grab_wid == None)
break;
auto it = g_objects.find(s_pointer_grab_wid);
if (it != g_objects.end() && g_platform_ops.set_pointer_grab)
if (auto* platform_window = get_platform_window(it->value->object.get<Object::Window>()))
g_platform_ops.set_pointer_grab(platform_window, false);
s_pointer_grab_wid = None;
break;
}
case X_GrabKeyboard:
@@ -1932,12 +1992,26 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
const auto& window = TRY_REF(get_window(client_info, wid, opcode));
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;
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;
}
const auto child_wid = find_child_window(wid, event_x, event_y);
const auto [root_x, root_y] = get_window_position(wid);
int32_t root_cursor_x, root_cursor_y;
if (g_platform_ops.query_pointer)
g_platform_ops.query_pointer(&root_cursor_x, &root_cursor_y);
else
{
root_cursor_x = root_x + window.cursor_x;
root_cursor_y = root_y + window.cursor_y;
}
xQueryPointerReply reply {
.type = X_Reply,
@@ -1945,11 +2019,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 == 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),
.child = static_cast<CARD32>(child_wid),
.rootX = static_cast<INT16>(root_cursor_x),
.rootY = static_cast<INT16>(root_cursor_y),
.winX = static_cast<INT16>(root_cursor_x - root_x),
.winY = static_cast<INT16>(root_cursor_y - root_y),
.mask = static_cast<KeyButMask>(g_keymask | g_butmask),
};
TRY(encode(client_info.output_buffer, reply));
@@ -1966,19 +2040,55 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
dprintln(" src_x: {}", request.srcX);
dprintln(" src_y: {}", request.srcY);
(void)TRY_REF(get_window(client_info, request.dstWid, X_TranslateCoords));
(void)TRY_REF(get_window(client_info, request.srcWid, X_TranslateCoords));
const auto [src_x, src_y] = get_window_position(request.srcWid);
const auto [dst_x, dst_y] = get_window_position(request.dstWid);
xTranslateCoordsReply reply {
.type = X_Reply,
.sameScreen = xTrue,
.sequenceNumber = client_info.sequence,
.length = 0,
.child = None,
.dstX = request.srcX,
.dstY = request.srcY,
.child = None, // FIXME
.dstX = static_cast<INT16>(src_x + request.srcX - dst_x),
.dstY = static_cast<INT16>(src_y + request.srcY - dst_y),
};
TRY(encode(client_info.output_buffer, reply));
break;
}
case X_WarpPointer:
{
auto request = decode<xWarpPointerReq>(packet).value();
dprintln("WarpPointer");
dprintln(" src_wid: {}", request.srcWid);
dprintln(" src_x: {}", request.srcX);
dprintln(" src_y: {}", request.srcY);
dprintln(" src_w: {}", request.srcWidth);
dprintln(" src_h: {}", request.srcHeight);
dprintln(" dst_wid: {}", request.dstWid);
dprintln(" dst_x: {}", request.dstX);
dprintln(" dst_y: {}", request.dstY);
if (g_platform_ops.warp_pointer == nullptr)
break;
// TODO: source window :)
if (request.dstWid == None)
g_platform_ops.warp_pointer(request.dstX, request.dstY, true);
else
{
(void)TRY_REF(get_window(client_info, request.dstWid, X_WarpPointer));
const auto [win_x, win_y] = get_window_position(request.dstWid);
g_platform_ops.warp_pointer(win_x + request.dstX, win_y + request.dstY, false);
}
break;
}
case X_QueryKeymap:
{
dprintln("QueryKeymap");

View File

@@ -14,6 +14,8 @@ BAN::ErrorOr<void> destroy_window(Client& client_info, WINDOW wid);
WINDOW find_child_window(WINDOW wid, int32_t& x, int32_t& y);
xPoint get_window_position(WINDOW wid);
static constexpr uint32_t COLOR_INVISIBLE = 0x69000000;
extern CARD16 g_keymask;

View File

@@ -24,15 +24,15 @@ endif()
set(PLATFORM "banan-os" CACHE STRING "target platform")
set(VALID_PLATFORMS "banan-os" "SDL2")
set(VALID_PLATFORMS "banan-os" "SDL3")
if(NOT PLATFORM IN_LIST VALID_PLATFORMS)
message(FATAL_ERROR "platform \"${PLATFORM}\" not known, valid platforms are ${VALID_PLATFORMS}")
endif()
if(PLATFORM STREQUAL "banan-os")
list(APPEND XBANAN_SOURCES banan-os/banan-os.cpp)
elseif(PLATFORM STREQUAL "SDL2")
list(APPEND XBANAN_SOURCES SDL2/sdl2.cpp)
elseif(PLATFORM STREQUAL "SDL3")
list(APPEND XBANAN_SOURCES SDL3/sdl3.cpp)
endif()
add_executable(xbanan ${XBANAN_SOURCES})
@@ -42,9 +42,9 @@ banan_link_library(xbanan libinput)
if(PLATFORM STREQUAL "banan-os")
banan_link_library(xbanan libgui)
elseif(PLATFORM STREQUAL "SDL2")
find_package(SDL2 REQUIRED)
banan_link_library(xbanan SDL2::SDL2)
elseif(PLATFORM STREQUAL "SDL3")
find_package(SDL3 REQUIRED)
banan_link_library(xbanan SDL3::SDL3)
endif()
target_compile_options(xbanan PRIVATE -Wall -Wextra)

View File

@@ -75,21 +75,8 @@ void on_window_close_event(WINDOW wid)
}
}
void on_window_resize_event(WINDOW wid, uint32_t new_width, uint32_t new_height)
static void send_window_configure(WINDOW wid, Object::Window& window)
{
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
{
window.width = new_width;
window.height = new_height;
MUST(window.pixels.resize(new_width * new_height));
for (auto& pixel : window.pixels)
pixel = window.background;
}
{
xEvent event = { .u = {
.configureNotify = {
@@ -129,12 +116,42 @@ void on_window_resize_event(WINDOW wid, uint32_t new_width, uint32_t new_height)
event.u.u.type = ConfigureNotify;
MUST(parent_window.send_event(event, SubstructureNotifyMask));
}
}
void on_window_resize_event(WINDOW wid, uint32_t new_width, uint32_t new_height)
{
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
{
window.width = new_width;
window.height = new_height;
MUST(window.pixels.resize(new_width * new_height));
for (auto& pixel : window.pixels)
pixel = window.background;
}
send_window_configure(wid, window);
send_exposure_recursive(wid);
invalidate_window(wid, 0, 0, window.width, window.height);
}
void on_window_move_event(WINDOW wid, int32_t x, int32_t y)
{
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
window.x = x;
window.y = y;
send_window_configure(wid, window);
}
void on_window_focus_event(WINDOW wid, bool focused)
{
if (focused)
@@ -219,15 +236,14 @@ void on_window_fullscreen_event(WINDOW wid, bool is_fullscreen)
static void send_key_button_pointer_event(WINDOW root_wid, BYTE detail, uint32_t event_mask, BYTE event_type, KeyButMask state)
{
int32_t root_x, root_y;
int32_t event_x, event_y;
{
auto& object = *g_objects[root_wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
root_x = event_x = window.cursor_x;
root_y = event_y = window.cursor_y;
event_x = window.cursor_x;
event_y = window.cursor_y;
}
const auto child_wid = find_child_window(root_wid, event_x, event_y);
@@ -257,14 +273,15 @@ static void send_key_button_pointer_event(WINDOW root_wid, BYTE detail, uint32_t
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
const auto [root_x, root_y] = get_window_position(wid);
xEvent event { .u = {
.keyButtonPointer = {
.time = static_cast<CARD32>(time(nullptr)),
.root = g_root.windowId,
.event = wid,
.child = static_cast<CARD32>(child_wid == wid ? None : child_wid),
.rootX = static_cast<INT16>(root_x),
.rootY = static_cast<INT16>(root_y),
.rootX = static_cast<INT16>(root_x + event_x),
.rootY = static_cast<INT16>(root_y + event_y),
.eventX = static_cast<INT16>(event_x),
.eventY = static_cast<INT16>(event_y),
.state = state,
@@ -389,14 +406,15 @@ static void send_enter_and_leave_events(WINDOW old_wid, int32_t old_x, int32_t o
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
const auto [root_x, root_y] = get_window_position(wid);
xEvent event { .u = {
.enterLeave = {
.time = static_cast<CARD32>(time(nullptr)),
.root = g_root.windowId,
.event = wid,
.child = first ? static_cast<WINDOW>(None) : old_child_path.back(),
.rootX = static_cast<INT16>(old_x),
.rootY = static_cast<INT16>(old_y),
.rootX = static_cast<INT16>(root_x + old_x),
.rootY = static_cast<INT16>(root_y + old_y),
.eventX = static_cast<INT16>(old_x),
.eventY = static_cast<INT16>(old_y),
.state = static_cast<KeyButMask>(g_keymask | g_butmask),
@@ -429,14 +447,15 @@ static void send_enter_and_leave_events(WINDOW old_wid, int32_t old_x, int32_t o
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
const auto [root_x, root_y] = get_window_position(wid);
xEvent event { .u = {
.enterLeave = {
.time = static_cast<CARD32>(time(nullptr)),
.root = g_root.windowId,
.event = wid,
.child = last ? static_cast<WINDOW>(None) : new_child_path.back(),
.rootX = static_cast<INT16>(new_x),
.rootY = static_cast<INT16>(new_y),
.rootX = static_cast<INT16>(root_x + new_x),
.rootY = static_cast<INT16>(root_y + new_y),
.eventX = static_cast<INT16>(new_x),
.eventY = static_cast<INT16>(new_y),
.state = static_cast<KeyButMask>(g_keymask | g_butmask),
@@ -453,7 +472,22 @@ static void send_enter_and_leave_events(WINDOW old_wid, int32_t old_x, int32_t o
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;
}
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;
}
void on_mouse_move_event(WINDOW wid, int32_t x, int32_t y)
@@ -464,24 +498,17 @@ 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())
{
old_wid = g_root.windowId;
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(old_wid, old_window.cursor_x, old_window.cursor_y, wid, x, y);
old_wid = wid;
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;
update_cursor_position_recursive(wid, x, y);
// TODO: optimize with PointerMotionHint

View File

@@ -7,8 +7,10 @@ void unregister_event_fd(int fd);
void on_window_close_event(WINDOW wid);
void on_window_resize_event(WINDOW wid, uint32_t width, uint32_t height);
void on_window_move_event(WINDOW wid, int32_t x, int32_t y);
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

@@ -16,9 +16,9 @@ struct PlatformCursor
enum class WindowType
{
Popup,
Normal,
Utility,
Popup,
};
enum class SystemCursorType
@@ -55,8 +55,16 @@ struct PlatformOps
void (*invalidate)(PlatformWindow*, const uint32_t* pixels, uint32_t x, uint32_t y, uint32_t width, uint32_t height);
/* Request resize of a window, can be async */
void (*request_resize)(PlatformWindow*, uint32_t width, uint32_t height);
/* Request window repositioning */
void (*request_reposition)(PlatformWindow*, int32_t x, int32_t y);
/* Request new fullscreen state, can be async */
void (*request_fullscreen)(PlatformWindow*, bool fullscreen);
/* Warp pointer */
void (*warp_pointer)(int32_t x, int32_t y, bool relative);
/* Get global position of the pointer */
void (*query_pointer)(int32_t* x, int32_t* y);
/* Grab pointer events on window */
void (*set_pointer_grab)(PlatformWindow*, bool grabbed);
/* Create a system cursor */
BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> (*create_system_cursor)(SystemCursorType);
/* Create cursor from custom bitmap */

View File

@@ -4,7 +4,7 @@
#include <BAN/Atomic.h>
#include <BAN/HashMap.h>
#include <SDL2/SDL.h>
#include <SDL3/SDL.h>
#include <pthread.h>
#include <sys/eventfd.h>
@@ -44,7 +44,7 @@ struct SDLCursor final : public PlatformCursor
~SDLCursor()
{
if (cursor != nullptr)
SDL_FreeCursor(cursor);
SDL_DestroyCursor(cursor);
}
SDL_Cursor* cursor { nullptr };
@@ -55,13 +55,13 @@ static int s_eventfd { -1 };
struct Keymap
{
consteval Keymap();
uint8_t map[SDL_NUM_SCANCODES];
uint8_t map[SDL_SCANCODE_COUNT];
};
static Keymap s_sdl_keymap;
static SDL_Cursor* s_default_cursor { nullptr };
static void* sdl2_thread(void*)
static void* sdl3_thread(void*)
{
for (;;)
{
@@ -73,22 +73,24 @@ static void* sdl2_thread(void*)
return nullptr;
}
static bool sdl2_initialize(uint32_t* display_w, uint32_t* display_h)
static bool sdl3_initialize(uint32_t* display_w, uint32_t* display_h)
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) == -1)
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
{
dwarnln("Could not initialize SDL: {}", SDL_GetError());
return false;
}
SDL_DisplayMode DM;
if (SDL_GetCurrentDisplayMode(0, &DM) == -1)
*display_w = *display_h = 0;
const SDL_DisplayID* display_ids = SDL_GetDisplays(nullptr);
for (int i = 0; display_ids[i]; i++)
{
dwarnln("Could not get display mode: {}", SDL_GetError());
return false;
SDL_Rect rect;
SDL_GetDisplayBounds(display_ids[i], &rect);
*display_w = BAN::Math::max<uint32_t>(*display_w, rect.x + rect.w);
*display_h = BAN::Math::max<uint32_t>(*display_h, rect.y + rect.h);
}
*display_w = DM.w;
*display_h = DM.h;
s_default_cursor = SDL_GetCursor();
@@ -102,12 +104,12 @@ static bool sdl2_initialize(uint32_t* display_w, uint32_t* display_h)
register_event_fd(s_eventfd, nullptr);
pthread_t thread;
pthread_create(&thread, nullptr, sdl2_thread, nullptr);
pthread_create(&thread, nullptr, sdl3_thread, nullptr);
return true;
}
static BAN::ErrorOr<BAN::UniqPtr<PlatformWindow>> sdl2_create_window(PlatformWindow* parent, WindowType type, WINDOW wid, int32_t x, int32_t y, uint32_t width, uint32_t height)
static BAN::ErrorOr<BAN::UniqPtr<PlatformWindow>> sdl3_create_window(PlatformWindow* parent, WindowType type, WINDOW wid, int32_t x, int32_t y, uint32_t width, uint32_t height)
{
auto window = TRY(BAN::UniqPtr<SDLWindow>::create());
@@ -115,39 +117,41 @@ static BAN::ErrorOr<BAN::UniqPtr<PlatformWindow>> sdl2_create_window(PlatformWin
window->width = width;
window->height = height;
if (parent == nullptr)
x = y = SDL_WINDOWPOS_UNDEFINED;
else
{
auto& sdl_parent = *static_cast<SDLWindow*>(parent);
int parent_x, parent_y;
SDL_GetWindowPosition(sdl_parent.window, &parent_x, &parent_y);
x += parent_x;
y += parent_y;
}
int flags;
switch (type)
{
case WindowType::Popup:
flags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_SKIP_TASKBAR | SDL_WINDOW_UTILITY;
break;
case WindowType::Utility:
flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_UTILITY;
break;
case WindowType::Normal:
case WindowType::Utility:
flags = SDL_WINDOW_RESIZABLE;
break;
case WindowType::Popup:
flags = SDL_WINDOW_BORDERLESS;
break;
}
if (parent == nullptr || type != WindowType::Popup)
{
if (type != WindowType::Normal)
flags |= SDL_WINDOW_UTILITY;
window->window = SDL_CreateWindow("", width, height, flags);
}
else
{
auto& sdl_parent = *static_cast<SDLWindow*>(parent);
int parent_x, parent_y;
SDL_GetWindowPosition(sdl_parent.window, &parent_x, &parent_y);
window->window = SDL_CreatePopupWindow(sdl_parent.window, x - parent_x, y - parent_y, width, height, flags | SDL_WINDOW_POPUP_MENU);
}
window->window = SDL_CreateWindow("", x, y, width, height, flags);
if (window->window == nullptr)
{
dwarnln("Could not create SDL window: {}", SDL_GetError());
return BAN::Error::from_errno(EFAULT);
}
window->renderer = SDL_CreateRenderer(window->window, -1, SDL_RENDERER_ACCELERATED);
window->renderer = SDL_CreateRenderer(window->window, nullptr);
if (window->renderer == nullptr)
{
dwarnln("Could not create SDL renderer: {}", SDL_GetError());
@@ -166,13 +170,19 @@ static BAN::ErrorOr<BAN::UniqPtr<PlatformWindow>> sdl2_create_window(PlatformWin
return BAN::UniqPtr<PlatformWindow>(BAN::move(window));
}
static void sdl2_request_resize(PlatformWindow* window, uint32_t width, uint32_t height)
static void sdl3_request_resize(PlatformWindow* window, uint32_t width, uint32_t height)
{
auto& sdl_window = *static_cast<SDLWindow*>(window);
SDL_SetWindowSize(sdl_window.window, width, height);
}
static void sdl2_poll_events(void*)
static void sdl3_request_reposition(PlatformWindow* window, int32_t x, int32_t y)
{
auto& sdl_window = *static_cast<SDLWindow*>(window);
SDL_SetWindowPosition(sdl_window.window, x, y);
}
static void sdl3_poll_events(void*)
{
uint64_t dummy;
read(s_eventfd, &dummy, sizeof(dummy));
@@ -182,51 +192,48 @@ static void sdl2_poll_events(void*)
{
switch (event.type)
{
case SDL_WINDOWEVENT:
{
auto it = s_window_map.find(event.window.windowID);
if (it == s_window_map.end())
break;
auto& window = *it->value;
switch (event.window.event)
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
if (auto it = s_window_map.find(event.window.windowID); it != s_window_map.end())
on_window_close_event(it->value->wid);
break;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
if (auto it = s_window_map.find(event.window.windowID); it != s_window_map.end())
{
case SDL_WINDOWEVENT_CLOSE:
on_window_close_event(window.wid);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
window.width = event.window.data1;
window.height = event.window.data2;
auto& window = *it->value;
window.width = event.window.data1;
window.height = event.window.data2;
SDL_DestroyTexture(window.texture);
window.texture = SDL_CreateTexture(window.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, window.width, window.height);
ASSERT(window.texture);
SDL_DestroyTexture(window.texture);
window.texture = SDL_CreateTexture(window.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, window.width, window.height);
ASSERT(window.texture);
on_window_resize_event(window.wid, event.window.data1, event.window.data2);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
on_window_focus_event(window.wid, true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
on_window_focus_event(window.wid, false);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
on_window_fullscreen_event(window.wid, true);
break;
case SDL_WINDOWEVENT_RESTORED:
on_window_fullscreen_event(window.wid, false);
break;
on_window_resize_event(window.wid, window.width, window.height);
}
break;
}
case SDL_MOUSEMOTION:
{
auto it = s_window_map.find(event.window.windowID);
if (it != s_window_map.end())
case SDL_EVENT_WINDOW_MOVED:
if (auto it = s_window_map.find(event.motion.windowID); it != s_window_map.end())
on_window_move_event(it->value->wid, event.window.data1, event.window.data2);
break;
case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST:
if (auto it = s_window_map.find(event.motion.windowID); it != s_window_map.end())
on_window_focus_event(it->value->wid, (event.type == SDL_EVENT_WINDOW_FOCUS_GAINED));
break;
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
if (auto it = s_window_map.find(event.motion.windowID); it != s_window_map.end())
on_window_fullscreen_event(it->value->wid, (event.type == SDL_EVENT_WINDOW_ENTER_FULLSCREEN));
break;
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
if (auto it = s_window_map.find(event.motion.windowID); it != s_window_map.end())
on_window_leave_event(it->value->wid);
break;
case SDL_EVENT_MOUSE_MOTION:
if (auto it = s_window_map.find(event.motion.windowID); it != s_window_map.end())
on_mouse_move_event(it->value->wid, event.motion.x, event.motion.y);
break;
}
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEBUTTONDOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
{
uint8_t xbutton { 0 };
switch (event.button.button)
@@ -240,10 +247,10 @@ static void sdl2_poll_events(void*)
auto it = s_window_map.find(event.window.windowID);
if (xbutton && it != s_window_map.end())
on_mouse_button_event(it->value->wid, xbutton, (event.type == SDL_MOUSEBUTTONDOWN));
on_mouse_button_event(it->value->wid, xbutton, (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN));
break;
}
case SDL_MOUSEWHEEL:
case SDL_EVENT_MOUSE_WHEEL:
{
uint8_t xbutton { 0 };
if (event.wheel.y > 0)
@@ -260,31 +267,31 @@ static void sdl2_poll_events(void*)
break;
}
case SDL_KEYUP:
case SDL_KEYDOWN:
case SDL_EVENT_KEY_UP:
case SDL_EVENT_KEY_DOWN:
{
uint8_t scancode = s_sdl_keymap.map[event.key.keysym.scancode];
uint8_t scancode = s_sdl_keymap.map[event.key.scancode];
uint8_t xmod { 0 };
if (event.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT))
if (event.key.mod & SDL_KMOD_SHIFT)
xmod |= (1 << 0);
if (event.key.keysym.mod & (KMOD_CAPS))
if (event.key.mod & SDL_KMOD_CAPS)
xmod |= (1 << 1);
if (event.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL))
if (event.key.mod & SDL_KMOD_CTRL)
xmod |= (1 << 2);
if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
if (event.key.mod & SDL_KMOD_ALT)
xmod |= (1 << 3);
auto it = s_window_map.find(event.window.windowID);
if (it != s_window_map.end())
on_key_event(it->value->wid, scancode, xmod, (event.type == SDL_KEYDOWN));
on_key_event(it->value->wid, scancode, xmod, (event.type == SDL_EVENT_KEY_DOWN));
break;
}
}
}
}
static void sdl2_invalidate(PlatformWindow* window, const uint32_t* src_pixels, uint32_t x, uint32_t y, uint32_t width, uint32_t height)
static void sdl3_invalidate(PlatformWindow* window, const uint32_t* src_pixels, uint32_t x, uint32_t y, uint32_t width, uint32_t height)
{
auto& sdl_window = *static_cast<SDLWindow*>(window);
@@ -300,7 +307,7 @@ static void sdl2_invalidate(PlatformWindow* window, const uint32_t* src_pixels,
void* dst_pixels;
int dst_pitch;
if (SDL_LockTexture(sdl_window.texture, &rect, &dst_pixels, &dst_pitch) == -1)
if (!SDL_LockTexture(sdl_window.texture, &rect, &dst_pixels, &dst_pitch))
{
dwarnln("Could not lock texture: {}", SDL_GetError());
return;
@@ -318,63 +325,68 @@ static void sdl2_invalidate(PlatformWindow* window, const uint32_t* src_pixels,
SDL_UnlockTexture(sdl_window.texture);
SDL_RenderClear(sdl_window.renderer);
SDL_RenderCopy(sdl_window.renderer, sdl_window.texture, NULL, NULL);
SDL_RenderTexture(sdl_window.renderer, sdl_window.texture, NULL, NULL);
SDL_RenderPresent(sdl_window.renderer);
}
static void sdl2_request_fullscreen(PlatformWindow* window, bool fullscreen)
static void sdl3_request_fullscreen(PlatformWindow* window, bool fullscreen)
{
auto& sdl_window = *static_cast<SDLWindow*>(window);
SDL_SetWindowFullscreen(sdl_window.window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
SDL_SetWindowFullscreen(sdl_window.window, fullscreen);
}
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_system_cursor(SystemCursorType type)
static void sdl3_warp_pointer(int32_t x, int32_t y, bool relative)
{
SDL_SystemCursor sdl_type;
switch (type)
if (relative)
{
case SystemCursorType::Help:
case SystemCursorType::Pointer:
sdl_type = SDL_SYSTEM_CURSOR_ARROW;
break;
case SystemCursorType::Text:
sdl_type = SDL_SYSTEM_CURSOR_IBEAM;
break;
case SystemCursorType::Wait:
sdl_type = SDL_SYSTEM_CURSOR_WAIT;
break;
case SystemCursorType::Hand:
sdl_type = SDL_SYSTEM_CURSOR_HAND;
break;
case SystemCursorType::Move:
sdl_type = SDL_SYSTEM_CURSOR_SIZEALL;
break;
case SystemCursorType::Forbidden:
sdl_type = SDL_SYSTEM_CURSOR_NO;
break;
case SystemCursorType::ResizeN:
case SystemCursorType::ResizeS:
case SystemCursorType::ResizeVertical:
sdl_type = SDL_SYSTEM_CURSOR_SIZENS;
break;
case SystemCursorType::ResizeE:
case SystemCursorType::ResizeW:
case SystemCursorType::ResizeHorizontal:
sdl_type = SDL_SYSTEM_CURSOR_SIZEWE;
break;
case SystemCursorType::ResizeNW:
case SystemCursorType::ResizeSE:
sdl_type = SDL_SYSTEM_CURSOR_SIZENWSE;
break;
case SystemCursorType::ResizeNE:
case SystemCursorType::ResizeSW:
sdl_type = SDL_SYSTEM_CURSOR_SIZENESW;
break;
float cx, cy;
SDL_GetGlobalMouseState(&cx, &cy);
x += cx;
x += cy;
}
SDL_WarpMouseGlobal(x, y);
}
static void sdl3_query_pointer(int32_t* x, int32_t* y)
{
float cx, cy;
SDL_GetGlobalMouseState(&cx, &cy);
*x = cx;
*y = cy;
}
static void sdl3_set_pointer_grab(PlatformWindow* window, bool grabbed)
{
auto& sdl_window = *static_cast<SDLWindow*>(window);
SDL_SetWindowMouseGrab(sdl_window.window, grabbed);
}
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl3_create_system_cursor(SystemCursorType type)
{
static constexpr SDL_SystemCursor cursor_type_map[] {
[static_cast<size_t>(SystemCursorType::Pointer)] = SDL_SYSTEM_CURSOR_DEFAULT,
[static_cast<size_t>(SystemCursorType::Text)] = SDL_SYSTEM_CURSOR_TEXT,
[static_cast<size_t>(SystemCursorType::Wait)] = SDL_SYSTEM_CURSOR_WAIT,
[static_cast<size_t>(SystemCursorType::Hand)] = SDL_SYSTEM_CURSOR_POINTER,
[static_cast<size_t>(SystemCursorType::Help)] = SDL_SYSTEM_CURSOR_DEFAULT, // :(
[static_cast<size_t>(SystemCursorType::Move)] = SDL_SYSTEM_CURSOR_MOVE,
[static_cast<size_t>(SystemCursorType::Forbidden)] = SDL_SYSTEM_CURSOR_NOT_ALLOWED,
[static_cast<size_t>(SystemCursorType::ResizeN)] = SDL_SYSTEM_CURSOR_N_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeE)] = SDL_SYSTEM_CURSOR_E_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeS)] = SDL_SYSTEM_CURSOR_S_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeW)] = SDL_SYSTEM_CURSOR_W_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeNW)] = SDL_SYSTEM_CURSOR_NW_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeNE)] = SDL_SYSTEM_CURSOR_NE_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeSW)] = SDL_SYSTEM_CURSOR_SW_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeSE)] = SDL_SYSTEM_CURSOR_SE_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeVertical)] = SDL_SYSTEM_CURSOR_NS_RESIZE,
[static_cast<size_t>(SystemCursorType::ResizeHorizontal)] = SDL_SYSTEM_CURSOR_EW_RESIZE,
};
auto cursor = TRY(BAN::UniqPtr<SDLCursor>::create());
cursor->cursor = SDL_CreateSystemCursor(sdl_type);
cursor->cursor = SDL_CreateSystemCursor(cursor_type_map[static_cast<size_t>(type)]);
if (cursor->cursor == nullptr)
{
dwarnln("Could not create SDL system cursor: {}", SDL_GetError());
@@ -384,11 +396,11 @@ 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)
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl3_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());
SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormatFrom(const_cast<uint32_t*>(pixels), width, height, 32, width * 4, SDL_PIXELFORMAT_ARGB8888);
SDL_Surface* surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_ARGB8888, const_cast<uint32_t*>(pixels), width * 4);
if (surface == nullptr)
{
dwarnln("Could not create SDL surface for cursor: {}", SDL_GetError());
@@ -399,7 +411,7 @@ static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_bitmap_cursor(cons
origin_y = BAN::Math::clamp<int32_t>(origin_y, 0, height - 1);
cursor->cursor = SDL_CreateColorCursor(surface, origin_x, origin_y);
SDL_FreeSurface(surface);
SDL_DestroySurface(surface);
if (cursor->cursor == nullptr)
{
@@ -410,7 +422,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)
static void sdl3_set_cursor(PlatformWindow*, PlatformCursor* cursor)
{
if (cursor == nullptr)
SDL_SetCursor(s_default_cursor);
@@ -422,15 +434,19 @@ static void sdl2_set_cursor(PlatformWindow*, PlatformCursor* cursor)
}
PlatformOps g_platform_ops = {
.initialize = sdl2_initialize,
.poll_events = sdl2_poll_events,
.create_window = sdl2_create_window,
.invalidate = sdl2_invalidate,
.request_resize = sdl2_request_resize,
.request_fullscreen = sdl2_request_fullscreen,
.create_system_cursor = sdl2_create_system_cursor,
.create_bitmap_cursor = sdl2_create_bitmap_cursor,
.set_cursor = sdl2_set_cursor,
.initialize = sdl3_initialize,
.poll_events = sdl3_poll_events,
.create_window = sdl3_create_window,
.invalidate = sdl3_invalidate,
.request_resize = sdl3_request_resize,
.request_reposition = sdl3_request_reposition,
.request_fullscreen = sdl3_request_fullscreen,
.warp_pointer = sdl3_warp_pointer,
.query_pointer = sdl3_query_pointer,
.set_pointer_grab = sdl3_set_pointer_grab,
.create_system_cursor = sdl3_create_system_cursor,
.create_bitmap_cursor = sdl3_create_bitmap_cursor,
.set_cursor = sdl3_set_cursor,
};
#include <LibInput/KeyEvent.h>

View File

@@ -120,6 +120,13 @@ static void bananos_request_resize(PlatformWindow* window, uint32_t width, uint3
banan_window.window->request_resize(width, height);
}
static void bananos_request_reposition(PlatformWindow* window, int32_t x, int32_t y)
{
(void)window;
(void)x;
(void)y;
}
static void bananos_invalidate(PlatformWindow* window, const uint32_t* pixels, uint32_t x, uint32_t y, uint32_t width, uint32_t height)
{
auto& banan_window = *static_cast<BananWindow*>(window);
@@ -134,6 +141,31 @@ static void bananos_invalidate(PlatformWindow* window, const uint32_t* pixels, u
banan_window.window->invalidate(x, y, width, height);
}
static void bananos_request_fullscreen(PlatformWindow* window, bool fullscreen)
{
auto& banan_window = *static_cast<BananWindow*>(window);
banan_window.window->set_fullscreen(fullscreen);
}
static void bananos_warp_pointer(int32_t x, int32_t y, bool relative)
{
(void)x;
(void)y;
(void)relative;
}
static void bananos_query_pointer(int32_t* x, int32_t* y)
{
(void)x;
(void)y;
}
static void bananos_set_pointer_grab(PlatformWindow* window, bool grabbed)
{
(void)window;
(void)grabbed;
}
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> bananos_create_system_cursor(SystemCursorType type)
{
(void)type;
@@ -169,19 +201,17 @@ static void bananos_set_cursor(PlatformWindow* window, PlatformCursor* cursor)
}
}
static void bananos_request_fullscreen(PlatformWindow* window, bool fullscreen)
{
auto& banan_window = *static_cast<BananWindow*>(window);
banan_window.window->set_fullscreen(fullscreen);
}
PlatformOps g_platform_ops = {
.initialize = bananos_initialize,
.poll_events = bananos_poll_events,
.create_window = bananos_create_window,
.invalidate = bananos_invalidate,
.request_resize = bananos_request_resize,
.request_reposition = bananos_request_reposition,
.request_fullscreen = bananos_request_fullscreen,
.warp_pointer = bananos_warp_pointer,
.query_pointer = nullptr, /* bananos_query_pointer */
.set_pointer_grab = bananos_set_pointer_grab,
.create_system_cursor = bananos_create_system_cursor,
.create_bitmap_cursor = bananos_create_bitmap_cursor,
.set_cursor = bananos_set_cursor,

View File

@@ -290,6 +290,18 @@ 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);
@@ -304,14 +316,13 @@ int main()
case Object::Type::Pixmap:
case Object::Type::GraphicsContext:
case Object::Type::Font:
case Object::Type::Window:
break;
case Object::Type::Window:
ASSERT_NOT_REACHED();
case Object::Type::Extension:
{
auto& extension = object.object.get<Object::Extension>();
extension.destructor(extension);
break;
}
}
g_objects.remove(it);