From 5dcb58f53ce8cbf44b23a329c6bb820909b23de6 Mon Sep 17 00:00:00 2001 From: Medusa Slockbower Date: Sun, 14 Dec 2025 15:47:11 -0500 Subject: [PATCH] - Setup wayland display and window. Window surface is created and appears in hotbar. Window is not visible. --- CMakeLists.txt | 5 + cmake/wayland.cmake | 3 +- include/fennec/containers/array.h | 1 + include/fennec/containers/bitfield.h | 5 +- include/fennec/core/logger.h | 88 +++++++++ include/fennec/filesystem/file.h | 28 +++ include/fennec/format/detail/_format.h | 14 ++ include/fennec/format/formatter.h | 7 + include/fennec/lang/intrinsics.h | 35 ++++ .../platform/interface/display_server.h | 21 +- include/fennec/platform/interface/platform.h | 17 +- include/fennec/platform/interface/window.h | 75 ++++++- .../fennec/platform/linux/wayland/server.h | 86 ++++++++ .../fennec/platform/linux/wayland/window.h | 82 ++++++++ include/fennec/rtti/type.h | 8 +- include/fennec/rtti/type_registry.h | 38 +++- source/core/log.cpp | 29 +++ source/core/logger.cpp | 47 +++++ source/filesystem/file.cpp | 23 ++- source/platform/interface/platform.cpp | 17 ++ .../platform/interface/window.cpp | 21 +- source/platform/linux/platform.cpp | 2 + source/platform/linux/wayland/server.cpp | 185 ++++++++++++++++++ source/platform/linux/wayland/window.cpp | 160 +++++++++++++++ test/tests/test_platform.h | 28 +++ 25 files changed, 985 insertions(+), 40 deletions(-) create mode 100644 include/fennec/core/logger.h create mode 100644 include/fennec/platform/linux/wayland/server.h create mode 100644 include/fennec/platform/linux/wayland/window.h create mode 100644 source/core/log.cpp create mode 100644 source/core/logger.cpp rename include/fennec/platform/linux/wayland/display_server.h => source/platform/interface/window.cpp (76%) create mode 100644 source/platform/linux/wayland/server.cpp create mode 100644 source/platform/linux/wayland/window.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d78c8dc..784103a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,6 +284,11 @@ add_library(fennec STATIC # EXTRA SOURCES ======================================================================================================== ${FENNEC_EXTRA_SOURCES} + source/platform/linux/wayland/window.cpp + source/platform/interface/window.cpp + include/fennec/core/logger.h + source/core/log.cpp + source/core/logger.cpp ) add_dependencies(fennec metaprogramming fennec-dependencies) diff --git a/cmake/wayland.cmake b/cmake/wayland.cmake index 1ccc421..4d1d30b 100644 --- a/cmake/wayland.cmake +++ b/cmake/wayland.cmake @@ -109,7 +109,8 @@ macro(fennec_check_wayland) include/fennec/platform/linux/wayland/lib/loader.h source/platform/linux/wayland/lib/loader.cpp # Fennec Files - include/fennec/platform/linux/wayland/display_server.h + include/fennec/platform/linux/wayland/window.h + include/fennec/platform/linux/wayland/server.h source/platform/linux/wayland/server.cpp ) fennec_add_definitions( diff --git a/include/fennec/containers/array.h b/include/fennec/containers/array.h index ef80b69..3069368 100644 --- a/include/fennec/containers/array.h +++ b/include/fennec/containers/array.h @@ -34,6 +34,7 @@ #include #include +#include namespace fennec { diff --git a/include/fennec/containers/bitfield.h b/include/fennec/containers/bitfield.h index 139f31f..22c2b74 100644 --- a/include/fennec/containers/bitfield.h +++ b/include/fennec/containers/bitfield.h @@ -33,6 +33,7 @@ #include #include +#include namespace fennec { @@ -49,7 +50,7 @@ public: : _bytes() { } - constexpr bitfield(const bool (&arr)[N]) + explicit constexpr bitfield(const bool (&arr)[N]) : _bytes() { for (size_t i = 0; i < arr; ++i) { this->store(i, arr[i]); @@ -57,7 +58,7 @@ public: } template - constexpr bitfield(const size_t (&arr)[I]) + explicit constexpr bitfield(const size_t (&arr)[I]) : _bytes() { for (size_t i : arr) { this->set(i); diff --git a/include/fennec/core/logger.h b/include/fennec/core/logger.h new file mode 100644 index 0000000..53c9f85 --- /dev/null +++ b/include/fennec/core/logger.h @@ -0,0 +1,88 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 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 . +// ===================================================================================================================== + +/// +/// \file logger.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + + +#ifndef FENNEC_CORE_LOGGER_H +#define FENNEC_CORE_LOGGER_H + +#include +#include +#include + +namespace fennec +{ + +class logger : public singleton { +public: + logger(); + ~logger(); + + static void log(const cstring& str, + uint32_t _line = FENNEC_BUILTIN_LINE(), + const char* const _file = FENNEC_BUILTIN_FILE() + ) { + logger& inst = instance(); + + if (inst._logfile.is_open()) { + inst._logfile.print(cstring(_file, strlen(_file))); + inst._logfile.printf("({}): ", _line); + inst._logfile.println(str); + } + + inst._cout->print(cstring(_file, strlen(_file))); + inst._cout->printf("({}): ", _line); + inst._cout->println(str); + } + + static void log(const string& str, + uint32_t _line = FENNEC_BUILTIN_LINE(), + const char* const _file = FENNEC_BUILTIN_FILE() + ) { + logger& inst = instance(); + + if (inst._logfile.is_open()) { + inst._logfile.print(cstring(_file, strlen(_file))); + inst._logfile.printf("({}): ", _line); + inst._logfile.println(str); + } + + inst._cout->print(cstring(_file, strlen(_file))); + inst._cout->printf("({}): ", _line); + inst._cout->println(str); + } + +private: + file _logfile; + file* _cout; +}; + +} + +#endif // FENNEC_CORE_LOGGER_H \ No newline at end of file diff --git a/include/fennec/filesystem/file.h b/include/fennec/filesystem/file.h index 3079ac9..d9e86ad 100644 --- a/include/fennec/filesystem/file.h +++ b/include/fennec/filesystem/file.h @@ -20,6 +20,7 @@ #define FENNEC_FILESYSTEM_FILE_H #include +#include #include #include @@ -108,6 +109,21 @@ public: /// \brief default constructor, initializes an empty stream file(); + file(const cstring& path, uint8_t mode) + : file() { + open(path, mode); + } + + file(const string& path, uint8_t mode) + : file() { + open(path, mode); + } + + file(const path& path, uint8_t mode) + : file() { + open(path, mode); + } + /// /// \brief default destructor, cleans up an open stream ~file(); @@ -302,6 +318,18 @@ public: // Printing Operations ================================================================================================= + void print(const cstring& str); + void print(const string& str); + + void println(const cstring& str); + void println(const string& str); + + template + void printf(const cstring& str, ArgsT&&...args) { + string fmt = fennec::format(str, fennec::forward(args)...); + this->print(cstring(fmt.cstr(), fmt.length())); + } + diff --git a/include/fennec/format/detail/_format.h b/include/fennec/format/detail/_format.h index 6bbfbfc..fd04183 100644 --- a/include/fennec/format/detail/_format.h +++ b/include/fennec/format/detail/_format.h @@ -80,6 +80,20 @@ struct _format_arg : _format_arg { } }; +// Polymorphic template specialization for x/r/xr value references +template +struct _format_arg : _format_arg { + _format_arg(const T& arg) : _format_arg(arg) { + } +}; + +// Polymorphic template specialization for x/r/xr value references +template +struct _format_arg : _format_arg { + _format_arg(const T& arg) : _format_arg(arg) { + } +}; + // Containing array for format args template struct _format_argarray { diff --git a/include/fennec/format/formatter.h b/include/fennec/format/formatter.h index 9bbda89..2060661 100644 --- a/include/fennec/format/formatter.h +++ b/include/fennec/format/formatter.h @@ -54,6 +54,13 @@ struct formatter { // strings ============================================================================================================= +template +struct formatter { + string operator()(const format_arg&, const char (&str)[N]) { + return string(str); + } +}; + template struct formatter { string operator()(const format_arg&, const char (&str)[N]) { diff --git a/include/fennec/lang/intrinsics.h b/include/fennec/lang/intrinsics.h index 90172c5..4162b1e 100644 --- a/include/fennec/lang/intrinsics.h +++ b/include/fennec/lang/intrinsics.h @@ -102,6 +102,9 @@ // Most major compilers support __has_builtin, notably GCC, MINGW, and CLANG #if defined(__has_builtin) + +// UTILITIES =========================================================================================================== + // addressof is very difficult to implement without intrinsics. #if __has_builtin(__builtin_addressof) # define FENNEC_HAS_BUILTIN_ADDRESSOF 1 @@ -118,6 +121,37 @@ # define FENNEC_HAS_BUILTIN_BIT_CAST 0 #endif +#if __has_builtin(__builtin_LINE) +# define FENNEC_HAS_BUILTIN_LINE 1 +# define FENNEC_BUILTIN_LINE() __builtin_LINE() +#else +# define FENNEC_HAS_BUILTIN_LINE 0 +#endif + +#if __has_builtin(__builtin_COLUMN) +# define FENNEC_HAS_BUILTIN_COLUMN 1 +# define FENNEC_BUILTIN_COLUMN() __builtin_COLUMN() +#else +# define FENNEC_HAS_BUILTIN_COLUMN 0 +#endif + +#if __has_builtin(__builtin_FILE) +# define FENNEC_HAS_BUILTIN_FILE 1 +# define FENNEC_BUILTIN_FILE() __builtin_FILE() +#else +# define FENNEC_HAS_BUILTIN_FILE 0 +#endif + +#if __has_builtin(__builtin_FUNCTION) +# define FENNEC_HAS_BUILTIN_FUNCTION 1 +# define FENNEC_BUILTIN_FUNCTION() __builtin_FUNCTION() +#else +# define FENNEC_HAS_BUILTIN_FUNCTION 0 +#endif + + +// PROPERTIES ========================================================================================================== + // Inconsistent without intrinsics #if __has_builtin(__is_abstract) # define FENNEC_HAS_BUILTIN_IS_ABSTRACT 1 @@ -142,6 +176,7 @@ # define FENNEC_HAS_BUILTIN_IS_CLASS #endif + // CONSTRUCTORS ======================================================================================================== // Difficult and Inconsistent without intrinsics diff --git a/include/fennec/platform/interface/display_server.h b/include/fennec/platform/interface/display_server.h index 7022bdf..653d8b0 100644 --- a/include/fennec/platform/interface/display_server.h +++ b/include/fennec/platform/interface/display_server.h @@ -35,11 +35,12 @@ #include #include #include +#include namespace fennec { -class display_server { +class display_server : public type_registry { // Typedefs/Constants/Enums ============================================================================================ public: @@ -90,19 +91,29 @@ enum feature_ : uint32_t { : platform(p) { } - virtual ~display_server() = default; + virtual ~display_server() { + } bool has_feature(uint32_t feature) const { - return _features.test(feature); + return features.test(feature); } virtual window* create_window(const window::config& conf) = 0; + window* get_window(size_t id) { + return id == window::nullid ? nullptr : windows[id]; + } -private: - featureset_t _features; + virtual void connect() = 0; + virtual void disconnect() = 0; + virtual bool connected() const = 0; + + +protected: + featureset_t features; + object_pool windows; FENNEC_RTTI_CLASS_ENABLE() { diff --git a/include/fennec/platform/interface/platform.h b/include/fennec/platform/interface/platform.h index 833637c..d05786a 100644 --- a/include/fennec/platform/interface/platform.h +++ b/include/fennec/platform/interface/platform.h @@ -19,12 +19,9 @@ #ifndef FENNEC_PLATFORM_INTERFACE_PLATFORM_H #define FENNEC_PLATFORM_INTERFACE_PLATFORM_H -#include -#include +#include #include -#include -#include -#include + #include #include #include @@ -63,7 +60,7 @@ namespace fennec { -class platform : singleton { +class platform : public singleton { public: using shared_object = struct shared_object; using symbol = void*; @@ -78,10 +75,14 @@ public: virtual symbol find_symbol(shared_object* obj, const cstring& name) = 0; virtual void initialize(); // Initialize Drivers and Contexts - virtual void shutdown(); // Close Drivers and Contexts + virtual void shutdown(); // Close Drivers and Contexts + + display_server* get_display_server() { return display.get(); } + +protected: + unique_ptr display; FENNEC_RTTI_CLASS_ENABLE() { - } }; diff --git a/include/fennec/platform/interface/window.h b/include/fennec/platform/interface/window.h index 3255a61..9c96c14 100644 --- a/include/fennec/platform/interface/window.h +++ b/include/fennec/platform/interface/window.h @@ -31,10 +31,10 @@ #ifndef FENNEC_PLATFORM_INTERFACE_WINDOW_H #define FENNEC_PLATFORM_INTERFACE_WINDOW_H -#include +#include +#include #include #include -#include namespace fennec { @@ -42,7 +42,23 @@ namespace fennec class window { // Structures & Typedefs =============================================================================================== public: - enum flag_ { + static constexpr size_t nullid = -1; + + enum mode_ : uint8_t { + mode_windowed = 0, + mode_minimized, + mode_maximized, + mode_fullscreen, + mode_exclusive_fullscreen, + }; + + enum vsync_ : uint8_t { + vsync_disabled = 0, + vsync_enabled, + vsync_adaptive + }; + + enum flag_ : uint8_t { flag_always_on_top = 0, // Window always appears on top level flag_borderless, // Window has no border decorations flag_child, // Window is a child window, and closes when the parent does @@ -53,6 +69,7 @@ public: flag_resizable, // Window can be resized through functions defined by Desktop Environment flag_transparent, // Window has an alpha value flag_visible, // Window is visible + flag_running, // Window is running flag_no_focus, // Window can be focused flag_count @@ -67,12 +84,64 @@ public: struct config { string title; flags_t flags; + uint8_t mode; + size_t parent; ivec2 size; accessibility accessibility; }; + window(display_server* server, size_t id, const config& conf) + : server(server), id(id) + , cfg(conf), root(nullptr) { + cfg.flags.clear(flag_visible); + } + + virtual ~window() = default; + + size_t get_id() const { return id; } + size_t get_parent_id() const { return cfg.parent; } + + window* get_parent() const; + + bool get_flag(uint8_t flag) const { return cfg.flags.test(flag); } + + bool is_always_on_top() const { return get_flag(flag_always_on_top); } + bool is_borderless() const { return get_flag(flag_borderless); } + bool is_child() const { return get_flag(flag_child); } + bool has_decorations() const { return get_flag(flag_decorations); } + bool is_modal() const { return get_flag(flag_modal); } + bool is_passing_mouse() const { return get_flag(flag_pass_mouse); } + bool is_popup() const { return get_flag(flag_popup); } + bool is_resizable() const { return get_flag(flag_resizable); } + bool is_transparent() const { return get_flag(flag_transparent); } + bool is_visible() const { return get_flag(flag_visible); } + bool is_running() const { return get_flag(flag_running); } + bool is_no_focus() const { return get_flag(flag_no_focus); } + + virtual bool set_flag(uint8_t flag, bool val) = 0; + + bool set_always_on_top(bool val) { return set_flag(flag_always_on_top, val); } + bool set_borderless(bool val) { return set_flag(flag_borderless, val); } + bool set_decorations(bool val) { return set_flag(flag_decorations, val); } + bool set_modal(bool val) { return set_flag(flag_modal, val); } + bool set_passing_mouse(bool val) { return set_flag(flag_pass_mouse, val); } + bool set_popup(bool val) { return set_flag(flag_popup, val); } + bool set_resizable(bool val) { return set_flag(flag_resizable, val); } + bool set_transparent(bool val) { return set_flag(flag_transparent, val); } + bool set_no_focus(bool val) { return set_flag(flag_no_focus, val); } + + virtual void show() = 0; + virtual void hide() = 0; + + virtual void dispatch() = 0; + +protected: + display_server* const server; + const size_t id; + config cfg; + window* root; }; } diff --git a/include/fennec/platform/linux/wayland/server.h b/include/fennec/platform/linux/wayland/server.h new file mode 100644 index 0000000..63c0215 --- /dev/null +++ b/include/fennec/platform/linux/wayland/server.h @@ -0,0 +1,86 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 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 . +// ===================================================================================================================== + +/// +/// \file wayland_server.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + +#ifndef FENNEC_PLATFORM_LINUX_WAYLAND_SERVER_H +#define FENNEC_PLATFORM_LINUX_WAYLAND_SERVER_H + +#include + +// forward defs to avoid flooding global namespace +struct wl_display; +struct wl_shm; +struct wl_compositor; +struct wl_registry; +struct wl_seat; + +struct wp_viewporter; +struct xdg_wm_base; + +namespace fennec +{ + +class wayland_server : public display_server { +public: + explicit wayland_server(fennec::platform* p); + ~wayland_server() override; + + void connect() override; + void disconnect() override; + bool connected() const override; + + window* create_window(const window::config& conf) override; + +private: + wl_display* display; + wl_registry* registry; + + wl_compositor* compositor; + xdg_wm_base* xdg; + wl_seat* seat; + bool fifo; + + static void listen_global(void*, wl_registry*, uint32_t, const char*, uint32_t); + static void listen_global_remove(void*, wl_registry*, uint32_t); + + static void listen_seat_capabilities(void*, wl_seat*, uint32_t); + static void listen_seat_name(void*, wl_seat*, const char*); + + static void listen_xdg_ping(void*, xdg_wm_base*, uint32_t); + + FENNEC_RTTI_CLASS_ENABLE(display_server) { + display_server::register_type(1); + } + + friend class wayland_window; +}; + +} + +#endif // FENNEC_PLATFORM_LINUX_WAYLAND_SERVER_H diff --git a/include/fennec/platform/linux/wayland/window.h b/include/fennec/platform/linux/wayland/window.h new file mode 100644 index 0000000..a2eac44 --- /dev/null +++ b/include/fennec/platform/linux/wayland/window.h @@ -0,0 +1,82 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 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 . +// ===================================================================================================================== + +/// +/// \file wayland_window.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + +#ifndef FENNEC_PLATFORM_LINUX_WAYLAND_WINDOW_H +#define FENNEC_PLATFORM_LINUX_WAYLAND_WINDOW_H + +#include + +struct wl_array; +struct wl_callback; +struct wl_output; +struct wl_surface; + +struct xdg_surface; +struct xdg_toplevel; + +namespace fennec +{ + +class wayland_window : public window { +public: + wayland_window(display_server* server, uint32_t id, const config& cfg); + ~wayland_window(); + + void show() override; + void hide() override; + + void dispatch() override; + + bool set_flag(uint8_t flag, bool val) override; + +private: + wl_surface* surface; + xdg_surface* xdgsurface; + xdg_toplevel* xdgtoplevel; + wl_callback* frame_callback; + + static void listen_enter(void*, wl_surface*, wl_output*); + static void listen_leave(void*, wl_surface*, wl_output*); + static void listen_preferred_buffer_scale(void*, wl_surface*, int32_t); + static void listen_preferred_buffer_transform(void*, wl_surface*, uint32_t); + + static void listen_frame_callback(void*, wl_callback*, uint32_t); + + static void listen_xdg_surface_configure(void*, xdg_surface*, uint32_t); + + static void listen_xdg_toplevel_configure(void*, xdg_toplevel*, int32_t, int32_t, wl_array*); + static void listen_xdg_toplevel_configure_bounds(void*, xdg_toplevel*, int32_t, int32_t); + static void listen_xdg_toplevel_close(void*, xdg_toplevel*); + static void listen_xdg_toplevel_capabilities(void*, xdg_toplevel*, wl_array*); +}; + +} + +#endif // FENNEC_PLATFORM_LINUX_WAYLAND_WINDOW_H diff --git a/include/fennec/rtti/type.h b/include/fennec/rtti/type.h index 3e3ae89..e64ea89 100644 --- a/include/fennec/rtti/type.h +++ b/include/fennec/rtti/type.h @@ -118,7 +118,7 @@ struct type { static type get_from_instance(TypeT* t) { return type(t); } private: - const type_data* const _data; + const type_data* _data; template type(TypeT*) : _data(type_storage::get_data()) { @@ -126,6 +126,12 @@ private: public: type(type_data* d) : _data(d) { } + + type(const type& t) = default; + type(type&& t) noexcept = default; + + type& operator=(const type&) = default; + type& operator=(type&&) noexcept = default; }; diff --git a/include/fennec/rtti/type_registry.h b/include/fennec/rtti/type_registry.h index e4a9010..719e2ec 100644 --- a/include/fennec/rtti/type_registry.h +++ b/include/fennec/rtti/type_registry.h @@ -39,12 +39,46 @@ namespace fennec template class type_registry { public: - using ctor_t = BaseT (*)(ArgsT&&...); + using ctor_t = BaseT* (*)(ArgsT&&...); struct entry { size_t priority; type type; ctor_t ctor; + + entry() + : priority(0) + , type(nullptr) + , ctor(nullptr) { + } + + entry(const entry& e) + : priority(e.priority) + , type(e.type) + , ctor(e.ctor) { + } + + entry(entry&& e) noexcept + : priority(e.priority) + , type(e.type) + , ctor(e.ctor) { + } + + ~entry() { + } + + entry& operator=(const entry& e) { + priority = e.priority; + type = e.type; + ctor = e.ctor; + return *this; + } + + entry& operator=(entry&&) noexcept = default; + + entry(size_t p, fennec::type type, ctor_t ctor) + : priority(p), type(type), ctor(ctor) { + } }; struct compare { @@ -60,7 +94,7 @@ public: _global_list().emplace( priority, type::get(), - _constructor_helper + _constructor_helper ); } diff --git a/source/core/log.cpp b/source/core/log.cpp new file mode 100644 index 0000000..89ce690 --- /dev/null +++ b/source/core/log.cpp @@ -0,0 +1,29 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 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 . +// ===================================================================================================================== + +/// +/// \file log.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// \ No newline at end of file diff --git a/source/core/logger.cpp b/source/core/logger.cpp new file mode 100644 index 0000000..c869f39 --- /dev/null +++ b/source/core/logger.cpp @@ -0,0 +1,47 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 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 . +// ===================================================================================================================== + +/// +/// \file logger.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + +#include + +namespace fennec +{ + +logger::logger() + : _logfile("./logs/current.log", fmode_write) + , _cout(&file::cout()) { + if (_logfile.get_error() != nullptr) { + _logfile.clear_error(); + } +} + +logger::~logger() { +} + +} diff --git a/source/filesystem/file.cpp b/source/filesystem/file.cpp index 08e4902..e6c6280 100644 --- a/source/filesystem/file.cpp +++ b/source/filesystem/file.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -32,6 +31,7 @@ #else #include +#include #endif @@ -120,7 +120,9 @@ file::file() } file::~file() { - close(); + if (is_open()) { + close(); + } } file::file(file&& file) noexcept @@ -1030,6 +1032,23 @@ wstring file::getwline() { return wstring(L""); } +void file::print(const cstring& str) { + write(str.data(), str.length()); +} + +void file::print(const string& str) { + write(str.data(), str.length()); +} + +void file::println(const cstring& str) { + write(str.data(), str.length()); + putc('\n'); +} + +void file::println(const string& str) { + write(str.data(), str.length()); +} + bool file::putc(char c) { assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); assert(not(_mode & fmode_wide), "Attempted Wide Operation on Byte File"); diff --git a/source/platform/interface/platform.cpp b/source/platform/interface/platform.cpp index 7741fa1..65696e3 100644 --- a/source/platform/interface/platform.cpp +++ b/source/platform/interface/platform.cpp @@ -16,6 +16,8 @@ // along with this program. If not, see . // ===================================================================================================================== +#include +#include #include namespace fennec @@ -27,9 +29,24 @@ platform::platform() { } void platform::initialize() { + logger::log(fennec::format("Initializing platform {}", get_type().name())); + + display_server::entrylist_t display_servers = display_server::get_type_list(); + while (not display_servers.empty()) { + display_server::entry it = display_servers.front(); + display_servers.pop(); + + unique_ptr server = unique_ptr(it.ctor(this)); + server->connect(); + if (server->connected()) { + display = move(server); + break; + } + } } void platform::shutdown() { + } } diff --git a/include/fennec/platform/linux/wayland/display_server.h b/source/platform/interface/window.cpp similarity index 76% rename from include/fennec/platform/linux/wayland/display_server.h rename to source/platform/interface/window.cpp index ade6a07..c2d2d2f 100644 --- a/include/fennec/platform/linux/wayland/display_server.h +++ b/source/platform/interface/window.cpp @@ -17,7 +17,7 @@ // ===================================================================================================================== /// -/// \file display_server.h +/// \file window.h /// \brief /// /// @@ -28,25 +28,14 @@ /// /// -#ifndef FENNEC_PLATFORM_LINUX_WAYLAND_DISPLAY_SERVER_H -#define FENNEC_PLATFORM_LINUX_WAYLAND_DISPLAY_SERVER_H +#include #include namespace fennec { -class wayland_server : display_server { -public: - explicit wayland_server(fennec::platform* p) - : display_server(p) { - } - -private: - FENNEC_RTTI_CLASS_ENABLE(display_server) { - - } -}; - +window* window::get_parent() const { + return server->get_window(cfg.parent); } -#endif // FENNEC_PLATFORM_LINUX_WAYLAND_DISPLAY_SERVER_H +} diff --git a/source/platform/linux/platform.cpp b/source/platform/linux/platform.cpp index b12d928..05e10ba 100644 --- a/source/platform/linux/platform.cpp +++ b/source/platform/linux/platform.cpp @@ -29,9 +29,11 @@ FENNEC_PRIVATE_STATIC_CONSTRUCTOR(_init_linux) { } void linux_platform::initialize() { + platform::initialize(); } void linux_platform::shutdown() { + platform::shutdown(); } } diff --git a/source/platform/linux/wayland/server.cpp b/source/platform/linux/wayland/server.cpp new file mode 100644 index 0000000..3475f62 --- /dev/null +++ b/source/platform/linux/wayland/server.cpp @@ -0,0 +1,185 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 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 . +// ===================================================================================================================== + +/// +/// \file wayland_server.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + +#include +#include +#include +#include +#include +#include + +namespace fennec +{ + +wayland_server::wayland_server(fennec::platform* p) + : display_server(p) + , display(nullptr), registry(nullptr), compositor(nullptr), seat(nullptr), fifo(false) { + + // load shared lib + if (libwayland::load_symbols(platform)) { + return; + } +} + +wayland_server::~wayland_server() { + + // ensure we did disconnect + wayland_server::disconnect(); + + // unload shared lib + libwayland::unload_symbols(platform); +} + +void wayland_server::connect() { + + static constexpr wl_registry_listener listener = { + listen_global, listen_global_remove + }; + + if (display != nullptr) { + return; + } + + // connect to wayland + display = wl_display_connect(nullptr); + if (display == nullptr) { + return; + } + + // get the registry + registry = wl_display_get_registry(display); + if (registry == nullptr) { + assert(registry, "Failed to acquire registry from Wayland"); + disconnect(); + return; + } + + // Aquire interfaces + wl_registry_add_listener(registry, &listener, this); + wl_display_roundtrip(display); + + // Check for fifo v1, wayland's builtin fifo only allows one frame to be in flight at a time + if (not fifo) { + assert(fifo, "Compositor does not support fifo-v1 protocol, falling back to other display protocols."); + disconnect(); + return; + } +} + +void wayland_server::disconnect() { + + // early escape + if (display == nullptr) { + return; + } + + // check compositor + if (compositor) { + wl_compositor_destroy(compositor); + } + + // check registry + if (registry) { + wl_registry_destroy(registry); + } + + // disconnect from wayland + wl_display_flush(display); + wl_display_disconnect(display); + + // clear vars + display = nullptr; + registry = nullptr; + compositor = nullptr; + seat = nullptr; + fifo = false; +} + +bool wayland_server::connected() const { + return display != nullptr; +} + +window* wayland_server::create_window(const window::config& conf) { + size_t id = windows.insert(new wayland_window(this, windows.next_id(), conf)); + return windows[id]; +} + +void wayland_server::listen_global(void* data, wl_registry* reg, uint32_t id, const char* name, uint32_t version) { + static constexpr wl_seat_listener seat_listener = { + listen_seat_capabilities, listen_seat_name + }; + + static constexpr xdg_wm_base_listener xdg_listener = { + listen_xdg_ping + }; + + wayland_server* server = static_cast(data); + const cstring interface = cstring(name, strlen(name) + 1); + + if (interface == "wl_compositor") { + server->compositor = static_cast(wl_registry_bind(reg, id, &wl_compositor_interface, version)); + return; + } + + if (interface == "xdg_wm_base") { + server->xdg = static_cast(wl_registry_bind(reg, id, &xdg_wm_base_interface, version)); + xdg_wm_base_add_listener(server->xdg, &xdg_listener, server); + return; + } + + if (interface == "wl_seat") { + server->seat = static_cast(wl_registry_bind(reg, id, &wl_seat_interface, version)); + wl_seat_add_listener(server->seat, &seat_listener, server); + return; + } + + if (interface == "wp_fifo_manager_v1") { + server->fifo = true; + return; + } +} + +void wayland_server::listen_global_remove(void*, wl_registry*, uint32_t) { + +} + + +void wayland_server::listen_seat_capabilities(void*, wl_seat*, uint32_t) { + +} + +void wayland_server::listen_seat_name(void*, wl_seat*, const char*) {} + + +void wayland_server::listen_xdg_ping(void*, xdg_wm_base* xdg, uint32_t serial) { + xdg_wm_base_pong(xdg, serial); +} + +} diff --git a/source/platform/linux/wayland/window.cpp b/source/platform/linux/wayland/window.cpp new file mode 100644 index 0000000..95fe10e --- /dev/null +++ b/source/platform/linux/wayland/window.cpp @@ -0,0 +1,160 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 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 . +// ===================================================================================================================== + +/// +/// \file window.h +/// \brief +/// +/// +/// \details +/// \author Medusa Slockbower +/// +/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)) +/// +/// + +#include + +#include +#include +#include +#include + +using namespace fennec; + +wayland_window::wayland_window(display_server* server, uint32_t id, const config& cfg) + : window(server, id, cfg) + , surface(nullptr) + , xdgsurface(nullptr) { + +} + +wayland_window::~wayland_window() { + +} + +void wayland_window::show() { + static constexpr wl_surface_listener surface_listener = { + listen_enter, listen_leave, listen_preferred_buffer_scale, listen_preferred_buffer_transform + }; + + static constexpr xdg_surface_listener xdg_surface_listener = { + listen_xdg_surface_configure + }; + + static constexpr xdg_toplevel_listener xdg_toplevel_listener = { + listen_xdg_toplevel_configure, listen_xdg_toplevel_close, listen_xdg_toplevel_configure_bounds, + listen_xdg_toplevel_capabilities + }; + + static constexpr wl_callback_listener frame_callback_listener = { + listen_frame_callback + }; + + if (is_visible()) { + return; + } + + wayland_window* root = this; + while (root != nullptr and root->is_popup()) { + root = static_cast(root->get_parent()); + } + + assertf(root != nullptr, "Failed to find appropriate top-level window."); + + wayland_server* wl_server = static_cast(server); + + surface = wl_compositor_create_surface(wl_server->compositor); + wl_surface_add_listener(surface, &surface_listener, this); + + + 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); + + cfg.flags.set(flag_visible); + cfg.flags.set(flag_running); +} + +void wayland_window::hide() { + if (not is_visible()) { + return; + } + + wayland_server* wl_server = static_cast(server); + + if (xdgtoplevel) { + xdg_toplevel_destroy(xdgtoplevel); + xdgtoplevel = nullptr; + } + + if (frame_callback) { + wl_callback_destroy(frame_callback); + frame_callback = nullptr; + } + + if (xdgsurface) { + xdg_surface_destroy(xdgsurface); + xdgsurface = nullptr; + } + + if (surface) { + wl_surface_destroy(surface); + surface = nullptr; + } + + wl_display_roundtrip(wl_server->display); + + cfg.flags.clear(flag_visible); + cfg.flags.clear(flag_running); +} + +void wayland_window::dispatch() { + wayland_server* wl_server = static_cast(server); + wl_display_dispatch(wl_server->display); +} + +bool wayland_window::set_flag(uint8_t, bool) { + return false; +} + +void wayland_window::listen_enter(void*, wl_surface*, wl_output*) {} +void wayland_window::listen_leave(void*, wl_surface*, wl_output*) {} +void wayland_window::listen_preferred_buffer_scale(void*, wl_surface*, int32_t) {} +void wayland_window::listen_preferred_buffer_transform(void*, wl_surface*, uint32_t) {} + +void wayland_window::listen_frame_callback(void*, wl_callback*, uint32_t) {} + +void wayland_window::listen_xdg_surface_configure(void*, xdg_surface* xdg, uint32_t serial) { + xdg_surface_ack_configure(xdg, serial); +} + +void wayland_window::listen_xdg_toplevel_configure(void*, xdg_toplevel*, int32_t, int32_t, wl_array*) {} +void wayland_window::listen_xdg_toplevel_configure_bounds(void*, xdg_toplevel*, int32_t, int32_t) {} +void wayland_window::listen_xdg_toplevel_close(void*, xdg_toplevel*) {} +void wayland_window::listen_xdg_toplevel_capabilities(void*, xdg_toplevel*, wl_array*) {} diff --git a/test/tests/test_platform.h b/test/tests/test_platform.h index 74a8615..397b3dd 100644 --- a/test/tests/test_platform.h +++ b/test/tests/test_platform.h @@ -29,6 +29,34 @@ namespace fennec::test inline void fennec_test_platform() { + platform* platform = platform::instance(); + platform->initialize(); + + display_server* display = platform->get_display_server(); + + window* window = display->create_window({ + .title = string("test window"), + .flags = {}, + .mode = window::mode_windowed, + .parent = window::nullid, + .size = ivec2{ 720, 480 }, + .accessibility = { string("test window"), string("test window description") } + }); + + assertf(window != nullptr, "Failed to create test window."); + + window->show(); + + while (window->is_running()) { + window->dispatch(); + } + + window->hide(); + + delete window; + + platform::instance()->shutdown(); + } }