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.
This commit is contained in:
2026-05-30 04:21:41 +03:00
parent 7fe5b0174f
commit f6a87f064f
11 changed files with 336 additions and 110 deletions

View File

@@ -711,6 +711,46 @@ WINDOW find_child_window(WINDOW wid, int32_t& x, int32_t& y)
return wid;
}
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)
return nullptr;
auto& object = *g_objects[window.parent];
ASSERT(object.type == Object::Type::Window);
return get_platform_window(object.object.get<Object::Window>());
}
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)
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;
}
}();
static CURSOR active_cid = None;
if (cid == active_cid)
return;
const auto& window = g_objects[wid]->object.get<Object::Window>();
g_platform_ops.set_cursor(get_platform_window(window), get_cursor_safe(cid));
active_cid = cid;
}
static void on_root_client_message(const xEvent& event)
{
@@ -930,7 +970,7 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
auto& window = TRY_REF(get_window(client_info, request.window, opcode));
CURSOR cursor_id = None;
BAN::Optional<CURSOR> cursor_id = None;
BAN::Optional<uint32_t> background;
for (size_t i = 0; i < 32; i++)
@@ -967,25 +1007,11 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
}
}
if (window.cursor != cursor_id)
if (cursor_id.has_value())
{
// FIXME: show cursor if wid is hovered
if (window.platform_window)
{
if (g_platform_ops.set_cursor)
{
const auto& cursor = get_cursor_safe(cursor_id);
g_platform_ops.set_cursor(
window.platform_window.ptr(),
cursor.pixels.data(),
cursor.width, cursor.height,
cursor.origin_x, cursor.origin_y
);
}
}
window.cursor = cursor_id;
window.cursor = cursor_id.value();
if (window.hovered)
update_cursor(request.window, window.cursor_x, window.cursor_y);
}
if (background.has_value())
@@ -2613,17 +2639,12 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
static_cast<uint32_t>(request.backBlue >> 8) << 8 |
static_cast<uint32_t>(request.backGreen >> 8) << 0;
Object::Cursor cursor {
.width = source.width,
.height = source.height,
.origin_x = request.x,
.origin_y = request.y,
};
TRY(cursor.pixels.resize(cursor.width * cursor.height));
BAN::Vector<uint32_t> pixels;
TRY(pixels.resize(source.width * source.height));
auto* source_data_u32 = reinterpret_cast<const uint32_t*>(source.data.data());
for (size_t i = 0; i < cursor.width * cursor.height; i++)
cursor.pixels[i] = 0xFF000000 | (source_data_u32[i] ? foreground : background);
for (size_t i = 0; i < source.width * source.height; i++)
pixels[i] = 0xFF000000 | (source_data_u32[i] ? foreground : background);
if (request.mask != None)
{
@@ -2633,9 +2654,17 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
ASSERT(mask.height == source.height);
auto* mask_data_u32 = reinterpret_cast<const uint32_t*>(mask.data.data());
for (size_t i = 0; i < cursor.width * cursor.height; i++)
for (size_t i = 0; i < source.width * source.height; i++)
if (!mask_data_u32[i])
cursor.pixels[i] = 0;
pixels[i] = 0;
}
BAN::UniqPtr<PlatformCursor> cursor;
if (g_platform_ops.create_bitmap_cursor)
{
auto cursor_or_error = g_platform_ops.create_bitmap_cursor(pixels.data(), source.width, source.height, request.x, request.y);
if (!cursor_or_error.is_error())
cursor = cursor_or_error.release_value();
}
TRY(client_info.objects.insert(request.cid));

View File

@@ -8,6 +8,8 @@ BAN::ErrorOr<void> handle_packet(Client& client_info, BAN::ConstByteSpan packet)
void invalidate_window(WINDOW wid, int32_t x, int32_t y, int32_t w, int32_t h);
void send_exposure_recursive(WINDOW wid);
void update_cursor(WINDOW wid, int32_t x, int32_t y);
BAN::ErrorOr<void> destroy_window(Client& client_info, WINDOW wid);
WINDOW find_child_window(WINDOW wid, int32_t& x, int32_t& y);

View File

@@ -41,15 +41,6 @@ struct Object
Type type;
struct Cursor
{
uint32_t width;
uint32_t height;
int32_t origin_x;
int32_t origin_y;
BAN::Vector<uint32_t> pixels;
};
struct Window
{
Client& owner;
@@ -57,6 +48,8 @@ struct Object
bool mapped { false };
bool focused { false };
bool fullscreen { false };
bool hovered { false };
uint8_t depth { 0 };
int32_t x { 0 };
int32_t y { 0 };
@@ -136,7 +129,7 @@ struct Object
void (*destructor)(Extension&);
};
BAN::Variant<Cursor, Window, Pixmap, GraphicsContext, BAN::RefPtr<PCFFont>, Extension> object;
BAN::Variant<Window, Pixmap, GraphicsContext, BAN::RefPtr<PCFFont>, BAN::UniqPtr<PlatformCursor>, Extension> object;
};
struct Client

View File

@@ -294,32 +294,6 @@ static void update_cursor_position_recursive(WINDOW wid, int32_t new_x, int32_t
}
}
static void update_cursor(WINDOW wid, int32_t old_x, int32_t old_y, int32_t new_x, int32_t new_y)
{
const auto old_wid = find_child_window(wid, old_x, old_y);
const auto new_wid = find_child_window(wid, new_x, new_y);
if (old_wid == new_wid || old_wid == None || new_wid == None)
return;
const auto& old_window = g_objects[old_wid]->object.get<Object::Window>();
const auto& new_window = g_objects[new_wid]->object.get<Object::Window>();
if (old_window.cursor == new_window.cursor)
return;
auto& window = g_objects[wid]->object.get<Object::Window>();
const auto& cursor = get_cursor_safe(window.cursor);
if (g_platform_ops.set_cursor)
{
g_platform_ops.set_cursor(
window.platform_window.ptr(),
cursor.pixels.data(),
cursor.width, cursor.height,
cursor.origin_x, cursor.origin_y
);
}
}
static BAN::Vector<WINDOW> get_path_to_child(WINDOW wid, int32_t x, int32_t y)
{
BAN::Vector<WINDOW> result;
@@ -474,6 +448,12 @@ static void send_enter_and_leave_events(WINDOW old_wid, int32_t old_x, int32_t o
event.u.u.detail = detail;
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;
}
void on_mouse_move_event(WINDOW wid, int32_t x, int32_t y)
@@ -482,10 +462,11 @@ void on_mouse_move_event(WINDOW wid, int32_t x, int32_t y)
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
update_cursor(wid, window.cursor_x, window.cursor_y, x, y);
update_cursor(wid, x, y);
{
static WINDOW old_wid = g_root.windowId;
auto it = g_objects.find(old_wid);
if (it == g_objects.end())
{
@@ -497,6 +478,7 @@ void on_mouse_move_event(WINDOW wid, int32_t x, int32_t y)
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;
}

View File

@@ -385,6 +385,8 @@ static BAN::ErrorOr<BAN::RefPtr<PCFFont>> parse_font(const BAN::String& path)
font->font_ascent = font->max_bounds.ascent;
font->font_descent = font->max_bounds.descent;
font->is_cursor_font = (path == "fonts/misc/cursor.pcf.gz"_sv);
return font;
}
@@ -938,6 +940,49 @@ BAN::ErrorOr<void> create_glyph_cursor(Client& client_info, BAN::ConstByteSpan p
const auto& source_font = TRY(get_fontable(client_info, request.source, X_CreateGlyphCursor));
// Try to use system cursor for known cursors
if (source_font->is_cursor_font && request.mask == request.source && request.sourceChar + 1 == request.maskChar && g_platform_ops.create_system_cursor)
{
BAN::Optional<SystemCursorType> type;
switch (request.sourceChar)
{
case 68: type = SystemCursorType::Pointer; break;
case 152: type = SystemCursorType::Text; break;
case 150: type = SystemCursorType::Wait; break;
case 60: type = SystemCursorType::Hand; break;
case 92: type = SystemCursorType::Help; break;
case 52: type = SystemCursorType::Move; break;
case 88: type = SystemCursorType::Forbidden; break;
case 138: type = SystemCursorType::ResizeN; break;
case 96: type = SystemCursorType::ResizeE; break;
case 16: type = SystemCursorType::ResizeS; break;
case 70: type = SystemCursorType::ResizeW; break;
case 134: type = SystemCursorType::ResizeNW; break;
case 136: type = SystemCursorType::ResizeNE; break;
case 12: type = SystemCursorType::ResizeSW; break;
case 14: type = SystemCursorType::ResizeSE; break;
case 108: type = SystemCursorType::ResizeHorizontal; break;
case 116: type = SystemCursorType::ResizeVertical; break;
}
if (type.has_value())
{
auto cursor_or_error = g_platform_ops.create_system_cursor(type.value());
if (!cursor_or_error.is_error())
{
TRY(client_info.objects.insert(request.cid));
TRY(g_objects.insert(
request.cid,
TRY(BAN::UniqPtr<Object>::create(Object {
.type = Object::Type::Cursor,
.object = BAN::move(cursor_or_error.value()),
}))
));
return {};
}
}
}
auto source_glyph_index = source_font->find_glyph(request.sourceChar);
if (!source_glyph_index.has_value())
{
@@ -958,19 +1003,14 @@ BAN::ErrorOr<void> create_glyph_cursor(Client& client_info, BAN::ConstByteSpan p
const uint32_t source_width = source_ci.rightSideBearing - source_ci.leftSideBearing;
const uint32_t source_height = source_ci.ascent + source_ci.descent;
Object::Cursor cursor {
.width = source_width,
.height = source_height,
.origin_x = -source_ci.leftSideBearing,
.origin_y = source_ci.ascent,
};
TRY(cursor.pixels.resize(cursor.width * cursor.height));
BAN::Vector<uint32_t> pixels;
TRY(pixels.resize(source_width * source_height));
for (uint32_t y = 0; y < source_height; y++)
{
const uint8_t* row_base = source_font->bitmap.data() + source_glyph.bitmap_offset + (source_width + 7) / 8 * y;
for (uint32_t x = 0; x < source_width; x++)
cursor.pixels[y * source_width + x] = 0xFF000000 | ((row_base[x / 8] & (1 << (x % 8))) ? foreground : background);
pixels[y * source_width + x] = 0xFF000000 | ((row_base[x / 8] & (1 << (x % 8))) ? foreground : background);
}
if (request.mask != None)
@@ -1008,11 +1048,19 @@ BAN::ErrorOr<void> create_glyph_cursor(Client& client_info, BAN::ConstByteSpan p
const uint8_t* row_base = mask_font->bitmap.data() + mask_glyph.bitmap_offset + (mask_width + 7) / 8 * mask_y;
if (!(row_base[mask_x / 8] & (1 << (mask_x % 8))))
cursor.pixels[src_y * source_width + src_x] = 0;
pixels[src_y * source_width + src_x] = 0;
}
}
}
BAN::UniqPtr<PlatformCursor> cursor;
if (g_platform_ops.create_bitmap_cursor)
{
auto cursor_or_error = g_platform_ops.create_bitmap_cursor(pixels.data(), source_width, source_height, -source_ci.leftSideBearing, source_ci.ascent);
if (!cursor_or_error.is_error())
cursor = cursor_or_error.release_value();
}
TRY(client_info.objects.insert(request.cid));
TRY(g_objects.insert(
request.cid,

View File

@@ -40,6 +40,8 @@ struct PCFFont : public BAN::RefCounted<PCFFont>, public BAN::Weakable<PCFFont>
BAN::Vector<MapEntry> map;
BAN::Vector<uint8_t> bitmap;
bool is_cursor_font;
BAN::Optional<uint16_t> find_glyph(uint16_t codepoint) const
{
size_t l = 0;

View File

@@ -9,6 +9,11 @@ struct PlatformWindow
virtual ~PlatformWindow() = default;
};
struct PlatformCursor
{
virtual ~PlatformCursor() = default;
};
enum class WindowType
{
Popup,
@@ -16,6 +21,27 @@ enum class WindowType
Utility,
};
enum class SystemCursorType
{
Pointer,
Text,
Wait,
Hand,
Help,
Move,
Forbidden,
ResizeN,
ResizeE,
ResizeS,
ResizeW,
ResizeNW,
ResizeNE,
ResizeSW,
ResizeSE,
ResizeVertical,
ResizeHorizontal,
};
// initialize, poll_events, create_window and invalidate are required
struct PlatformOps
{
@@ -31,7 +57,11 @@ struct PlatformOps
void (*request_resize)(PlatformWindow*, uint32_t width, uint32_t height);
/* Request new fullscreen state, can be async */
void (*request_fullscreen)(PlatformWindow*, bool fullscreen);
/* Create a system cursor */
BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> (*create_system_cursor)(SystemCursorType);
/* Create cursor from custom bitmap */
BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> (*create_bitmap_cursor)(const uint32_t* pixels, uint32_t width, uint32_t height, int32_t origin_x, int32_t origin_y);
/* Set custom cursor */
void (*set_cursor)(PlatformWindow*, const uint32_t* pixels, uint32_t width, uint32_t height, int32_t origin_x, int32_t origin_y);
void (*set_cursor)(PlatformWindow*, PlatformCursor*);
};
extern PlatformOps g_platform_ops;

View File

@@ -39,6 +39,17 @@ struct SDLWindow final : public PlatformWindow
SDL_Texture* texture { nullptr };
};
struct SDLCursor final : public PlatformCursor
{
~SDLCursor()
{
if (cursor != nullptr)
SDL_FreeCursor(cursor);
}
SDL_Cursor* cursor { nullptr };
};
static int s_eventfd { -1 };
struct Keymap
@@ -48,6 +59,8 @@ struct Keymap
};
static Keymap s_sdl_keymap;
static SDL_Cursor* s_default_cursor { nullptr };
static void* sdl2_thread(void*)
{
for (;;)
@@ -77,6 +90,8 @@ static bool sdl2_initialize(uint32_t* display_w, uint32_t* display_h)
*display_w = DM.w;
*display_h = DM.h;
s_default_cursor = SDL_GetCursor();
s_eventfd = eventfd(0, 0);
if (s_eventfd == -1)
{
@@ -307,31 +322,115 @@ static void sdl2_invalidate(PlatformWindow* window, const uint32_t* src_pixels,
SDL_RenderPresent(sdl_window.renderer);
}
static void sdl2_set_cursor(PlatformWindow* window, const uint32_t* pixels, uint32_t width, uint32_t height, int32_t origin_x, int32_t origin_y)
{
auto& sdl_window = *static_cast<SDLWindow*>(window);
(void)sdl_window;
(void)pixels;
(void)width;
(void)height;
(void)origin_x;
(void)origin_y;
}
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);
}
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> sdl2_create_system_cursor(SystemCursorType type)
{
SDL_SystemCursor sdl_type;
switch (type)
{
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;
}
auto cursor = TRY(BAN::UniqPtr<SDLCursor>::create());
cursor->cursor = SDL_CreateSystemCursor(sdl_type);
if (cursor->cursor == nullptr)
{
dwarnln("Could not create SDL system cursor: {}", SDL_GetError());
return BAN::Error::from_errno(EFAULT);
}
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)
{
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);
if (surface == nullptr)
{
dwarnln("Could not create SDL surface for cursor: {}", SDL_GetError());
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);
if (cursor->cursor == nullptr)
{
dwarnln("Could not create SDL color cursor: {}", SDL_GetError());
return BAN::Error::from_errno(EFAULT);
}
return BAN::UniqPtr<PlatformCursor>(BAN::move(cursor));
}
static void sdl2_set_cursor(PlatformWindow*, PlatformCursor* cursor)
{
if (cursor == nullptr)
SDL_SetCursor(s_default_cursor);
else
{
auto& sdl_cursor = *static_cast<SDLCursor*>(cursor);
SDL_SetCursor(sdl_cursor.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,
.set_cursor = sdl2_set_cursor,
.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,
};
#include <LibInput/KeyEvent.h>

View File

@@ -109,13 +109,12 @@ BAN::ErrorOr<DrawableInfo> get_drawable_info(Client& client_info, CARD32 drawabl
return info;
}
Object::Cursor& get_cursor_safe(CURSOR cid)
PlatformCursor* get_cursor_safe(CURSOR cid)
{
static Object::Cursor dummy {};
auto it = g_objects.find(cid);
if (it == g_objects.end())
return dummy;
return nullptr;
if (it->value->type != Object::Type::Cursor)
return dummy;
return it->value->object.get<Object::Cursor>();
return nullptr;
return it->value->object.get<BAN::UniqPtr<PlatformCursor>>().ptr();
}

View File

@@ -14,4 +14,4 @@ BAN::ErrorOr<Object::Pixmap&> get_pixmap(Client& client_info, CARD32 pid, BYTE o
BAN::ErrorOr<Object::GraphicsContext&> get_gc(Client& client_info, CARD32 gc, BYTE op_major, BYTE op_minor = 0);
BAN::ErrorOr<DrawableInfo> get_drawable_info(Client& client_info, CARD32 drawable, BYTE op_major, BYTE op_minor = 0);
Object::Cursor& get_cursor_safe(CURSOR cid);
PlatformCursor* get_cursor_safe(CURSOR cid);

View File

@@ -1,6 +1,8 @@
#include "../Events.h"
#include "../Platform.h"
#include <BAN/Vector.h>
#include <LibGUI/Window.h>
struct BananWindow final : public PlatformWindow
@@ -14,6 +16,15 @@ struct BananWindow final : public PlatformWindow
BAN::UniqPtr<LibGUI::Window> window;
};
struct BananCursor final : public PlatformCursor
{
BAN::Vector<uint32_t> pixels;
uint32_t width;
uint32_t height;
int32_t origin_x;
int32_t origin_y;
};
static bool bananos_initialize(uint32_t* display_w, uint32_t* display_h)
{
auto attributes = LibGUI::Window::default_attributes;
@@ -123,10 +134,39 @@ static void bananos_invalidate(PlatformWindow* window, const uint32_t* pixels, u
banan_window.window->invalidate(x, y, width, height);
}
static void bananos_set_cursor(PlatformWindow* window, const uint32_t* pixels, uint32_t width, uint32_t height, int32_t origin_x, int32_t origin_y)
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> bananos_create_system_cursor(SystemCursorType type)
{
(void)type;
return BAN::Error::from_errno(ENOTSUP);
}
static BAN::ErrorOr<BAN::UniqPtr<PlatformCursor>> bananos_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<BananCursor>::create());
cursor->width = width;
cursor->height = height;
cursor->origin_x = origin_x;
cursor->origin_y = origin_y;
TRY(cursor->pixels.resize(width * height));
memcpy(cursor->pixels.data(), pixels, cursor->pixels.size() * 4);
return BAN::UniqPtr<PlatformCursor>(BAN::move(cursor));
}
static void bananos_set_cursor(PlatformWindow* window, PlatformCursor* cursor)
{
if (window == nullptr)
return;
auto& banan_window = *static_cast<BananWindow*>(window);
banan_window.window->set_cursor(width, height, { pixels, width * height }, origin_x, origin_y);
if (cursor == nullptr)
banan_window.window->set_cursor(0, 0, {}, 0, 0);
else
{
auto& banan_cursor = *static_cast<BananCursor*>(cursor);
banan_window.window->set_cursor(banan_cursor.width, banan_cursor.height, banan_cursor.pixels.span(), banan_cursor.origin_x, banan_cursor.origin_y);
}
}
static void bananos_request_fullscreen(PlatformWindow* window, bool fullscreen)
@@ -136,11 +176,13 @@ static void bananos_request_fullscreen(PlatformWindow* window, bool 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_fullscreen = bananos_request_fullscreen,
.set_cursor = bananos_set_cursor,
.initialize = bananos_initialize,
.poll_events = bananos_poll_events,
.create_window = bananos_create_window,
.invalidate = bananos_invalidate,
.request_resize = bananos_request_resize,
.request_fullscreen = bananos_request_fullscreen,
.create_system_cursor = bananos_create_system_cursor,
.create_bitmap_cursor = bananos_create_bitmap_cursor,
.set_cursor = bananos_set_cursor,
};