#include #include #include #include // #include #include #include #include namespace lt::surface { template struct overloads: Ts... { using Ts::operator()...; }; void ensure_component_sanity(const SurfaceComponent &component); constexpr auto all_events_mask = KeyPressMask | // KeyReleaseMask | // ButtonPressMask | // ButtonReleaseMask | // EnterWindowMask | // LeaveWindowMask | // PointerMotionMask | // KeymapStateMask | // ExposureMask | // VisibilityChangeMask | // StructureNotifyMask | // FocusChangeMask | // ColormapChangeMask | // OwnerGrabButtonMask; System::System(Ref registry): m_registry(std::move(registry)) { ensure(m_registry, "Failed to initialize surface system: null registry"); ensure( m_registry->view().get_size() == 0, "Failed to initialize surface system: registry has surface component(s)" ); m_registry->connect_on_construct( [this](ecs::Registry ®istry, ecs::EntityId entity) { on_surface_construct(registry, entity); } ); m_registry->connect_on_destruct( [this](ecs::Registry ®istry, ecs::EntityId entity) { on_surface_destruct(registry, entity); } ); } System::~System() { if (!m_registry) { return; } try { // TODO(Light): make registry.remove not validate iterators auto entities_to_remove = std::vector {}; for (auto &[entity, surface] : m_registry->view()) { entities_to_remove.emplace_back(entity); } for (auto entity : entities_to_remove) { m_registry->remove(entity); } m_registry->disconnect_on_construct(); m_registry->disconnect_on_destruct(); } catch (const std::exception &exp) { log_err("Uncaught exception in surface::~System:"); log_err("\twhat: {}", exp.what()); } } void System::on_register() { } void System::on_unregister() { } void System::on_surface_construct(ecs::Registry ®istry, ecs::EntityId entity) { try { auto &surface = registry.get(entity); const auto &resolution = surface.get_resolution(); const auto &position = surface.get_position(); ensure_component_sanity(surface); // TODO(Light): refactor "environment" into standalone module // NOLINTNEXTLINE(concurrency-mt-unsafe) auto *display_env = std::getenv("DISPLAY"); ensure(display_env != nullptr, "DISPLAY env var not found!"); auto *display = XOpenDisplay(display_env); auto root_window = XDefaultRootWindow(display); auto border_width = 0; auto depth = int32_t { CopyFromParent }; auto window_class = CopyFromParent; auto *visual = (Visual *)CopyFromParent; auto attribute_value_mask = CWBackPixel | CWEventMask; auto attributes = XSetWindowAttributes { .background_pixel = 0xffafe9af, .event_mask = all_events_mask, }; typedef struct Hints { unsigned long flags; unsigned long functions; unsigned long decorations; long inputMode; unsigned long status; } Hints; auto main_window = XCreateWindow( display, root_window, position.x, position.y, resolution.x, resolution.y, border_width, depth, window_class, visual, attribute_value_mask, &attributes ); surface.m_native_data.display = display; surface.m_native_data.window = main_window; surface.m_native_data.wm_delete_message = XInternAtom(display, "WM_DELETE_WINDOW", False); XSetWMProtocols(display, main_window, &surface.m_native_data.wm_delete_message, 1); // code to remove decoration auto hints = std::array { 2, 0, 0, 0, 0 }; const auto motif_hints = XInternAtom(display, "_MOTIF_WM_HINTS", False); XChangeProperty( display, surface.m_native_data.window, motif_hints, motif_hints, 32, PropModeReplace, hints.data(), 5 ); XMapWindow(display, main_window); XStoreName(display, main_window, surface.m_title.c_str()); XFlush(display); if (!surface.is_visible()) { XUnmapWindow(display, main_window); } } catch (const std::exception &exp) { log_err("Exception thrown when on_constructing surface component"); log_err("\tentity: {}", entity); log_err("\twhat: {}", exp.what()); registry.remove(entity); throw; } } void System::on_surface_destruct(ecs::Registry ®istry, ecs::EntityId entity) { const auto &[display, window, _] = registry.get(entity).get_native_data(); if (!display) { log_wrn("Surface component destroyed with null display"); return; } XDestroyWindow(display, window); XCloseDisplay(display); } void System::handle_events(SurfaceComponent &surface) { auto &queue = surface.m_event_queue; queue.clear(); auto event = XEvent {}; auto &[display, window, wm_delete_message] = surface.m_native_data; XFlush(display); while (XEventsQueued(display, QueuedAlready) != 0) { XNextEvent(surface.m_native_data.display, &event); switch (event.type) { case KeyPress: { queue.emplace_back( static_cast(XLookupKeysym(&event.xkey, 0)) ); break; } case KeyRelease: { queue.emplace_back( static_cast(XLookupKeysym(&event.xkey, 0)) ); break; } case ButtonPress: { queue.emplace_back(static_cast(event.xbutton.button)); break; } case ButtonRelease: { queue.emplace_back(static_cast(event.xbutton.button)); break; } case FocusIn: { queue.emplace_back({}); break; } case FocusOut: { queue.emplace_back({}); break; } case ClientMessage: { if (event.xclient.data.l[0] == wm_delete_message) { queue.emplace_back({}); } break; } case MotionNotify: { queue.emplace_back(MouseMovedEvent { static_cast(event.xmotion.x), static_cast(event.xmotion.y), }); break; } case ConfigureNotify: { const auto [prev_width, prev_height] = surface.get_resolution(); const auto new_width = event.xconfigure.width; const auto new_height = event.xconfigure.height; if (prev_width != new_width || prev_height != new_height) { surface.m_resolution.x = new_width; surface.m_resolution.y = new_height; queue.emplace_back(ResizedEvent { static_cast(new_width), static_cast(new_height), }); } const auto [prev_x, prev_y] = surface.get_position(); const auto new_x = event.xconfigure.x; const auto new_y = event.xconfigure.y; if (prev_x != new_x || prev_y != new_y) { surface.m_position.x = new_x; surface.m_position.y = new_y; queue.emplace_back(MovedEvent { new_x, new_y, }); } break; } default: break; /* pass */ } } } void System::handle_requests(SurfaceComponent &surface) { const auto visitor = overloads { [&](const ModifyTitleRequest &request) { modify_title(surface, request); }, [&](const ModifyResolutionRequest &request) { modify_resolution(surface, request); }, [&](const ModifyPositionRequest &request) { modify_position(surface, request); }, [&](const ModifyVisibilityRequest &request) { modify_visiblity(surface, request); }, [&](const auto &) { log_err("Unknown surface request"); }, }; for (const auto &request : surface.peek_requests()) { std::visit(visitor, request); } surface.m_requests.clear(); } void System::modify_title(SurfaceComponent &surface, const ModifyTitleRequest &request) { surface.m_title = request.title; const auto &[display, window, _] = surface.get_native_data(); XStoreName(display, window, request.title.c_str()); } void System::modify_resolution(SurfaceComponent &surface, const ModifyResolutionRequest &request) { surface.m_resolution = request.resolution; const auto &[display, window, _] = surface.get_native_data(); const auto &[width, height] = request.resolution; XResizeWindow(display, window, width, height); } void System::modify_position(SurfaceComponent &surface, const ModifyPositionRequest &request) { surface.m_position = request.position; const auto &[display, window, _] = surface.get_native_data(); const auto &[x, y] = request.position; XMoveWindow(display, window, static_cast(x), static_cast(y)); } void System::modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityRequest &request) { const auto &[display, window, _] = surface.get_native_data(); surface.m_visible = request.visible; if (request.visible) { XMapWindow(display, window); } else { XUnmapWindow(display, window); } } void System::tick(app::TickInfo tick) { for (auto &dense : m_registry->view()) { auto &surface = dense.second; handle_requests(surface); handle_events(surface); } const auto now = std::chrono::steady_clock::now(); m_last_tick_result = app::TickResult { .info = tick, .duration = now - tick.start_time, .end_time = now, }; } void ensure_component_sanity(const SurfaceComponent &component) { auto [width, height] = component.get_resolution(); ensure(width != 0u, "Received bad values for surface component: width({}) == 0", width); ensure(height != 0u, "Received bad values for surface component: height({}) == 0", height); ensure( width < SurfaceComponent::max_dimension, "Received bad values for surface component: width({}) > max_dimension({})", width, SurfaceComponent::max_dimension ); ensure( height < SurfaceComponent::max_dimension, "Received bad values for surface component: height({}) > max_dimension({})", height, SurfaceComponent::max_dimension ); ensure( component.get_title().size() < SurfaceComponent::max_title_length, "Received bad values for surface component: title.size({}) > max_title_length({})", component.get_title().size(), SurfaceComponent::max_title_length ); } } // namespace lt::surface namespace lt { } // namespace lt