// ===================================================================================================================== // fennec, a free and open source game engine // Copyright © 2025 - 2026 Medusa Slockbower // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // ===================================================================================================================== #include #include #include #include #include #include #include #include #include #include #if FENNEC_HAS_LIBDECOR #include #endif using namespace fennec; wayland_window::wayland_window(display_server* server, const config& cfg, window* parent) : window_base(server, cfg, parent) , surface(nullptr) , xdgsurface(nullptr) { } wayland_window::~wayland_window() { } void wayland_window::initialize() { static constexpr wl_surface_listener surface_listener = { .enter = _wl_surface_listen_enter, .leave = _wl_surface_listen_leave, .preferred_buffer_scale = _wl_surface_listen_preferred_buffer_scale, .preferred_buffer_transform = _wl_surface_listen_preferred_buffer_transform }; static constexpr xdg_surface_listener xdg_surface_listener = { .configure = _xdg_surface_listen_configure }; static constexpr xdg_toplevel_listener xdg_toplevel_listener = { .configure = _xdg_toplevel_listen_configure, .close = _xdg_toplevel_listen_close, .configure_bounds = _xdg_toplevel_listen_configure_bounds, .wm_capabilities = _xdg_toplevel_listen_wm_capabilities }; static constexpr wl_callback_listener frame_callback_listener = { .done = _wl_frame_listen_done }; if (is_visible()) { return; } if (is_running()) { // reshow window return; } wayland_server* wl_server = static_cast(server); surface = wl_compositor_create_surface(wl_server->compositor); wl_surface_add_listener(surface, &surface_listener, this); #if FENNEC_HAS_LIBDECOR static libdecor_frame_interface libdecor_frame_listener = { .configure = _libdecor_frame_listen_configure, .close = _libdecor_frame_listen_close, .commit = _libdecor_frame_listen_commit, .dismiss_popup = _libdecor_frame_listen_dismiss_popup, .reserved0 = nullptr, .reserved1 = nullptr, .reserved2 = nullptr, .reserved3 = nullptr, .reserved4 = nullptr, .reserved5 = nullptr, .reserved6 = nullptr, .reserved7 = nullptr, .reserved8 = nullptr, .reserved9 = nullptr, }; gfx_surface = unique_ptr(wl_server->get_gfx_context()->create_surface(this)); if (wl_server->has_libdecor) { libdecorframe = libdecor_decorate(wl_server->libdecor, surface, &libdecor_frame_listener, this); libdecor_frame_set_app_id(libdecorframe, cfg.title.cstr()); libdecor_frame_set_title(libdecorframe, cfg.title.cstr()); libdecor_frame_map(libdecorframe); xdgsurface = libdecor_frame_get_xdg_surface(libdecorframe); xdgtoplevel = libdecor_frame_get_xdg_toplevel(libdecorframe); } else #endif { xdgsurface = xdg_wm_base_get_xdg_surface(wl_server->xdg, surface); xdg_surface_add_listener(xdgsurface, &xdg_surface_listener, this); xdgtoplevel = xdg_surface_get_toplevel(xdgsurface); xdg_toplevel_add_listener(xdgtoplevel, &xdg_toplevel_listener, this); frame_callback = wl_surface_frame(surface); wl_callback_add_listener(frame_callback, &frame_callback_listener, this); wl_surface_commit(surface); } wl_display_roundtrip(wl_server->display); wl_display_roundtrip(wl_server->display); state.flags.set(state_visible); state.flags.set(state_running); } void wayland_window::shutdown() { if (not is_running()) { return; } wayland_server* wl_server = static_cast(server); #if FENNEC_HAS_LIBDECOR if (libdecorframe) { libdecor_frame_unref(libdecorframe); xdgtoplevel = nullptr; xdgsurface = nullptr; } #endif if (gfx_surface) { gfx_surface.reset(); } if (frame_callback) { wl_callback_destroy(frame_callback); frame_callback = nullptr; } if (xdgtoplevel) { xdg_toplevel_destroy(xdgtoplevel); xdgtoplevel = nullptr; } if (xdgsurface) { xdg_surface_destroy(xdgsurface); xdgsurface = nullptr; } if (surface) { wl_surface_destroy(surface); surface = nullptr; } wl_display_roundtrip(wl_server->display); state.flags.clear(state_visible); state.flags.clear(state_running); } void* wayland_window::get_native_handle() { return surface; } bool wayland_window::set_flag(uint8_t flag, bool value) { // Do nothing if already set if (cfg.flags.test(flag) == value) { return false; } if (not is_running()) { return false; } switch (flag) { case flag_always_on_top: break; case flag_borderless: #if FENNEC_HAS_LIBDECOR if (libdecorframe) { bool vis = libdecor_frame_is_visible(libdecorframe); bool tgt = not value; if (vis != tgt) { libdecor_frame_set_visibility(libdecorframe, tgt); } } else #endif // FENNEC_HAS_LIBDECOR { } break; case flag_modal: break; case flag_pass_mouse: break; case flag_popup: break; case flag_resizable: break; case flag_transparent: break; case flag_no_focus: break; default: logger::log("Invalid flag passed to window::set_flag."); break; } // Store the value cfg.flags.store(flag, value); return true; } // Private Helpers ===================================================================================================== void wayland_window::_update_size(const ivec2& size) { //bool size_changed = any(notEqual(size, state.rect.size)); state.rect.size = size; if (gfx_surface) { gfx_surface->resize(size); } #if FENNEC_HAS_LIBDECOR if (libdecorframe) { libdecor_state* state = libdecor_state_new(size.x, size.y); libdecor_frame_commit(libdecorframe, state, libdecorcfg); libdecor_state_free(state); libdecorcfg = nullptr; } #endif } // Listeners =========================================================================================================== // Surface Listeners void wayland_window::_wl_surface_listen_enter(void*, wl_surface*, wl_output*) {} void wayland_window::_wl_surface_listen_leave(void*, wl_surface*, wl_output*) {} void wayland_window::_wl_surface_listen_preferred_buffer_scale(void*, wl_surface*, int32_t) {} void wayland_window::_wl_surface_listen_preferred_buffer_transform(void*, wl_surface*, uint32_t) {} // Frame Listeners void wayland_window::_wl_frame_listen_done(void*, wl_callback*, uint32_t) {} // XDG Listeners void wayland_window::_xdg_surface_listen_configure(void*, xdg_surface* xdg, uint32_t serial) { xdg_surface_ack_configure(xdg, serial); } void wayland_window::_xdg_toplevel_listen_configure(void*, xdg_toplevel*, int32_t, int32_t, wl_array*) {} void wayland_window::_xdg_toplevel_listen_configure_bounds(void*, xdg_toplevel*, int32_t, int32_t) {} void wayland_window::_xdg_toplevel_listen_close(void* data, xdg_toplevel*) { wayland_window* window = static_cast(data); window->shutdown(); } void wayland_window::_xdg_toplevel_listen_wm_capabilities(void*, xdg_toplevel*, wl_array*) {} // Libdecor Listeners #if FENNEC_HAS_LIBDECOR void wayland_window::_libdecor_frame_listen_configure(libdecor_frame* frame, libdecor_configuration* cfg, void* data) { wayland_window* window = static_cast(data); ivec2 size = window->state.rect.size; libdecor_configuration_get_content_size(cfg, frame, &size.x, &size.y); size.x = size.x == 0 ? window->state.rect.size.x : size.x; size.y = size.y == 0 ? window->state.rect.size.y : size.y; size.x = size.x == 0 ? window->cfg.rect.size.x : size.x; size.y = size.y == 0 ? window->cfg.rect.size.y : size.y; assertf(size.x != 0 or size.y != 0, "Invalid window size!"); libdecor_window_state state = LIBDECOR_WINDOW_STATE_NONE; window->state.mode = mode_windowed; window->state.flags.clear(state_suspended); window->libdecorcfg = cfg; if (libdecor_configuration_get_window_state(cfg, &state)) { if (state & LIBDECOR_WINDOW_STATE_MAXIMIZED) { window->state.mode = mode_maximized; } if (state & LIBDECOR_WINDOW_STATE_FULLSCREEN) { window->state.mode = mode_fullscreen; } if (state & LIBDECOR_WINDOW_STATE_SUSPENDED) { window->state.flags.set(state_suspended); } } window->_update_size(size); } void wayland_window::_libdecor_frame_listen_close(libdecor_frame*, void* data) { wayland_window* window = static_cast(data); window->shutdown(); } void wayland_window::_libdecor_frame_listen_commit(libdecor_frame*, void* data) { wayland_window* window = static_cast(data); window->gfx_surface->swap(); } void wayland_window::_libdecor_frame_listen_dismiss_popup(libdecor_frame*, const char*, void*) {} #endif