Files
xbanan/xbanan/Events.cpp
2026-06-07 22:00:42 +03:00

560 lines
15 KiB
C++

#include "Base.h"
#include "Definitions.h"
#include "Keymap.h"
#include "Utils.h"
#include <X11/X.h>
#include <X11/Xatom.h>
#include <time.h>
#include <sys/epoll.h>
void register_event_fd(int fd, void* data)
{
epoll_event event { .events = EPOLLIN, .data = { .fd = fd } };
epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &event);
MUST(g_epoll_thingies.insert(fd, {
.type = EpollThingy::Type::Event,
.value = data,
}));
}
void unregister_event_fd(int fd)
{
epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
g_epoll_thingies.remove(fd);
}
void on_window_close_event(WINDOW wid)
{
static CARD32 WM_PROTOCOLS = g_atoms_name_to_id["WM_PROTOCOLS"_sv];
static CARD32 WM_DELETE_WINDOW = g_atoms_name_to_id["WM_DELETE_WINDOW"_sv];
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
const bool supports_wm_delete_winow =
[&window]
{
auto wm_protocols_it = window.properties.find(WM_PROTOCOLS);
if (wm_protocols_it == window.properties.end())
return false;
const auto& wm_protocols = wm_protocols_it->value;
if (wm_protocols.type != XA_ATOM || wm_protocols.format != 32)
return false;
const auto atoms = BAN::ConstByteSpan(wm_protocols.data.span()).as_span<const CARD32>();
for (auto atom : atoms)
if (atom == WM_DELETE_WINDOW)
return true;
return false;
}();
if (!supports_wm_delete_winow)
MUST(destroy_window(window.owner, wid));
else
{
xEvent event { .u = {
.clientMessage = {
.window = wid,
.u = { .l = {
.type = WM_PROTOCOLS,
.longs0 = static_cast<INT32>(WM_DELETE_WINDOW),
.longs1 = static_cast<INT32>(time(nullptr)),
}}
}
}};
event.u.u.type = ClientMessage;
event.u.u.detail = 32;
event.u.u.sequenceNumber = window.owner.sequence;
MUST(encode(window.owner.output_buffer, event));
}
}
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;
MUST(window.double_buffer.resize(new_width * new_height));
for (auto& pixel : window.double_buffer)
pixel = window.background;
MUST(send_configure_notify(wid));
MUST(send_exposure_events_recursive(wid));
}
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;
MUST(send_configure_notify(wid));
}
void on_window_focus_event(WINDOW wid, bool focused)
{
if (focused)
g_focus_window = wid;
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
if (window.focused == focused)
return;
window.focused = focused;
// FIXME: handle children
xEvent event { .u = {
.focus = {
.window = wid,
.mode = NotifyNormal,
}
}};
event.u.u.type = focused ? FocusIn : FocusOut,
event.u.u.detail = NotifyNonlinear;
MUST(window.send_event(event, FocusChangeMask));
}
void on_window_fullscreen_event(WINDOW wid, bool is_fullscreen)
{
static CARD32 _NET_WM_STATE = g_atoms_name_to_id["_NET_WM_STATE"_sv];
static CARD32 _NET_WM_STATE_FULLSCREEN = g_atoms_name_to_id["_NET_WM_STATE_FULLSCREEN"_sv];
auto window_it = g_objects.find(wid);
if (window_it == g_objects.end() || window_it->value->type != Object::Type::Window)
return;
auto& window = window_it->value->object.get<Object::Window>();
window.fullscreen = is_fullscreen;
auto it = window.properties.find(_NET_WM_STATE);
if (it == window.properties.end())
it = MUST(window.properties.emplace(_NET_WM_STATE));
auto& _net_wm_state = it->value;
if (_net_wm_state.type != XA_ATOM || _net_wm_state.format != 32)
_net_wm_state = {};
_net_wm_state.type = XA_ATOM;
_net_wm_state.format = 32;
for (size_t i = 0; i + 4 <= _net_wm_state.data.size(); i += 4)
{
const auto atom = *reinterpret_cast<const CARD32*>(_net_wm_state.data.data() + i);
if (atom != _NET_WM_STATE_FULLSCREEN)
continue;
_net_wm_state.data.remove(i);
_net_wm_state.data.remove(i);
_net_wm_state.data.remove(i);
_net_wm_state.data.remove(i);
i -= 4;
}
if (is_fullscreen)
{
const size_t atom_count = _net_wm_state.data.size() / 4;
MUST(_net_wm_state.data.resize(_net_wm_state.data.size() + 4));
reinterpret_cast<CARD32*>(_net_wm_state.data.data())[atom_count] = _NET_WM_STATE_FULLSCREEN;
}
xEvent event = { .u = {
.property = {
.window = wid,
.atom = _NET_WM_STATE,
.time = static_cast<CARD32>(time(nullptr)),
.state = PropertyNewValue,
}
}};
event.u.u.type = PropertyNotify;
MUST(window.send_event(event, PropertyChangeMask));
}
static void send_key_button_pointer_event(WINDOW root_wid, BYTE detail, uint32_t event_mask, BYTE event_type, KeyButMask state)
{
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>();
event_x = window.cursor_x;
event_y = window.cursor_y;
}
const auto child_wid = find_child_window(root_wid, event_x, event_y);
auto wid = child_wid;
while (wid != None)
{
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
if (window.full_event_mask() & event_mask)
break;
event_x += window.x;
event_y += window.y;
wid = window.parent;
}
if (wid == None)
{
if (event_type == MotionNotify && g_butmask == 0)
return;
wid = root_wid;
}
auto& object = *g_objects[wid];
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 + event_x),
.rootY = static_cast<INT16>(root_y + event_y),
.eventX = static_cast<INT16>(event_x),
.eventY = static_cast<INT16>(event_y),
.state = state,
.sameScreen = xTrue,
}
}};
event.u.u.type = event_type,
event.u.u.detail = detail;
MUST(window.send_event(event, event_mask));
}
static void update_cursor_position_recursive(WINDOW wid, int32_t new_x, int32_t new_y)
{
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
window.cursor_x = new_x;
window.cursor_y = new_y;
for (auto child_wid : window.children)
{
auto& child_object = *g_objects[child_wid];
ASSERT(child_object.type == Object::Type::Window);
auto& child_window = child_object.object.get<Object::Window>();
update_cursor_position_recursive(child_wid, new_x - child_window.x, new_y - child_window.y);
}
}
static BAN::Vector<WINDOW> get_path_to_child(WINDOW wid, int32_t x, int32_t y)
{
BAN::Vector<WINDOW> result;
const auto window_contains =
[](const Object::Window& window, int32_t x, int32_t y) -> bool
{
return x >= 0 && y >= 0 && x < window.width && y < window.height;
};
for (;;)
{
const auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
const auto& window = object.object.get<Object::Window>();
if (!window_contains(window, x, y))
break;
MUST(result.push_back(wid));
const WINDOW old_wid = wid;
for (auto child_wid : window.children)
{
const auto& child_object = *g_objects[child_wid];
ASSERT(child_object.type == Object::Type::Window);
const auto& child_window = child_object.object.get<Object::Window>();
if (!window_contains(child_window, x - child_window.x, y - child_window.y))
continue;
wid = child_wid;
break;
}
if (old_wid == wid)
break;
}
return result;
}
static void send_enter_and_leave_events(WINDOW old_wid, int32_t old_x, int32_t old_y, WINDOW new_wid, int32_t new_x, int32_t new_y)
{
// FIXME: correct event_x and event_y values in events
const auto old_child_path = get_path_to_child(old_wid, old_x, old_y);
const auto new_child_path = get_path_to_child(new_wid, new_x, new_y);
size_t common_ancestors = 0;
while (common_ancestors < old_child_path.size() && common_ancestors < new_child_path.size())
{
if (old_child_path[common_ancestors] != new_child_path[common_ancestors])
break;
common_ancestors++;
}
if (old_child_path.size() == common_ancestors && new_child_path.size() == common_ancestors)
return;
const bool linear = (common_ancestors == old_child_path.size() || common_ancestors == new_child_path.size());
const auto get_flags =
[](WINDOW wid) -> BYTE
{
return ELFlagSameScreen | (g_focus_window == wid ? ELFlagFocus : 0);
};
size_t leave_events = old_child_path.size() - common_ancestors;
if (linear && common_ancestors && old_child_path.size() < new_child_path.size())
leave_events++;
size_t enter_events = new_child_path.size() - common_ancestors;
if (linear && common_ancestors && new_child_path.size() < old_child_path.size())
enter_events++;
for (size_t i = 0; i < leave_events; i++)
{
const bool first = (i == 0);
const BYTE detail =
[&]() -> BYTE {
if (!linear)
return first ? NotifyNonlinear : NotifyNonlinearVirtual;
if (!first)
return NotifyVirtual;
return (old_child_path.size() > new_child_path.size())
? NotifyAncestor
: NotifyInferior;
}();
const auto& wid = old_child_path[old_child_path.size() - i - 1];
auto& object = *g_objects[wid];
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>(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),
.mode = NotifyNormal,
.flags = get_flags(wid),
}
}};
event.u.u.type = LeaveNotify,
event.u.u.detail = detail;
MUST(window.send_event(event, LeaveWindowMask));
}
for (size_t i = 0; i < enter_events; i++)
{
const bool last = (i == enter_events - 1);
const BYTE detail =
[&]() -> BYTE {
if (!linear)
return last ? NotifyNonlinear : NotifyNonlinearVirtual;
if (!last)
return NotifyVirtual;
return (old_child_path.size() > new_child_path.size())
? NotifyInferior
: NotifyAncestor;
}();
const auto& wid = new_child_path[new_child_path.size() - enter_events + i];
auto& object = *g_objects[wid];
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>(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),
.mode = NotifyNormal,
.flags = get_flags(wid),
}
}};
event.u.u.type = EnterNotify,
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;
}
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)
{
auto& object = *g_objects[wid];
ASSERT(object.type == Object::Type::Window);
auto& window = object.object.get<Object::Window>();
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
{
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;
update_cursor_position_recursive(wid, x, y);
// TODO: optimize with PointerMotionHint
uint32_t event_mask = PointerMotionMask | PointerMotionHintMask;
if (g_butmask) event_mask |= ButtonMotionMask;
if (g_butmask & Button1Mask) event_mask |= Button1MotionMask;
if (g_butmask & Button2Mask) event_mask |= Button2MotionMask;
if (g_butmask & Button3Mask) event_mask |= Button3MotionMask;
if (g_butmask & Button4Mask) event_mask |= Button4MotionMask;
if (g_butmask & Button5Mask) event_mask |= Button5MotionMask;
send_key_button_pointer_event(wid, NotifyNormal, event_mask, MotionNotify, g_keymask | g_butmask);
}
void on_mouse_button_event(WINDOW wid, uint8_t xbutton, bool pressed)
{
uint16_t mask = 0;
switch (xbutton)
{
case Button1: mask = Button1Mask; break;
case Button2: mask = Button2Mask; break;
case Button3: mask = Button3Mask; break;
case Button4: mask = Button4Mask; break;
case Button5: mask = Button5Mask; break;
}
const auto old_state = g_keymask | g_butmask;
if (pressed)
g_butmask |= mask;
else
g_butmask &= ~mask;
send_key_button_pointer_event(
wid,
xbutton,
pressed ? ButtonPressMask : ButtonReleaseMask,
pressed ? ButtonPress : ButtonRelease,
old_state
);
}
void on_key_event(WINDOW wid, uint8_t scancode, uint8_t xmod, bool pressed)
{
const uint8_t xkeycode = scancode + g_keymap_min_keycode;
if (xkeycode < g_keymap_min_keycode || xkeycode > g_keymap_max_keycode)
return;
{
const uint8_t byte = xkeycode / 8;
const uint8_t mask = 1 << (xkeycode % 8);
if (pressed)
g_pressed_keys[byte] |= mask;
else
g_pressed_keys[byte] &= ~mask;
}
const auto old_state = g_keymask | g_butmask;
g_keymask = xmod;
send_key_button_pointer_event(
wid,
xkeycode,
pressed ? KeyPressMask : KeyReleaseMask,
pressed ? KeyPress : KeyRelease,
old_state
);
}
void on_keymap_changed()
{
for (auto& [id, object] : g_objects)
{
if (object->type != Object::Type::Window)
continue;
auto& client_info = object->object.get<Object::Window>().owner;
xEvent event {};
event.u.u.type = MappingNotify;
event.u.u.sequenceNumber = client_info.sequence;
event.u.mappingNotify.request = MappingKeyboard,
event.u.mappingNotify.firstKeyCode = g_keymap_min_keycode,
event.u.mappingNotify.count = g_keymap_size,
MUST(encode(client_info.output_buffer, event));
event.u.mappingNotify.request = MappingModifier;
MUST(encode(client_info.output_buffer, event));
}
}