Compare commits

...

4 Commits

Author SHA1 Message Date
16d57d0194 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 05:01:11 +03:00
f1a0738d13 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 04:55:43 +03:00
b81ff29e00 Handle external window leave events 2026-06-01 04:55:43 +03:00
0dc88d09bf Don't create platform window for windows without WM_CLASS
gtk and other toolkits create a lot of dummy windows that should not be
visible.
2026-06-01 04:55:43 +03:00
5 changed files with 65 additions and 29 deletions

View File

@@ -374,6 +374,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);
@@ -506,17 +509,26 @@ 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,
@@ -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);
@@ -1932,12 +1942,15 @@ 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;
const auto child_wid = find_child_window(wid, event_x, event_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;
}
xQueryPointerReply reply {
.type = X_Reply,
@@ -1945,11 +1958,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>(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),
.mask = static_cast<KeyButMask>(g_keymask | g_butmask),
};
TRY(encode(client_info.output_buffer, reply));

View File

@@ -453,7 +453,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 +479,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

@@ -9,6 +9,7 @@ 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

@@ -215,6 +215,9 @@ 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;
}

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,15 +316,14 @@ 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);
}