From 366a57d5c5cc06a69e631115603fc0c33b2566aa Mon Sep 17 00:00:00 2001 From: Joel Winarske Date: Fri, 12 Apr 2024 17:57:11 -0700 Subject: [PATCH] Rework -registrar abstraction -remove cursor (for now) -support many output -support many seats Signed-off-by: Joel Winarske --- CMakeLists.txt | 9 +- README.md | 4 + cmake/compiler.cmake | 11 +- cmake/options.cmake | 7 + cmake/wayland.cmake | 92 +- demo/demo.cc | 130 - {demo => examples}/CMakeLists.txt | 26 +- examples/simple-egl.cc | 470 + include/config.h | 41 + include/wayland-protocols.h | 71 + src/CMakeLists.txt | 24 +- src/{window/window_vulkan.h => logging.cc} | 27 +- src/{window/window_egl.h => logging.h} | 33 +- src/seat/cursor.cc | 138 +- src/seat/cursor.h | 33 +- src/seat/keyboard.cc | 171 +- src/seat/keyboard.h | 181 +- src/seat/pointer.cc | 125 +- src/seat/pointer.h | 128 +- src/seat/seat.cc | 23 +- src/seat/seat.h | 31 +- src/seat/touch.cc | 64 +- src/seat/touch.h | 37 +- src/window/egl.cc | 187 +- src/window/egl.h | 86 +- src/window/window.cc | 330 +- src/window/window.h | 190 +- src/window/window_egl.cc | 73 - src/window/window_vulkan.cc | 39 - src/window_manager/agl_shell.cc | 223 + src/window_manager/agl_shell.h | 140 + src/window_manager/buffer.cc | 17 + src/window_manager/buffer.h | 54 + src/window_manager/display.cc | 187 - src/window_manager/display.h | 112 - src/window_manager/ivi_window_manager.cc | 300 + src/window_manager/ivi_window_manager.h | 178 + src/window_manager/output.cc | 243 +- src/window_manager/output.h | 103 +- src/window_manager/registrar.cc | 288 + src/window_manager/registrar.h | 225 + src/window_manager/window_manager.cc | 144 +- src/window_manager/window_manager.h | 84 +- src/window_manager/xdg_toplevel.cc | 253 + src/window_manager/xdg_toplevel.h | 146 + src/window_manager/xdg_window_manager.cc | 93 + src/window_manager/xdg_window_manager.h | 59 + src/window_manager/xdg_wm.cc | 303 - src/window_manager/xdg_wm.h | 106 - third_party/CMakeLists.txt | 14 + third_party/cxxopts-3.2.1/BUILD.bazel | 16 + third_party/cxxopts-3.2.1/CHANGELOG.md | 163 + third_party/cxxopts-3.2.1/CMakeLists.txt | 82 + third_party/cxxopts-3.2.1/INSTALL | 46 + third_party/cxxopts-3.2.1/LICENSE | 19 + third_party/cxxopts-3.2.1/README.md | 277 + third_party/cxxopts-3.2.1/WORKSPACE | 16 + third_party/cxxopts-3.2.1/cmake/cxxopts.cmake | 163 + .../cxxopts-3.2.1/include/CMakeLists.txt | 23 + third_party/cxxopts-3.2.1/include/cxxopts.hpp | 2878 +++ .../packaging/cxxopts-config.cmake.in | 4 + .../cxxopts-3.2.1/packaging/pkgconfig.pc.in | 7 + third_party/cxxopts-3.2.1/src/CMakeLists.txt | 24 + third_party/cxxopts-3.2.1/src/example.cpp | 202 + third_party/cxxopts-3.2.1/test/CMakeLists.txt | 60 + .../test/add-subdirectory-test/CMakeLists.txt | 11 + third_party/cxxopts-3.2.1/test/catch.hpp | 17976 ++++++++++++++++ .../test/find-package-test/CMakeLists.txt | 11 + third_party/cxxopts-3.2.1/test/fuzz.cpp | 107 + third_party/cxxopts-3.2.1/test/link_a.cpp | 6 + third_party/cxxopts-3.2.1/test/link_b.cpp | 1 + third_party/cxxopts-3.2.1/test/main.cpp | 2 + third_party/cxxopts-3.2.1/test/options.cpp | 1028 + .../include/gtest/internal/gtest-port.h | 2 +- .../googletest/googletest/src/gtest.cc | 4 +- .../test/googletest-death-test-test.cc | 2 +- third_party/spdlog-1.13.0/.clang-format | 19 + third_party/spdlog-1.13.0/.clang-tidy | 54 + .../spdlog-1.13.0/.git-blame-ignore-revs | 6 + third_party/spdlog-1.13.0/.gitattributes | 1 + .../spdlog-1.13.0/.github/workflows/ci.yml | 78 + third_party/spdlog-1.13.0/.gitignore | 95 + third_party/spdlog-1.13.0/CMakeLists.txt | 359 + third_party/spdlog-1.13.0/INSTALL | 24 + third_party/spdlog-1.13.0/LICENSE | 26 + third_party/spdlog-1.13.0/README.md | 500 + third_party/spdlog-1.13.0/appveyor.yml | 89 + .../spdlog-1.13.0/bench/CMakeLists.txt | 37 + .../spdlog-1.13.0/bench/async_bench.cpp | 168 + third_party/spdlog-1.13.0/bench/bench.cpp | 246 + .../spdlog-1.13.0/bench/formatter-bench.cpp | 71 + third_party/spdlog-1.13.0/bench/latency.cpp | 220 + third_party/spdlog-1.13.0/bench/utils.h | 32 + third_party/spdlog-1.13.0/cmake/ide.cmake | 18 + third_party/spdlog-1.13.0/cmake/pch.h.in | 258 + third_party/spdlog-1.13.0/cmake/spdlog.pc.in | 13 + .../spdlog-1.13.0/cmake/spdlogCPack.cmake | 60 + .../spdlog-1.13.0/cmake/spdlogConfig.cmake.in | 20 + third_party/spdlog-1.13.0/cmake/utils.cmake | 62 + third_party/spdlog-1.13.0/cmake/version.rc.in | 42 + .../spdlog-1.13.0/example/CMakeLists.txt | 23 + third_party/spdlog-1.13.0/example/example.cpp | 378 + .../spdlog-1.13.0/include/spdlog/async.h | 100 + .../include/spdlog/async_logger-inl.h | 84 + .../include/spdlog/async_logger.h | 74 + .../spdlog-1.13.0/include/spdlog/cfg/argv.h | 40 + .../spdlog-1.13.0/include/spdlog/cfg/env.h | 36 + .../include/spdlog/cfg/helpers-inl.h | 107 + .../include/spdlog/cfg/helpers.h | 29 + .../spdlog-1.13.0/include/spdlog/common-inl.h | 68 + .../spdlog-1.13.0/include/spdlog/common.h | 411 + .../include/spdlog/details/backtracer-inl.h | 63 + .../include/spdlog/details/backtracer.h | 45 + .../include/spdlog/details/circular_q.h | 113 + .../include/spdlog/details/console_globals.h | 28 + .../include/spdlog/details/file_helper-inl.h | 152 + .../include/spdlog/details/file_helper.h | 61 + .../include/spdlog/details/fmt_helper.h | 141 + .../include/spdlog/details/log_msg-inl.h | 44 + .../include/spdlog/details/log_msg.h | 40 + .../spdlog/details/log_msg_buffer-inl.h | 54 + .../include/spdlog/details/log_msg_buffer.h | 32 + .../include/spdlog/details/mpmc_blocking_q.h | 177 + .../include/spdlog/details/null_mutex.h | 35 + .../include/spdlog/details/os-inl.h | 585 + .../spdlog-1.13.0/include/spdlog/details/os.h | 123 + .../spdlog/details/periodic_worker-inl.h | 26 + .../include/spdlog/details/periodic_worker.h | 57 + .../include/spdlog/details/registry-inl.h | 265 + .../include/spdlog/details/registry.h | 122 + .../spdlog/details/synchronous_factory.h | 22 + .../spdlog/details/tcp_client-windows.h | 135 + .../include/spdlog/details/tcp_client.h | 127 + .../include/spdlog/details/thread_pool-inl.h | 127 + .../include/spdlog/details/thread_pool.h | 117 + .../spdlog/details/udp_client-windows.h | 98 + .../include/spdlog/details/udp_client.h | 81 + .../include/spdlog/details/windows_include.h | 11 + .../include/spdlog/fmt/bin_to_hex.h | 224 + .../include/spdlog/fmt/bundled/args.h | 234 + .../include/spdlog/fmt/bundled/chrono.h | 2069 ++ .../include/spdlog/fmt/bundled/color.h | 651 + .../include/spdlog/fmt/bundled/compile.h | 611 + .../include/spdlog/fmt/bundled/core.h | 3323 +++ .../spdlog/fmt/bundled/fmt.license.rst | 27 + .../include/spdlog/fmt/bundled/format-inl.h | 1723 ++ .../include/spdlog/fmt/bundled/format.h | 4217 ++++ .../include/spdlog/fmt/bundled/locale.h | 2 + .../include/spdlog/fmt/bundled/os.h | 478 + .../include/spdlog/fmt/bundled/ostream.h | 237 + .../include/spdlog/fmt/bundled/printf.h | 640 + .../include/spdlog/fmt/bundled/ranges.h | 722 + .../include/spdlog/fmt/bundled/std.h | 171 + .../include/spdlog/fmt/bundled/xchar.h | 229 + .../spdlog-1.13.0/include/spdlog/fmt/chrono.h | 23 + .../include/spdlog/fmt/compile.h | 23 + .../spdlog-1.13.0/include/spdlog/fmt/fmt.h | 34 + .../spdlog-1.13.0/include/spdlog/fmt/ostr.h | 23 + .../spdlog-1.13.0/include/spdlog/fmt/ranges.h | 23 + .../spdlog-1.13.0/include/spdlog/fmt/std.h | 24 + .../spdlog-1.13.0/include/spdlog/fmt/xchar.h | 23 + .../spdlog-1.13.0/include/spdlog/formatter.h | 17 + .../spdlog-1.13.0/include/spdlog/fwd.h | 18 + .../spdlog-1.13.0/include/spdlog/logger-inl.h | 198 + .../spdlog-1.13.0/include/spdlog/logger.h | 379 + .../include/spdlog/pattern_formatter-inl.h | 1268 ++ .../include/spdlog/pattern_formatter.h | 118 + .../include/spdlog/sinks/android_sink.h | 137 + .../include/spdlog/sinks/ansicolor_sink-inl.h | 135 + .../include/spdlog/sinks/ansicolor_sink.h | 115 + .../include/spdlog/sinks/base_sink-inl.h | 59 + .../include/spdlog/sinks/base_sink.h | 51 + .../spdlog/sinks/basic_file_sink-inl.h | 42 + .../include/spdlog/sinks/basic_file_sink.h | 65 + .../include/spdlog/sinks/callback_sink.h | 56 + .../include/spdlog/sinks/daily_file_sink.h | 252 + .../include/spdlog/sinks/dist_sink.h | 81 + .../include/spdlog/sinks/dup_filter_sink.h | 92 + .../include/spdlog/sinks/hourly_file_sink.h | 191 + .../include/spdlog/sinks/kafka_sink.h | 119 + .../include/spdlog/sinks/mongo_sink.h | 108 + .../include/spdlog/sinks/msvc_sink.h | 68 + .../include/spdlog/sinks/null_sink.h | 41 + .../include/spdlog/sinks/ostream_sink.h | 43 + .../include/spdlog/sinks/qt_sinks.h | 304 + .../include/spdlog/sinks/ringbuffer_sink.h | 67 + .../spdlog/sinks/rotating_file_sink-inl.h | 144 + .../include/spdlog/sinks/rotating_file_sink.h | 89 + .../include/spdlog/sinks/sink-inl.h | 22 + .../spdlog-1.13.0/include/spdlog/sinks/sink.h | 34 + .../spdlog/sinks/stdout_color_sinks-inl.h | 38 + .../include/spdlog/sinks/stdout_color_sinks.h | 49 + .../include/spdlog/sinks/stdout_sinks-inl.h | 126 + .../include/spdlog/sinks/stdout_sinks.h | 84 + .../include/spdlog/sinks/syslog_sink.h | 103 + .../include/spdlog/sinks/systemd_sink.h | 121 + .../include/spdlog/sinks/tcp_sink.h | 75 + .../include/spdlog/sinks/udp_sink.h | 69 + .../include/spdlog/sinks/win_eventlog_sink.h | 260 + .../include/spdlog/sinks/wincolor_sink-inl.h | 163 + .../include/spdlog/sinks/wincolor_sink.h | 82 + .../spdlog-1.13.0/include/spdlog/spdlog-inl.h | 92 + .../spdlog-1.13.0/include/spdlog/spdlog.h | 352 + .../spdlog-1.13.0/include/spdlog/stopwatch.h | 62 + .../spdlog-1.13.0/include/spdlog/tweakme.h | 141 + .../spdlog-1.13.0/include/spdlog/version.h | 11 + .../logos/jetbrains-variant-4.svg | 43 + third_party/spdlog-1.13.0/logos/spdlog.png | Bin 0 -> 10820 bytes .../spdlog-1.13.0/scripts/ci_setup_clang.sh | 12 + .../spdlog-1.13.0/scripts/extract_version.py | 17 + third_party/spdlog-1.13.0/scripts/format.sh | 19 + third_party/spdlog-1.13.0/src/async.cpp | 11 + .../src/bundled_fmtlib_format.cpp | 49 + third_party/spdlog-1.13.0/src/cfg.cpp | 8 + third_party/spdlog-1.13.0/src/color_sinks.cpp | 55 + third_party/spdlog-1.13.0/src/file_sinks.cpp | 20 + third_party/spdlog-1.13.0/src/spdlog.cpp | 28 + .../spdlog-1.13.0/src/stdout_sinks.cpp | 37 + .../spdlog-1.13.0/tests/CMakeLists.txt | 90 + third_party/spdlog-1.13.0/tests/includes.h | 36 + third_party/spdlog-1.13.0/tests/main.cpp | 10 + .../spdlog-1.13.0/tests/test_async.cpp | 200 + .../spdlog-1.13.0/tests/test_backtrace.cpp | 73 + .../spdlog-1.13.0/tests/test_bin_to_hex.cpp | 97 + third_party/spdlog-1.13.0/tests/test_cfg.cpp | 169 + .../spdlog-1.13.0/tests/test_circular_q.cpp | 50 + .../spdlog-1.13.0/tests/test_create_dir.cpp | 83 + .../tests/test_custom_callbacks.cpp | 35 + .../spdlog-1.13.0/tests/test_daily_logger.cpp | 173 + .../spdlog-1.13.0/tests/test_dup_filter.cpp | 83 + .../spdlog-1.13.0/tests/test_errors.cpp | 112 + .../spdlog-1.13.0/tests/test_eventlog.cpp | 75 + .../spdlog-1.13.0/tests/test_file_helper.cpp | 169 + .../spdlog-1.13.0/tests/test_file_logging.cpp | 103 + .../spdlog-1.13.0/tests/test_fmt_helper.cpp | 82 + .../spdlog-1.13.0/tests/test_macros.cpp | 53 + third_party/spdlog-1.13.0/tests/test_misc.cpp | 169 + .../spdlog-1.13.0/tests/test_mpmc_q.cpp | 114 + .../tests/test_pattern_formatter.cpp | 502 + .../spdlog-1.13.0/tests/test_registry.cpp | 112 + third_party/spdlog-1.13.0/tests/test_sink.h | 69 + .../spdlog-1.13.0/tests/test_stdout_api.cpp | 90 + .../spdlog-1.13.0/tests/test_stopwatch.cpp | 42 + .../spdlog-1.13.0/tests/test_systemd.cpp | 14 + .../spdlog-1.13.0/tests/test_time_point.cpp | 35 + third_party/spdlog-1.13.0/tests/utils.cpp | 103 + third_party/spdlog-1.13.0/tests/utils.h | 18 + 247 files changed, 58988 insertions(+), 2056 deletions(-) delete mode 100644 demo/demo.cc rename {demo => examples}/CMakeLists.txt (52%) create mode 100644 examples/simple-egl.cc create mode 100644 include/config.h create mode 100644 include/wayland-protocols.h rename src/{window/window_vulkan.h => logging.cc} (55%) rename src/{window/window_egl.h => logging.h} (51%) delete mode 100644 src/window/window_egl.cc delete mode 100644 src/window/window_vulkan.cc create mode 100644 src/window_manager/agl_shell.cc create mode 100644 src/window_manager/agl_shell.h create mode 100644 src/window_manager/buffer.cc create mode 100644 src/window_manager/buffer.h delete mode 100644 src/window_manager/display.cc delete mode 100644 src/window_manager/display.h create mode 100644 src/window_manager/ivi_window_manager.cc create mode 100644 src/window_manager/ivi_window_manager.h create mode 100644 src/window_manager/registrar.cc create mode 100644 src/window_manager/registrar.h create mode 100644 src/window_manager/xdg_toplevel.cc create mode 100644 src/window_manager/xdg_toplevel.h create mode 100644 src/window_manager/xdg_window_manager.cc create mode 100644 src/window_manager/xdg_window_manager.h delete mode 100644 src/window_manager/xdg_wm.cc delete mode 100644 src/window_manager/xdg_wm.h create mode 100644 third_party/cxxopts-3.2.1/BUILD.bazel create mode 100644 third_party/cxxopts-3.2.1/CHANGELOG.md create mode 100644 third_party/cxxopts-3.2.1/CMakeLists.txt create mode 100644 third_party/cxxopts-3.2.1/INSTALL create mode 100644 third_party/cxxopts-3.2.1/LICENSE create mode 100644 third_party/cxxopts-3.2.1/README.md create mode 100644 third_party/cxxopts-3.2.1/WORKSPACE create mode 100644 third_party/cxxopts-3.2.1/cmake/cxxopts.cmake create mode 100644 third_party/cxxopts-3.2.1/include/CMakeLists.txt create mode 100644 third_party/cxxopts-3.2.1/include/cxxopts.hpp create mode 100644 third_party/cxxopts-3.2.1/packaging/cxxopts-config.cmake.in create mode 100644 third_party/cxxopts-3.2.1/packaging/pkgconfig.pc.in create mode 100644 third_party/cxxopts-3.2.1/src/CMakeLists.txt create mode 100644 third_party/cxxopts-3.2.1/src/example.cpp create mode 100644 third_party/cxxopts-3.2.1/test/CMakeLists.txt create mode 100644 third_party/cxxopts-3.2.1/test/add-subdirectory-test/CMakeLists.txt create mode 100644 third_party/cxxopts-3.2.1/test/catch.hpp create mode 100644 third_party/cxxopts-3.2.1/test/find-package-test/CMakeLists.txt create mode 100644 third_party/cxxopts-3.2.1/test/fuzz.cpp create mode 100644 third_party/cxxopts-3.2.1/test/link_a.cpp create mode 100644 third_party/cxxopts-3.2.1/test/link_b.cpp create mode 100644 third_party/cxxopts-3.2.1/test/main.cpp create mode 100644 third_party/cxxopts-3.2.1/test/options.cpp create mode 100644 third_party/spdlog-1.13.0/.clang-format create mode 100644 third_party/spdlog-1.13.0/.clang-tidy create mode 100644 third_party/spdlog-1.13.0/.git-blame-ignore-revs create mode 100644 third_party/spdlog-1.13.0/.gitattributes create mode 100644 third_party/spdlog-1.13.0/.github/workflows/ci.yml create mode 100644 third_party/spdlog-1.13.0/.gitignore create mode 100644 third_party/spdlog-1.13.0/CMakeLists.txt create mode 100644 third_party/spdlog-1.13.0/INSTALL create mode 100644 third_party/spdlog-1.13.0/LICENSE create mode 100644 third_party/spdlog-1.13.0/README.md create mode 100644 third_party/spdlog-1.13.0/appveyor.yml create mode 100644 third_party/spdlog-1.13.0/bench/CMakeLists.txt create mode 100644 third_party/spdlog-1.13.0/bench/async_bench.cpp create mode 100644 third_party/spdlog-1.13.0/bench/bench.cpp create mode 100644 third_party/spdlog-1.13.0/bench/formatter-bench.cpp create mode 100644 third_party/spdlog-1.13.0/bench/latency.cpp create mode 100644 third_party/spdlog-1.13.0/bench/utils.h create mode 100644 third_party/spdlog-1.13.0/cmake/ide.cmake create mode 100644 third_party/spdlog-1.13.0/cmake/pch.h.in create mode 100644 third_party/spdlog-1.13.0/cmake/spdlog.pc.in create mode 100644 third_party/spdlog-1.13.0/cmake/spdlogCPack.cmake create mode 100644 third_party/spdlog-1.13.0/cmake/spdlogConfig.cmake.in create mode 100644 third_party/spdlog-1.13.0/cmake/utils.cmake create mode 100644 third_party/spdlog-1.13.0/cmake/version.rc.in create mode 100644 third_party/spdlog-1.13.0/example/CMakeLists.txt create mode 100644 third_party/spdlog-1.13.0/example/example.cpp create mode 100644 third_party/spdlog-1.13.0/include/spdlog/async.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/async_logger-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/async_logger.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/cfg/argv.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/cfg/env.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/cfg/helpers-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/cfg/helpers.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/common-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/common.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/backtracer-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/backtracer.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/circular_q.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/console_globals.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/file_helper-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/file_helper.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/fmt_helper.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/log_msg-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/log_msg.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/log_msg_buffer-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/log_msg_buffer.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/mpmc_blocking_q.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/null_mutex.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/os-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/os.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/periodic_worker-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/periodic_worker.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/registry-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/registry.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/synchronous_factory.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/tcp_client-windows.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/tcp_client.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/thread_pool-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/thread_pool.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/udp_client-windows.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/udp_client.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/details/windows_include.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bin_to_hex.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/args.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/chrono.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/color.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/compile.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/core.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/fmt.license.rst create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/format-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/format.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/locale.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/os.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/ostream.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/printf.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/ranges.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/std.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/bundled/xchar.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/chrono.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/compile.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/fmt.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/ostr.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/ranges.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/std.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fmt/xchar.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/formatter.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/fwd.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/logger-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/logger.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/pattern_formatter-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/pattern_formatter.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/android_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/ansicolor_sink-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/ansicolor_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/base_sink-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/base_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/basic_file_sink-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/basic_file_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/callback_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/daily_file_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/dist_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/dup_filter_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/hourly_file_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/kafka_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/mongo_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/msvc_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/null_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/ostream_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/qt_sinks.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/ringbuffer_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/rotating_file_sink-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/rotating_file_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/sink-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/stdout_color_sinks-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/stdout_color_sinks.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/stdout_sinks-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/stdout_sinks.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/syslog_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/systemd_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/tcp_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/udp_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/win_eventlog_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/wincolor_sink-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/sinks/wincolor_sink.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/spdlog-inl.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/spdlog.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/stopwatch.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/tweakme.h create mode 100644 third_party/spdlog-1.13.0/include/spdlog/version.h create mode 100644 third_party/spdlog-1.13.0/logos/jetbrains-variant-4.svg create mode 100644 third_party/spdlog-1.13.0/logos/spdlog.png create mode 100755 third_party/spdlog-1.13.0/scripts/ci_setup_clang.sh create mode 100755 third_party/spdlog-1.13.0/scripts/extract_version.py create mode 100755 third_party/spdlog-1.13.0/scripts/format.sh create mode 100644 third_party/spdlog-1.13.0/src/async.cpp create mode 100644 third_party/spdlog-1.13.0/src/bundled_fmtlib_format.cpp create mode 100644 third_party/spdlog-1.13.0/src/cfg.cpp create mode 100644 third_party/spdlog-1.13.0/src/color_sinks.cpp create mode 100644 third_party/spdlog-1.13.0/src/file_sinks.cpp create mode 100644 third_party/spdlog-1.13.0/src/spdlog.cpp create mode 100644 third_party/spdlog-1.13.0/src/stdout_sinks.cpp create mode 100644 third_party/spdlog-1.13.0/tests/CMakeLists.txt create mode 100644 third_party/spdlog-1.13.0/tests/includes.h create mode 100644 third_party/spdlog-1.13.0/tests/main.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_async.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_backtrace.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_bin_to_hex.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_cfg.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_circular_q.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_create_dir.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_custom_callbacks.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_daily_logger.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_dup_filter.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_errors.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_eventlog.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_file_helper.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_file_logging.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_fmt_helper.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_macros.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_misc.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_mpmc_q.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_pattern_formatter.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_registry.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_sink.h create mode 100644 third_party/spdlog-1.13.0/tests/test_stdout_api.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_stopwatch.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_systemd.cpp create mode 100644 third_party/spdlog-1.13.0/tests/test_time_point.cpp create mode 100644 third_party/spdlog-1.13.0/tests/utils.cpp create mode 100644 third_party/spdlog-1.13.0/tests/utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a8b5d5a..711f1fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,22 +45,25 @@ message(STATUS "Build Type ............. ${CMAKE_BUILD_TYPE}") include(compiler) include(utils) +include_directories(include) + # # libraries # include(wayland) add_subdirectory(third_party) - # # library # add_subdirectory(src) # -# Demos +# Examples # -add_subdirectory(demo) +if (BUILD_EXAMPLES) + add_subdirectory(examples) +endif () if (BUILD_UNIT_TESTS) enable_testing() diff --git a/README.md b/README.md index 96c8b05..abee82e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ This project requires the following packages/libraries: - libwayland-dev - libxkbcommon-dev - wayland-protocols +- cairo-dev +- glib-2.0 + +### If your graphics driver is supported by Mesa, then use: - mesa-common-dev - libgles2-mesa-dev - libegl1-mesa-dev diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index 484c8d5..d193f66 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -84,16 +84,7 @@ COMPILER_FLAGS_APPEND(RELEASE " -fno-omit-frame-pointer" "-f(no-)?omit-frame-poi COMPILER_FLAGS_APPEND(RELEASE " -Wformat=2" "-Wformat(=[0-9]+)?") COMPILER_FLAGS_APPEND(RELEASE " -D_FORTIFY_SOURCE=2" "-D_FORTIFY_SOURCE(=[0-9]+)?") -if (BUILD_PLUGIN_FIREBASE_CORE OR - BUILD_PLUGIN_CLOUD_FIRESTORE OR - BUILD_PLUGIN_FIREBASE_AUTH OR - BUILD_PLUGIN_FIREBASE_STORAGE OR - BUILD_CRASH_HANDLER OR - BUILD_PLUGIN_FILAMENT_VIEW) - string(APPEND CMAKE_CXX_FLAGS " -frtti") -else () - string(APPEND CMAKE_CXX_FLAGS " -fno-rtti") -endif () +string(APPEND CMAKE_CXX_FLAGS " -frtti") string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,--build-id=sha1") diff --git a/cmake/options.cmake b/cmake/options.cmake index b4e3a91..5ab224d 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -14,6 +14,13 @@ # limitations under the License. # + +# +# Examples +# +option(BUILD_EXAMPLES "Build Examples" ON) +MESSAGE(STATUS "Build Examples ......... ${BUILD_EXAMPLES}") + # # Unit Tests # diff --git a/cmake/wayland.cmake b/cmake/wayland.cmake index 008aeeb..89f3f76 100644 --- a/cmake/wayland.cmake +++ b/cmake/wayland.cmake @@ -48,36 +48,90 @@ endmacro() set(WAYLAND_PROTOCOL_SOURCES) -wayland_generate( - ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml - ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/protocols) wayland_generate( - ${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml - ${CMAKE_CURRENT_BINARY_DIR}/xdg-decoration-unstable-client-protocol) + ${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml + ${CMAKE_CURRENT_BINARY_DIR}/protocols/xdg-shell-client-protocol) wayland_generate( ${CMAKE_SOURCE_DIR}/third_party/agl/protocol/agl-shell.xml - ${CMAKE_CURRENT_BINARY_DIR}/agl-shell-client-protocol) + ${CMAKE_CURRENT_BINARY_DIR}/protocols/agl-shell-client-protocol) wayland_generate( ${CMAKE_SOURCE_DIR}/third_party/agl/protocol/agl-shell-desktop.xml - ${CMAKE_CURRENT_BINARY_DIR}/agl-shell-desktop-client-protocol) + ${CMAKE_CURRENT_BINARY_DIR}/protocols/agl-shell-desktop-client-protocol) wayland_generate( ${CMAKE_SOURCE_DIR}/third_party/agl/protocol/agl-screenshooter.xml - ${CMAKE_CURRENT_BINARY_DIR}/agl-screenshooter-client-protocol) + ${CMAKE_CURRENT_BINARY_DIR}/protocols/agl-screenshooter-client-protocol) wayland_generate( ${CMAKE_SOURCE_DIR}/third_party/weston/protocol/ivi-application.xml - ${CMAKE_CURRENT_BINARY_DIR}/ivi-application-client-protocol) + ${CMAKE_CURRENT_BINARY_DIR}/protocols/ivi-application-client-protocol) wayland_generate( ${CMAKE_SOURCE_DIR}/third_party/weston/protocol/ivi-wm.xml - ${CMAKE_CURRENT_BINARY_DIR}/ivi-wm-client-protocol) + ${CMAKE_CURRENT_BINARY_DIR}/protocols/ivi-wm-client-protocol) + +# +# Optional +# +set(WAYLAND_PROTOCOL_HAS_XDG_DECORATION OFF) +if (EXISTS ${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml) + set(WAYLAND_PROTOCOL_HAS_XDG_DECORATION ON) + wayland_generate( + ${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + ${CMAKE_CURRENT_BINARY_DIR}/protocols/xdg-decoration-unstable-client-protocol) +endif () +message(STATUS "XDG Decoration ........ ${WAYLAND_PROTOCOL_HAS_XDG_DECORATION}") + +set(WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE OFF) +if (EXISTS ${WAYLAND_PROTOCOLS_BASE}/staging/fractional-scale/fractional-scale-v1.xml) + set(WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE ON) + wayland_generate( + ${WAYLAND_PROTOCOLS_BASE}/staging/fractional-scale/fractional-scale-v1.xml + ${CMAKE_CURRENT_BINARY_DIR}/protocols/fractional-scale-v1-client-protocol) +endif () +message(STATUS "Fractional Scale ...... ${WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE}") + +set(WAYLAND_PROTOCOL_HAS_VIEWPORTER OFF) +if (EXISTS ${WAYLAND_PROTOCOLS_BASE}/stable/viewporter/viewporter.xml) + set(WAYLAND_PROTOCOL_HAS_VIEWPORTER ON) + wayland_generate( + ${WAYLAND_PROTOCOLS_BASE}/stable/viewporter/viewporter.xml + ${CMAKE_CURRENT_BINARY_DIR}/protocols/viewporter-client-protocol) +endif () +message(STATUS "Viewporter ............ ${WAYLAND_PROTOCOL_HAS_VIEWPORTER}") + +set(WAYLAND_PROTOCOL_HAS_TEARING_CONTROL OFF) +if (EXISTS ${WAYLAND_PROTOCOLS_BASE}/staging/tearing-control/tearing-control-v1.xml) + set(WAYLAND_PROTOCOL_HAS_TEARING_CONTROL ON) + wayland_generate( + ${WAYLAND_PROTOCOLS_BASE}/staging/tearing-control/tearing-control-v1.xml + ${CMAKE_CURRENT_BINARY_DIR}/protocols/tearing-control-v1-client-protocol) +endif () +message(STATUS "Tearing Control ....... ${WAYLAND_PROTOCOL_HAS_TEARING_CONTROL}") + +set(WAYLAND_PROTOCOL_HAS_PRESENTATION_TIME OFF) +if (EXISTS ${WAYLAND_PROTOCOLS_BASE}/stable/presentation-time/presentation-time.xml) + set(WAYLAND_PROTOCOL_HAS_PRESENTATION_TIME ON) + wayland_generate( + ${WAYLAND_PROTOCOLS_BASE}/stable/presentation-time/presentation-time.xml + ${CMAKE_CURRENT_BINARY_DIR}/protocols/presentation-time-client-protocol) +endif () +message(STATUS "Presentation Time ..... ${WAYLAND_PROTOCOL_HAS_PRESENTATION_TIME}") + +set(WAYLAND_PROTOCOL_HAS_DRM_LEASE OFF) +if (EXISTS ${WAYLAND_PROTOCOLS_BASE}/staging/drm-lease/drm-lease-v1.xml) + set(WAYLAND_PROTOCOL_HAS_DRM_LEASE ON) + wayland_generate( + ${WAYLAND_PROTOCOLS_BASE}/staging/drm-lease/drm-lease-v1.xml + ${CMAKE_CURRENT_BINARY_DIR}/protocols/drm-lease-v1-client-protocol) +endif () +message(STATUS "DRM Lease ............. ${WAYLAND_PROTOCOL_HAS_DRM_LEASE}") add_library(wayland-gen STATIC ${WAYLAND_PROTOCOL_SOURCES}) target_link_libraries(wayland-gen PUBLIC PkgConfig::WAYLAND) - if (ENABLE_XDG_CLIENT) target_compile_definitions(wayland-gen PUBLIC ENABLE_XDG_CLIENT) endif () @@ -88,6 +142,22 @@ if (ENABLE_IVI_SHELL_CLIENT) target_compile_definitions(wayland-gen PUBLIC ENABLE_IVI_SHELL_CLIENT) endif () +if (WAYLAND_PROTOCOL_HAS_XDG_DECORATION) + target_compile_definitions(wayland-gen PUBLIC WAYLAND_PROTOCOL_HAS_XDG_DECORATION) +endif () +if (WAYLAND_PROTOCOL_HAS_PRESENTATION_TIME) + target_compile_definitions(wayland-gen PUBLIC WAYLAND_PROTOCOL_HAS_PRESENTATION_TIME) +endif () +if (WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE) + target_compile_definitions(wayland-gen PUBLIC WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE) +endif () +if (WAYLAND_PROTOCOL_HAS_VIEWPORTER) + target_compile_definitions(wayland-gen PUBLIC WAYLAND_PROTOCOL_HAS_VIEWPORTER) +endif () +if (WAYLAND_PROTOCOL_HAS_TEARING_CONTROL) + target_compile_definitions(wayland-gen PUBLIC WAYLAND_PROTOCOL_HAS_TEARING_CONTROL) +endif () + target_include_directories(wayland-gen PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) if (IPO_SUPPORT_RESULT) diff --git a/demo/demo.cc b/demo/demo.cc deleted file mode 100644 index 9475d3a..0000000 --- a/demo/demo.cc +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include - -#include "window/window_egl.h" -#include "window_manager/window_manager.h" - -static volatile bool keep_running = true; -constexpr int WINDOW_HEIGHT = 200; -constexpr int WINDOW_WIDTH = 200; - -/** - * @brief Signal handler function to handle signals. - * - * This function is a signal handler for handling signals. It sets the value of keep_running - * to false, which will stop the program from running. The function does not take any input - * parameters. - * - * @param signal The signal number. This parameter is not used by the function. - * - * @return void - */ -void handle_signal(int /* signal */) { - keep_running = false; -} - -/** -* -*/ -static float hue_to_channel(const float *const hue, const int n) { - const auto k = static_cast(fmod(n + ((*hue) * 3 / M_PI), 6)); - return 1 - MAX(0, MIN(MIN(k, 4 - k), 1)); -} - -/** - * @brief Converts hue value to RGB. - * - * This function takes an array of hue values and calculates the corresponding RGB values using the hue_to_channel() function. - * The resulting RGB values are stored in the given RGB array. - * - * @param hue Pointer to an array of hue values. - * @param rgb Pointer to an array for storing the RGB values. - */ -static void hue_to_rgb(const float *const hue, float (*rgb)[3]) { - (*rgb)[0] = hue_to_channel(hue, 5); - (*rgb)[1] = hue_to_channel(hue, 3); - (*rgb)[2] = hue_to_channel(hue, 1); -} - -/** - * Calculate the current hue value based on the current time. - * - * The hue value is calculated using the current system time, and a constant value for hue change. - * The current time is obtained using std::chrono library, which provides utilities for time-related operations. - * The hue change is a constant value calculated as (2 * M_PI) / 10, where M_PI is the mathematical constant pi. - * - * @return The calculated hue value as a float. - */ -static float calculate_hue() { - static const auto hue_change = static_cast((2 * M_PI) / 10); - auto tp = std::chrono::system_clock::now(); - auto tp_sec = std::chrono::time_point_cast(tp); - std::chrono::nanoseconds ns = tp - tp_sec; - auto t = static_cast(tp_sec.time_since_epoch().count()) + - static_cast(ns.count()) * 1e-9; - return static_cast(fmod(t * hue_change, 2 * M_PI)); -} - -/** - * @brief Updates the frame by drawing it. - * - * This function updates the frame by drawing it on the screen. It sets the OpenGL clear color based on the calculated hue, - * clears the color buffer, swaps the buffers to display the updated frame, and clears the current rendering context. - * - * @param data A pointer to the WindowEgl object. - * @param time The current time in milliseconds. - */ -void frame_update(void *data, uint32_t time) { - std::cout << "draw_frame: " << time << std::endl; - auto obj = static_cast(data); - (void) obj->make_current(); - - auto hue = calculate_hue(); - float rgb[3] = {0, 0, 0}; - hue_to_rgb(&hue, &rgb); - glClearColor(rgb[0], rgb[1], rgb[2], 1); - glClear(GL_COLOR_BUFFER_BIT); - glFinish(); - - (void) obj->swap_buffers(); - (void) obj->clear_current(); -} - -/** - * @brief Main function for the program. - * - * This function initializes the window manager and creates a window with the specified dimensions and type. - * It sets up a signal handler for SIGINT (Ctrl+C) to stop the program, and then enters a loop to handle window events. - * - * @param argc The number of command line arguments. - * @param argv An array of strings representing the command line arguments. - * @return An integer representing the exit status of the program. - */ -int main(int /* argc */, char ** /* argv */) { - std::signal(SIGINT, handle_signal); - WindowManager wm(Window::ShellType::XDG); - wm.create_window(WINDOW_WIDTH, WINDOW_HEIGHT, - WindowManager::WindowType::EGL, frame_update); - - while (keep_running && wm.poll_events(0) >= 0); - return EXIT_SUCCESS; -} diff --git a/demo/CMakeLists.txt b/examples/CMakeLists.txt similarity index 52% rename from demo/CMakeLists.txt rename to examples/CMakeLists.txt index cc13904..a6bfd9c 100644 --- a/demo/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -14,13 +14,21 @@ # limitations under the License. # -# Define variables -set(TARGET_NAME demo) -set(SOURCE_FILE demo.cc) -set(COMPILE_DEFINITIONS EGL_NO_X11 MESA_EGL_NO_X11_HEADERS) -set(LINK_LIBRARIES waypp GLESv2) +find_package(PkgConfig REQUIRED) +pkg_check_modules(GLESv2 IMPORTED_TARGET glesv2) -# Use variables in place of hard-coded values -add_executable(${TARGET_NAME} ${SOURCE_FILE}) -target_compile_definitions(${TARGET_NAME} PRIVATE ${COMPILE_DEFINITIONS}) -target_link_libraries(${TARGET_NAME} ${LINK_LIBRARIES}) \ No newline at end of file +# +# simple-egl +# +add_executable(simple-egl simple-egl.cc) +target_include_directories(simple-egl PRIVATE ${CMAKE_SOURCE_DIR}/include) + +target_compile_definitions(simple-egl PRIVATE EGL_NO_X11 MESA_EGL_NO_X11_HEADERS) + +target_link_libraries(simple-egl PRIVATE waypp PkgConfig::GLESv2 cxxopts::cxxopts) + +if (IPO_SUPPORT_RESULT) + set_property(TARGET simple-egl PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +endif () + +add_sanitizers(simple-egl) diff --git a/examples/simple-egl.cc b/examples/simple-egl.cc new file mode 100644 index 0000000..0ba6574 --- /dev/null +++ b/examples/simple-egl.cc @@ -0,0 +1,470 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "window_manager/registrar.h" +#include "window_manager/xdg_window_manager.h" +#include "window/window.h" + +#include "config.h" +#include "logging.h" + +static volatile bool running = true; + +GLuint programObject_{}; +volatile bool scene_initialized = false; + +std::unique_ptr gLogging; + +/// EGL Context Attribute configuration +static constexpr std::array kEglContextAttribs = { + { + EGL_CONTEXT_MAJOR_VERSION, 2, + EGL_NONE + } +}; + +/// EGL Configuration Attributes +std::array kEglConfigAttribs = { + { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + } +}; + +typedef struct { + int delay; + bool fullscreen; + bool fullscreen_ratio; + int maximized; + bool opaque; + int buffer_bpp; + bool tearing; + bool toggled_tearing; + bool vertical_bar; + int interval; + int width; + int height; +} CONFIGURATION_T; + +CONFIGURATION_T config; + +struct { + GLint rotation_uniform; + GLuint pos; + GLuint col; +} gl; + +/** + * @brief Signal handler function to handle signals. + * + * This function is a signal handler for handling signals. It sets the value of keep_running + * to false, which will stop the program from running. The function does not take any input + * parameters. + * + * @param signal The signal number. This parameter is not used by the function. + * + * @return void + */ +void handle_signal(int signal) { + if (signal == SIGINT) { + running = false; + } +} + +GLuint LoadShader(const GLchar *shaderSrc, const GLenum type) { + // Create the shader object + const GLuint shader = glCreateShader(type); + if (shader == 0) + return 0; + glShaderSource(shader, 1, &shaderSrc, nullptr); + glCompileShader(shader); + GLint compiled; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen > 1) { + auto *infoLog = static_cast( + malloc(sizeof(char) * static_cast(infoLen))); + glGetShaderInfoLog(shader, infoLen, nullptr, infoLog); + spdlog::error("Error compiling shader:\n{}", infoLog); + free(infoLog); + } + glDeleteShader(shader); + return 0; + } + return shader; +} + +void initialize_scene(Window *window) { + static constexpr GLchar vert_shader_text[] = + "uniform mat4 rotation;\n" + "attribute vec4 pos;\n" + "attribute vec4 color;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_Position = rotation * pos;\n" + " v_color = color;\n" + "}\n"; + + static constexpr GLchar frag_shader_text[] = + "precision mediump float;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_FragColor = v_color;\n" + "}\n"; + GLuint frag, vert; + GLuint program; + GLint status; + EGLBoolean ret; + + window->update_buffer_geometry(); + + window->make_current(); + + frag = LoadShader(frag_shader_text, GL_FRAGMENT_SHADER); + vert = LoadShader(vert_shader_text, GL_VERTEX_SHADER); + + program = glCreateProgram(); + glAttachShader(program, frag); + glAttachShader(program, vert); + glLinkProgram(program); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetProgramInfoLog(program, 1000, &len, log); + fprintf(stderr, "Error: linking:\n%.*s\n", len, log); + exit(1); + } + + glUseProgram(program); + + gl.pos = 0; + gl.col = 1; + + glBindAttribLocation(program, gl.pos, "pos"); + glBindAttribLocation(program, gl.col, "color"); + glLinkProgram(program); + + gl.rotation_uniform = glGetUniformLocation(program, "rotation"); +} + +enum weston_matrix_transform_type { + WESTON_MATRIX_TRANSFORM_TRANSLATE = (1 << 0), + WESTON_MATRIX_TRANSFORM_SCALE = (1 << 1), + WESTON_MATRIX_TRANSFORM_ROTATE = (1 << 2), + WESTON_MATRIX_TRANSFORM_OTHER = (1 << 3), +}; + +struct weston_matrix { + float d[16]; + unsigned int type; +}; + +/* + * Matrices are stored in column-major order, that is the array indices are: + * 0 4 8 12 + * 1 5 9 13 + * 2 6 10 14 + * 3 7 11 15 + */ + +void weston_matrix_init(struct weston_matrix *matrix) { + static const struct weston_matrix identity = { + .d = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .type = 0, + }; + + memcpy(matrix, &identity, sizeof identity); +} + +/* m <- n * m, that is, m is multiplied on the LEFT. */ +void weston_matrix_multiply(struct weston_matrix *m, const struct weston_matrix *n) { + struct weston_matrix tmp; + const float *row, *column; + int i, j, k; + + for (i = 0; i < 4; i++) { + row = m->d + i * 4; + for (j = 0; j < 4; j++) { + tmp.d[4 * i + j] = 0; + column = n->d + j; + for (k = 0; k < 4; k++) + tmp.d[4 * i + j] += row[k] * column[k * 4]; + } + } + tmp.type = m->type | n->type; + memcpy(m, &tmp, sizeof tmp); +} + +void weston_matrix_scale(struct weston_matrix *matrix, float x, float y, float z) { + struct weston_matrix scale = { + .d = {x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1}, + .type = WESTON_MATRIX_TRANSFORM_SCALE, + }; + + weston_matrix_multiply(matrix, &scale); +} + +void weston_matrix_rotate_xy(struct weston_matrix *matrix, float cos, float sin) { + struct weston_matrix translate = { + .d = {cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, + .type = WESTON_MATRIX_TRANSFORM_ROTATE, + }; + + weston_matrix_multiply(matrix, &translate); +} + +uint32_t frames; +uint32_t initial_frame_time; +uint32_t benchmark_time; + +static void draw_triangle(Window *window) { + static const GLfloat verts[3][2] = { + {-0.5, -0.5}, + {0.5, -0.5}, + {0, 0.5} + }; + static const GLfloat colors[3][3] = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1} + }; + struct wl_region *region; + EGLint rect[4]; + + glVertexAttribPointer(gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(gl.col, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(gl.pos); + glEnableVertexAttribArray(gl.col); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + glDisableVertexAttribArray(gl.pos); + glDisableVertexAttribArray(gl.col); + + usleep(config.delay); + +#if 0 + if (config.opaque || config.fullscreen) { + region = wl_compositor_create_region(window->display->compositor); + wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_set_opaque_region(window->surface, region); + wl_region_destroy(region); + } else { + wl_surface_set_opaque_region(window->surface, NULL); + } + + if (display->swap_buffers_with_damage && buffer_age > 0) { + rect[0] = window->buffer_size.width / 4 - 1; + rect[1] = window->buffer_size.height / 4 - 1; + rect[2] = window->buffer_size.width / 2 + 2; + rect[3] = window->buffer_size.height / 2 + 2; + display->swap_buffers_with_damage(display->egl.dpy, + window->egl_surface, + rect, 1); + } else { +#endif + window->swap_buffers(); +//TODO } +} + +/** + * @brief Updates the frame by drawing it. + * + * This function updates the frame by drawing it on the screen. It sets the OpenGL clear color based on the calculated hue, + * clears the color buffer, swaps the buffers to display the updated frame, and clears the current rendering context. + * + * @param data A pointer to the WindowEgl object. + * @param time The current time in milliseconds. + */ +static void draw_frame(void *userdata, uint32_t /* time */) { + auto window = static_cast(userdata); + + if (!scene_initialized) { + initialize_scene(window); + scene_initialized = true; + } + + GLfloat angle; + struct weston_matrix rotation; + static const uint32_t speed_div = 5, benchmark_interval = 5; + struct timeval tv; + + window->update_buffer_geometry(); + + gettimeofday(&tv, NULL); + auto time = static_cast(tv.tv_sec * 1000 + tv.tv_usec / 1000); + if (frames == 0) { + initial_frame_time = time; + benchmark_time = time; + } + if (time - benchmark_time > (benchmark_interval * 1000)) { + printf("%d frames in %d seconds: %f fps\n", + frames, + benchmark_interval, + (float) frames / benchmark_interval); + benchmark_time = time; + frames = 0; + } + + weston_matrix_init(&rotation); + if (config.vertical_bar) { + angle = 0; + } else { + angle = static_cast(((time - initial_frame_time) / speed_div) + % 360 * M_PI / 180.0); + } + rotation.d[0] = cos(angle); + rotation.d[2] = sin(angle); + rotation.d[8] = -sin(angle); + rotation.d[10] = cos(angle); + + switch (window->get_buffer_transform()) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_scale(&rotation, -1, 1, 1); + break; + default: + break; + } + + switch (window->get_buffer_transform()) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_FLIPPED: + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + weston_matrix_rotate_xy(&rotation, 0, 1); + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + weston_matrix_rotate_xy(&rotation, -1, 0); + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_rotate_xy(&rotation, 0, -1); + break; + } + + glViewport(0, 0, window->get_width(), window->get_height()); + + glUniformMatrix4fv(gl.rotation_uniform, 1, GL_FALSE, (GLfloat *) rotation.d); + + if (config.opaque || config.fullscreen) + glClearColor(0.0, 0.0, 0.0, 1); + else + glClearColor(0.0, 0.0, 0.0, 0.5); + glClear(GL_COLOR_BUFFER_BIT); + + draw_triangle(window); + + frames++; +} + +/** + * @brief Main function for the program. + * + * This function initializes the surface manager and creates a surface with the specified dimensions and type. + * It sets up a signal handler for SIGINT (Ctrl+C) to stop the program, and then enters a loop to handle surface events. + * + * @param argc The number of command line arguments. + * @param argv An array of strings representing the command line arguments. + * @return An integer representing the exit status of the program. + */ +int main(int argc, char **argv) { + + gLogging = std::make_unique(); + + std::signal(SIGINT, handle_signal); + + cxxopts::Options options("simple-egl", "Weston simple-egl example"); + options.add_options() + ("w,width", "Set width", cxxopts::value()->default_value("250")) + ("h,height", "Set height", cxxopts::value()->default_value("250")) + ("d,delay", "Buffer swap delay in microseconds", cxxopts::value()->default_value("0")) + ("f,fullscreen", "Run in fullscreen mode") + ("r,fullscreen-ratio", "Use fixed width/height ratio when run in fullscreen mode") + ("m,maximized", "Run in maximized mode") + ("o,opaque", "Create an opaque surface") + ("s,buffer-bpp", "Use a 16 bpp EGL config") + ("b,non-blocking", "Don't sync to compositor redraw (eglSwapInterval 0)") + ("t,tearing", "Enable tearing via the tearing_control protocol") + ("v,vertical-bar", "Draw a moving vertical bar instead of a triangle") + ("i,interval", "Set eglSwapInterval to interval", cxxopts::value()->default_value("1")); + auto result = options.parse(argc, argv); + + config.delay = result["delay"].as(); + config.fullscreen = result["fullscreen"].as(); + config.fullscreen_ratio = result["fullscreen-ratio"].as(); + config.maximized = result["maximized"].as() ? 1 : 0; + config.opaque = result["opaque"].as(); + config.buffer_bpp = result["buffer-bpp"].as() ? 16 : 0; + config.tearing = result["tearing"].as(); + config.toggled_tearing = false; + if (result["tearing"].as()) { + config.tearing = true; + config.toggled_tearing = true; + } + config.vertical_bar = result["vertical-bar"].as(); + config.interval = result["interval"].as(); + if (result["non-blocking"].as()) { + config.interval = 0; + } + config.width = result["width"].as(); + config.height = result["height"].as(); + + // Control EGL_ALPHA_SIZE value + if (config.opaque || config.buffer_bpp == 16) { + kEglConfigAttribs[9] = 0; + } + + spdlog::info("delay: {}", config.delay); + spdlog::info("fullscreen_ratio: {}", config.fullscreen_ratio); + spdlog::info("toggled_tearing: {}", config.toggled_tearing); + spdlog::info("vertical_bar: {}", config.vertical_bar); + + XdgWindowManager wm; + auto window = wm.CreateTopLevel("simple-egl", config.width, config.height, WL_OUTPUT_TRANSFORM_NORMAL, + config.fullscreen, + config.maximized, + config.fullscreen_ratio, config.tearing, draw_frame, + kEglContextAttribs.data(), kEglContextAttribs.size(), + kEglConfigAttribs.data(), kEglConfigAttribs.size(), + config.buffer_bpp, config.interval); + + while (running && wm.dispatch_pending() != -1) { + draw_frame(window, 0); + } + + return EXIT_SUCCESS; +} diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..8bac480 --- /dev/null +++ b/include/config.h @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_CONFIG_H_ +#define INCLUDE_CONFIG_H_ + +/// Wayland interface version selection +static constexpr uint32_t kWlOutputMinVersion = UINT32_C(4); +static constexpr uint32_t kWlSeatMinVersion = UINT32_C(8); +static constexpr uint32_t kWlShmMinVersion = UINT32_C(1); +static constexpr uint32_t kWlCompositorMinVersion = UINT32_C(5); +static constexpr uint32_t kWlSubcompositorMinVersion = UINT32_C(1); + +static constexpr uint32_t kXdgWmBaseMinVersion = UINT32_C(6); +static constexpr uint32_t kIviWmMinVersion = UINT32_C(1); +static constexpr uint32_t kAglShellMinVersion = UINT32_C(10); + +static constexpr uint32_t kXdgDecorationManagerMinVersion = UINT32_C(1); +static constexpr uint32_t kPresentationTimeMinVersion = UINT32_C(1); +static constexpr uint32_t kTearingControlManagerMinVersion = UINT32_C(1); +static constexpr uint32_t kViewporterMinVersion = UINT32_C(1); +static constexpr uint32_t kFractionalScaleManagerMinVersion = UINT32_C(1); + +/// Logging Constants +static constexpr int64_t kLogFlushInterval = INT64_C(5); + + +#endif // INCLUDE_CONFIG_H_ diff --git a/include/wayland-protocols.h b/include/wayland-protocols.h new file mode 100644 index 0000000..fe49dcc --- /dev/null +++ b/include/wayland-protocols.h @@ -0,0 +1,71 @@ +#ifndef INCLUDE_PROTOCOLS_H_ +#define INCLUDE_PROTOCOLS_H_ + +#include "protocols/agl-shell-client-protocol.h" +#include "protocols/agl-shell-desktop-client-protocol.h" +#include "protocols/agl-screenshooter-client-protocol.h" + +#include "protocols/ivi-wm-client-protocol.h" +#include "protocols/ivi-application-client-protocol.h" + +#include "protocols/xdg-shell-client-protocol.h" + +#if defined(WAYLAND_PROTOCOL_HAS_XDG_DECORATION) + +#include "protocols/xdg-decoration-unstable-client-protocol.h" + +#else +struct zxdg_decoration_manager_v1; +struct zxdg_toplevel_decoration_v1; +#endif + +#if defined(WAYLAND_PROTOCOL_HAS_PRESENTATION_TIME) + +#include "protocols/presentation-time-client-protocol.h" + +#else +struct wp_presentation; +#endif + +#if defined(WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE) + +#include "protocols/fractional-scale-v1-client-protocol.h" + +#else +struct wp_fractional_scale_manager_v1; +struct wp_fractional_scale_v1; +#endif + +#if defined(WAYLAND_PROTOCOL_HAS_TEARING_CONTROL) + +#include "protocols/tearing-control-v1-client-protocol.h" + +#else +struct wp_tearing_control_manager_v1; +#endif + +#if defined(WAYLAND_PROTOCOL_HAS_VIEWPORTER) + +#include "protocols/viewporter-client-protocol.h" + +#else +struct wp_viewporter; +struct wp_viewport; +#endif + +#if defined(WAYLAND_PROTOCOL_HAS_DRM_LEASE) + +#include "protocols/drm-lease-v1-client-protocol.h" + +#else +struct wp_drm_lease_connector_v1; +struct wp_drm_lease_device_v1; +struct wp_drm_lease_request_v1; +struct wp_drm_lease_v1; +#endif + +#include + +#include "protocols/xdg-shell-client-protocol.h" + +#endif //INCLUDE_PROTOCOLS_H_ \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2beb515..8573ec1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,28 +16,34 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(CAIRO REQUIRED IMPORTED_TARGET cairo) find_package(OpenGL REQUIRED COMPONENTS EGL) set(WINDOW_MANAGER_SRC - window_manager/display.cc + window_manager/agl_shell.cc + window_manager/ivi_window_manager.cc window_manager/output.cc + window_manager/registrar.cc window_manager/window_manager.cc - window_manager/xdg_wm.cc) + window_manager/xdg_toplevel.cc + window_manager/xdg_window_manager.cc +) set(SEAT_SRC + seat/cursor.cc seat/seat.cc seat/keyboard.cc seat/pointer.cc - seat/cursor.cc - seat/touch.cc) + seat/touch.cc +) set(WINDOW_SRC window/egl.cc window/window.cc - window/window_egl.cc - window/window_vulkan.cc) +) add_library(waypp + logging.cc ${WINDOW_MANAGER_SRC} ${SEAT_SRC} ${WINDOW_SRC}) @@ -47,5 +53,9 @@ target_include_directories(waypp PUBLIC .) target_link_libraries(waypp PUBLIC wayland-gen PkgConfig::GLIB + PkgConfig::CAIRO OpenGL::EGL -) \ No newline at end of file + spdlog +) + +add_sanitizers(waypp) diff --git a/src/window/window_vulkan.h b/src/logging.cc similarity index 55% rename from src/window/window_vulkan.h rename to src/logging.cc index 77abe1d..cae13fd 100644 --- a/src/window/window_vulkan.h +++ b/src/logging.cc @@ -14,21 +14,20 @@ * limitations under the License. */ -#ifndef SRC_WINDOW_WINDOW_VULKAN_H_ -#define SRC_WINDOW_WINDOW_VULKAN_H_ +#include "logging.h" -#include "window.h" +#include "config.h" +Logging::Logging() { + console_sink_ = std::make_shared(); + logger_ = std::make_shared("waypp", console_sink_); + spdlog::set_default_logger(logger_); + spdlog::set_pattern("[%H:%M:%S.%f] [%L] %v"); -class WindowVulkan : public Window { -public: - explicit WindowVulkan(struct wl_display *display, struct wl_compositor *compositor, int width, int height, - ShellType shell_type = XDG, - const std::function &draw_callback = nullptr); + spdlog::flush_on(spdlog::level::err); + spdlog::flush_every(std::chrono::seconds(kLogFlushInterval)); + spdlog::cfg::load_env_levels(); +} - ~WindowVulkan() override; - -private: -}; - -#endif // SRC_WINDOW_WINDOW_VULKAN_H_ \ No newline at end of file +Logging::~Logging() { +} diff --git a/src/window/window_egl.h b/src/logging.h similarity index 51% rename from src/window/window_egl.h rename to src/logging.h index 6cd8502..11600c0 100644 --- a/src/window/window_egl.h +++ b/src/logging.h @@ -14,25 +14,30 @@ * limitations under the License. */ -#ifndef SRC_WINDOW_WINDOW_EGL_H_ -#define SRC_WINDOW_WINDOW_EGL_H_ +#ifndef INCLUDE_LOGGING_H_ +#define INCLUDE_LOGGING_H_ -#include "window.h" -#include "egl.h" +#if !defined(NDEBUG) +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE +#else +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_OFF +#endif -class WindowEgl : public Egl { -public: - explicit WindowEgl(struct wl_display *display, struct wl_compositor *compositor, struct wl_surface *surface, - int width, int height, - Window::ShellType shell_type = Window::ShellType::XDG, - const std::function &draw_callback = nullptr); +#include +#include +#include - ~WindowEgl(); +class Logging { +public: + Logging(); - friend class Egl; + ~Logging(); private: - struct wl_egl_window *egl_window_{}; + std::shared_ptr logger_{}; + std::shared_ptr< + spdlog::sinks::ansicolor_stdout_sink> + console_sink_; }; -#endif // SRC_WINDOW_WINDOW_EGL_H_ \ No newline at end of file +#endif // INCLUDE_LOGGING_H_ \ No newline at end of file diff --git a/src/seat/cursor.cc b/src/seat/cursor.cc index f379bc4..150792d 100644 --- a/src/seat/cursor.cc +++ b/src/seat/cursor.cc @@ -1,121 +1,47 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ #include "cursor.h" -#include +#include +#include -constexpr int kCursorSize = 24; -constexpr char kCursorKindBasic[] = "left_ptr"; -constexpr char kCursorKindClick[] = "hand"; -constexpr char kCursorKindText[] = "left_ptr"; -constexpr char kCursorKindForbidden[] = "pirate"; +#include "logging.h" -/** - * @class Cursor - * @brief Represents a cursor for a Pointer. - */ -Cursor::Cursor(Pointer *parent, struct wl_pointer *pointer, struct wl_shm *shm, struct wl_compositor *compositor, - bool enable, const char *theme_name) - : parent_(parent), - wl_pointer_(pointer), - wl_shm_(shm), - wl_surface_(wl_compositor_create_surface(compositor)), - theme_name_(theme_name), - enable_(enable) { - if (enable) { - theme_ = wl_cursor_theme_load(theme_name_.c_str(), kCursorSize, wl_shm_); + +Cursor::Cursor(struct wl_shm *shm, struct wl_compositor *compositor, int size) { + assert(shm); + wl_surface_ = wl_compositor_create_surface(compositor); + theme_ = wl_cursor_theme_load(nullptr, size, shm); + if (!theme_) { + spdlog::error("unable to load default theme"); + return; } } -/** - * @brief Destructor for the Cursor class. - * - * This function destroys the Cursor object by freeing any allocated resources. - * It destroys the wl_cursor_theme object pointed to by the theme_ pointer. - * It also destroys the wl_surface object pointed to by the wl_surface_ pointer. - */ Cursor::~Cursor() { - if (theme_) + SPDLOG_TRACE("++Cursor::~Cursor()"); + if (theme_) { wl_cursor_theme_destroy(theme_); - - if (wl_surface_) - wl_surface_destroy(wl_surface_); + } + SPDLOG_TRACE("--Cursor::~Cursor()"); } -/** - * @brief Sets the cursor for the specified device and kind. - * - * @param device The device ID. - * @param kind The kind of cursor. - * @return True if the cursor was successfully set, false otherwise. - * - * This function sets the cursor for the specified device and kind. If the enable flag is false, - * the default cursor is set. If the enable flag is true, the cursor is set based on the specified kind. - * - * If the specified kind is not found or if an invalid cursor buffer is encountered, the function returns false. - * If the cursor is successfully set, the function returns true. - */ -bool Cursor::enable(const int32_t device, const char *kind) const { - (void) device; - if (!enable_) { - wl_pointer_set_cursor(wl_pointer_, parent_->get_serial(), - wl_surface_, 0, 0); - wl_surface_damage(wl_surface_, 0, 0, 0, 0); - wl_surface_commit(wl_surface_); - return true; +void Cursor::update_pointer(struct wl_pointer *pointer, uint32_t serial, const char *name) { + auto cursor = wl_cursor_theme_get_cursor(theme_, name); + if (!cursor) { + spdlog::error("unable to load {}", name); + return; } - - if (wl_pointer_) { - const char *cursor_name; - if (strcmp(kind, "basic") == 0) { - cursor_name = kCursorKindBasic; - } else if (strcmp(kind, "click") == 0) { - cursor_name = kCursorKindClick; - } else if (strcmp(kind, "text") == 0) { - cursor_name = kCursorKindText; - } else if (strcmp(kind, "forbidden") == 0) { - cursor_name = kCursorKindForbidden; - } else { - // Cursor kind - return false; - } - - const auto cursor = wl_cursor_theme_get_cursor(theme_, cursor_name); - if (cursor == nullptr) { - // not found - return false; - } - const auto cursor_buffer = wl_cursor_image_get_buffer(cursor->images[0]); - if (cursor_buffer && wl_surface_) { - - wl_pointer_set_cursor(wl_pointer_, parent_->get_serial(), - wl_surface_, - static_cast(cursor->images[0]->hotspot_x), - static_cast(cursor->images[0]->hotspot_y)); - wl_surface_attach(wl_surface_, cursor_buffer, 0, 0); - wl_surface_damage(wl_surface_, 0, 0, - static_cast(cursor->images[0]->width), - static_cast(cursor->images[0]->height)); - wl_surface_commit(wl_surface_); - } else { - // Failed to set cursor: Invalid Cursor Buffer - return false; - } + auto image = cursor->images[0]; + auto buffer = wl_cursor_image_get_buffer(image); + if (!buffer) { + return; } - - return true; + wl_pointer_set_cursor(pointer, serial, + wl_surface_, + static_cast(image->hotspot_x), + static_cast(image->hotspot_y)); + wl_surface_attach(wl_surface_, buffer, 0, 0); + wl_surface_damage(wl_surface_, 0, 0, + static_cast(image->width), static_cast(image->height)); + wl_surface_commit(wl_surface_); } diff --git a/src/seat/cursor.h b/src/seat/cursor.h index 2c9ae1c..7bee4cb 100644 --- a/src/seat/cursor.h +++ b/src/seat/cursor.h @@ -14,39 +14,24 @@ * limitations under the License. */ -#ifndef SRC_SEAT_CURSOR_H_ -#define SRC_SEAT_CURSOR_H_ +#pragma once -#include - -#include - -#include "window_manager/display.h" -#include "pointer.h" - -class Display; - -class Pointer; +#include class Cursor { public: - Cursor(Pointer *parent, struct wl_pointer *pointer, struct wl_shm *shm, struct wl_compositor *compositor, - bool enable, - const char *theme_name = "DMZ-White"); + explicit Cursor(struct wl_shm *shm, struct wl_compositor *compositor, int size = 24); ~Cursor(); - bool enable(int32_t device, const char *kind) const; + void update_pointer(struct wl_pointer *pointer, uint32_t serial, const char *name = "left_ptr"); + + // Disallow copy and assign. + Cursor(const Cursor &) = delete; + + Cursor &operator=(const Cursor &) = delete; private: - Pointer *parent_; - struct wl_pointer *wl_pointer_; struct wl_surface *wl_surface_; struct wl_cursor_theme *theme_; - struct wl_shm *wl_shm_; - bool enable_; - - std::string theme_name_; }; - -#endif // SRC_SEAT_CURSOR_H_ \ No newline at end of file diff --git a/src/seat/keyboard.cc b/src/seat/keyboard.cc index ae79819..a070c22 100644 --- a/src/seat/keyboard.cc +++ b/src/seat/keyboard.cc @@ -16,13 +16,12 @@ #include "keyboard.h" -#include - #include #include #include #include +#include "logging.h" /** * @class Keyboard @@ -33,7 +32,8 @@ */ Keyboard::Keyboard(struct wl_keyboard *keyboard) : keyboard_(keyboard), xkb_context_(xkb_context_new(XKB_CONTEXT_NO_FLAGS)) { - wl_keyboard_add_listener(keyboard, &listener_, this); + SPDLOG_DEBUG("Keyboard"); + wl_keyboard_add_listener(keyboard_, &keyboard_listener_, this); } /** @@ -44,64 +44,34 @@ Keyboard::Keyboard(struct wl_keyboard *keyboard) : keyboard_(keyboard), */ Keyboard::~Keyboard() { wl_keyboard_release(keyboard_); - wl_keyboard_destroy(keyboard_); } -/** - * @brief Handles the enter event from the keyboard - * - * This function is called when the keyboard enters a surface. - * - * @param data The user data associated with the keyboard - * @param keyboard The keyboard object - * @param serial The serial of the event - * @param surface The surface the keyboard entered - * @param keys The keys array - * - * @return None - */ -void Keyboard::handle_enter(void *data, - struct wl_keyboard * /* keyboard */, - uint32_t /* serial */, - struct wl_surface *surface, - struct wl_array * /* keys */) { - std::cerr << "handle_enter" << std::endl; - const auto obj = static_cast(data); - obj->active_surface_ = surface; -} +gboolean Keyboard::handle_repeat(Keyboard *keyboard) { -/** - * @brief This function handles the leave event for the keyboard. - * - * When the keyboard leaves a surface, this function is called to update the - * active surface to nullptr. - * - * @param data A pointer to the Keyboard object. - * @param keyboard A pointer to the wl_keyboard object. - * @param serial The serial of the event. - * @param surface A pointer to the wl_surface object. - */ -void Keyboard::handle_leave(void *data, - struct wl_keyboard * /* keyboard */, - uint32_t /* serial */, - struct wl_surface * /* surface */) { - std::cerr << "handle_leave" << std::endl; - const auto obj = static_cast(data); - obj->active_surface_ = nullptr; + if (keyboard) { + if (keyboard->key_repeat_rate_) { + keyboard->key_timeout_id_ = g_timeout_add(static_cast(keyboard->key_repeat_rate_), + reinterpret_cast(handle_repeat), keyboard); + return TRUE; + } else { + g_source_remove(keyboard->key_timeout_id_); + return FALSE; + } + } + return TRUE; } -/** - * @class Keyboard - * @brief The Keyboard class handles keyboard input events. - * - * This class provides functionality to handle keyboard events such as keymap changes. - */ void Keyboard::handle_keymap(void *data, - struct wl_keyboard * /* keyboard */, + struct wl_keyboard *keyboard, uint32_t /* format */, int fd, uint32_t size) { const auto obj = static_cast(data); + if (obj->keyboard_ != keyboard) { + return; + } + SPDLOG_DEBUG("handle_keymap"); + char *keymap_string = static_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); xkb_keymap_unref(obj->keymap_); obj->keymap_ = xkb_keymap_new_from_string(obj->xkb_context_, keymap_string, @@ -113,25 +83,42 @@ void Keyboard::handle_keymap(void *data, obj->xkb_state_ = xkb_state_new(obj->keymap_); } -/** - * @brief Handles key events from the keyboard. - * - * This function is called when a key is pressed or released on the keyboard. - * - * @param data The pointer to the Keyboard instance. - * @param keyboard The wl_keyboard object associated with the event. - * @param serial The serial number of the event. - * @param time The timestamp of the event. - * @param key The key that was pressed or released. - * @param state The state of the key (pressed or released). - */ +void Keyboard::handle_enter(void *data, + struct wl_keyboard *keyboard, + uint32_t /* serial */, + struct wl_surface *surface, + struct wl_array * /* keys */) { + const auto obj = static_cast(data); + if (obj->keyboard_ != keyboard) { + return; + } + SPDLOG_DEBUG("[Keyboard] handle_enter"); + obj->active_surface_ = surface; +} + +void Keyboard::handle_leave(void *data, + struct wl_keyboard *keyboard, + uint32_t /* serial */, + struct wl_surface * /* surface */) { + const auto obj = static_cast(data); + if (obj->keyboard_ != keyboard) { + return; + } + SPDLOG_DEBUG("[Keyboard] handle_leave"); + obj->active_surface_ = nullptr; +} + void Keyboard::handle_key(void *data, - struct wl_keyboard * /* keyboard */, + struct wl_keyboard *keyboard, uint32_t /* serial */, uint32_t /* time */, uint32_t key, uint32_t state) { const auto obj = static_cast(data); + if (obj->keyboard_ != keyboard) { + return; + } + SPDLOG_DEBUG("[Keyboard] handle_key"); if (!obj->xkb_state_) return; @@ -152,7 +139,7 @@ void Keyboard::handle_key(void *data, // only use the first symbol until the use case for two is clarified keysym = key_symbols[0]; for (int i = 0; i < res; i++) { - std::cerr << "xkb keysym: 0x" << std::hex << key_symbols[i] << std::dec << std::endl; + SPDLOG_DEBUG("xkb keysym: 0x{}", key_symbols[i]); } } } @@ -164,69 +151,41 @@ void Keyboard::handle_key(void *data, } } -/***************************************************************************/ void Keyboard::handle_modifiers(void *data, - struct wl_keyboard * /* keyboard */, + struct wl_keyboard *keyboard, uint32_t /* serial */, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { const auto obj = static_cast(data); - xkb_state_update_mask(obj->xkb_state_, mods_depressed, mods_latched, mods_locked, 0, 0, group); -} - -/** - * @brief Handles the repeated key events for the Keyboard. - * - * This function is called when a key is being held down and needs to be repeated. - * - * @param keyboard A pointer to the Keyboard instance. - * - * @return TRUE if the key repeat rate is set, FALSE otherwise. - */ -gboolean Keyboard::handle_repeat(Keyboard *keyboard) { - - if (keyboard) { - if (keyboard->key_repeat_rate_) { - keyboard->key_timeout_id_ = g_timeout_add(static_cast(keyboard->key_repeat_rate_), - reinterpret_cast(handle_repeat), keyboard); - return TRUE; - } else { - g_source_remove(keyboard->key_timeout_id_); - return FALSE; - } + if (obj->keyboard_ != keyboard) { + return; } - return TRUE; + SPDLOG_DEBUG("[Keyboard] handle_modifiers"); + xkb_state_update_mask(obj->xkb_state_, mods_depressed, mods_latched, mods_locked, 0, 0, group); } -/** -* @brief Handles repeat info for the Keyboard class. -* -* This function is called when repeat rate and delay of key repeats are received. -* It creates a timeout to call the handle_repeat function at the specified delay. -* -* @param data A pointer to the Keyboard object. -* @param wl_keyboard A pointer to the wl_keyboard object. -* @param rate The repeat rate of key events in milliseconds. -* @param delay The delay before key repeat starts in milliseconds. -*/ void Keyboard::handle_repeat_info(void *data, - struct wl_keyboard * /* wl_keyboard */, + struct wl_keyboard *keyboard, int32_t rate, int32_t delay) { const auto obj = static_cast(data); + if (obj->keyboard_ != keyboard) { + return; + } + SPDLOG_DEBUG("[Keyboard] handle_repeat_info: rate: {}, delay: {}", rate, delay); obj->key_timeout_id_ = g_timeout_add(static_cast(delay), reinterpret_cast(handle_repeat), obj); obj->key_repeat_rate_ = rate; obj->key_timeout_id_ = g_timeout_add(static_cast(delay), reinterpret_cast(handle_repeat), obj); } -const struct wl_keyboard_listener Keyboard::listener_ = { +const struct wl_keyboard_listener Keyboard::keyboard_listener_ = { .keymap = handle_keymap, .enter = handle_enter, .leave = handle_leave, .key = handle_key, .modifiers = handle_modifiers, - .repeat_info = handle_repeat_info -}; \ No newline at end of file + .repeat_info = handle_repeat_info, +}; diff --git a/src/seat/keyboard.h b/src/seat/keyboard.h index 751517a..7af72a9 100644 --- a/src/seat/keyboard.h +++ b/src/seat/keyboard.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef SRC_SEAT_KEYBOARD_H_ -#define SRC_SEAT_KEYBOARD_H_ +#pragma once #include @@ -40,46 +39,144 @@ class Keyboard { int32_t key_repeat_rate_{}; + /** + * @brief Handles the repeated key events for the Keyboard. + * + * This function is called when a key is being held down and needs to be repeated. + * + * @param keyboard A pointer to the Keyboard instance. + * + * @return TRUE if the key repeat rate is set, FALSE otherwise. + */ static gboolean handle_repeat(Keyboard *keyboard); - static void handle_enter(void * /* data */, - struct wl_keyboard * /* keyboard */, - uint32_t /* serial */, - struct wl_surface * /* surface */, - struct wl_array * /* keys */); - - static void handle_leave(void * /* data */, - struct wl_keyboard * /* keyboard */, - uint32_t /* serial */, - struct wl_surface * /* surface */); - - static void handle_keymap(void * /* data */, - struct wl_keyboard * /* keyboard */, - uint32_t /* format */, - int /* fd */, - uint32_t /* size */); - - static void handle_key(void * /* data */, - struct wl_keyboard * /* keyboard */, - uint32_t /* serial */, - uint32_t /* time */, - uint32_t /* key */, - uint32_t /* state */); - - static void handle_modifiers(void * /* data */, - struct wl_keyboard * /* keyboard */, - uint32_t /* serial */, - uint32_t /* mods_depressed */, - uint32_t /* mods_latched */, - uint32_t /* mods_locked */, - uint32_t /* group */); - - static void handle_repeat_info(void * /* data */, - struct wl_keyboard * /* wl_keyboard */, - int32_t /* rate */, - int32_t /* delay */); - - static const struct wl_keyboard_listener listener_; + /** + * keyboard mapping + * + * This event provides a file descriptor to the client which can + * be memory-mapped in read-only mode to provide a keyboard mapping + * description. + * + * From version 7 onwards, the fd must be mapped with MAP_PRIVATE + * by the recipient, as MAP_SHARED may fail. + * @param format keymap format + * @param fd keymap file descriptor + * @param size keymap size, in bytes + */ + static void handle_keymap(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t format, + int32_t fd, + uint32_t size); + + /** + * enter event + * + * Notification that this seat's keyboard focus is on a certain + * surface. + * + * The compositor must send the wl_keyboard.modifiers event after + * this event. + * @param serial serial number of the enter event + * @param surface surface gaining keyboard focus + * @param keys the currently pressed keys + */ + static void handle_enter(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface, + struct wl_array *keys); + + /** + * leave event + * + * Notification that this seat's keyboard focus is no longer on a + * certain surface. + * + * The leave notification is sent before the enter notification for + * the new focus. + * + * After this event client must assume that all keys, including + * modifiers, are lifted and also it must stop key repeating if + * there's some going on. + * @param serial serial number of the leave event + * @param surface surface that lost keyboard focus + */ + static void handle_leave(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface); + + /** + * key event + * + * A key was pressed or released. The time argument is a + * timestamp with millisecond granularity, with an undefined base. + * + * The key is a platform-specific key code that can be interpreted + * by feeding it to the keyboard mapping (see the keymap event). + * + * If this event produces a change in modifiers, then the resulting + * wl_keyboard.modifiers event must be sent after this event. + * @param serial serial number of the key event + * @param time timestamp with millisecond granularity + * @param key key that produced the event + * @param state physical state of the key + */ + static void handle_key(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state); + + /** + * modifier and group state + * + * Notifies clients that the modifier and/or group state has + * changed, and it should update its local state. + * @param serial serial number of the modifiers event + * @param mods_depressed depressed modifiers + * @param mods_latched latched modifiers + * @param mods_locked locked modifiers + * @param group keyboard layout + */ + static void handle_modifiers(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group); + + /** + * repeat rate and delay + * + * Informs the client about the keyboard's repeat rate and delay. + * + * This event is sent as soon as the wl_keyboard object has been + * created, and is guaranteed to be received by the client before + * any key press event. + * + * Negative values for either rate or delay are illegal. A rate of + * zero will disable any repeating (regardless of the value of + * delay). + * + * This event can be sent later on as well with a new value if + * necessary, so clients should continue listening for the event + * past the creation of wl_keyboard. + * @param rate the rate of repeating keys in characters per second + * @param delay delay in milliseconds since key down until repeating starts + * @since 4 + */ + static void handle_repeat_info(void *data, + struct wl_keyboard *wl_keyboard, + int32_t rate, + int32_t delay); + + /** + * @ingroup iface_wl_keyboard + * @struct wl_keyboard_listener + */ + static const struct wl_keyboard_listener keyboard_listener_; }; - -#endif // SRC_SEAT_KEYBOARD_H_ \ No newline at end of file diff --git a/src/seat/pointer.cc b/src/seat/pointer.cc index 848e2d3..cefd211 100644 --- a/src/seat/pointer.cc +++ b/src/seat/pointer.cc @@ -15,27 +15,22 @@ */ #include "pointer.h" -#include "xdg-shell-client-protocol.h" - -#include +#include "wayland-protocols.h" #include #include +#include "logging.h" + /** * @brief Pointer class represents a Wayland pointer device. * * The Pointer class is responsible for handling Wayland pointer events and managing the cursor. */ -Pointer::Pointer(struct wl_pointer *pointer, struct wl_shm *shm, struct wl_compositor *compositor, bool enable_cursor) : - pointer_(pointer), - shm_(shm), - enable_cursor_(enable_cursor) { - wl_pointer_add_listener(pointer, &listener_, this); - if (enable_cursor_) { - cursor_ = std::make_unique(this, pointer_, shm_, compositor, enable_cursor_); - cursor_->enable(0, "basic"); - } +Pointer::Pointer(struct wl_pointer *pointer) : + pointer_(pointer) { + SPDLOG_DEBUG("Pointer"); + wl_pointer_add_listener(pointer, &pointer_listener_, this); } /** @@ -50,11 +45,7 @@ Pointer::Pointer(struct wl_pointer *pointer, struct wl_shm *shm, struct wl_compo * @param enable_cursor A boolean flag indicating whether to enable cursor. */ Pointer::~Pointer() { - if (cursor_) - cursor_.reset(); - wl_pointer_release(pointer_); - wl_pointer_destroy(pointer_); } /** @@ -65,13 +56,17 @@ Pointer::~Pointer() { * such as enter, leave, motion, button, axis, frame, axis source, axis stop, * and axis discrete events. */ -void Pointer::handle_enter(void * /* data */, - struct wl_pointer * /* pointer */, +void Pointer::handle_enter(void *data, + struct wl_pointer *pointer, uint32_t /* serial */, struct wl_surface * /* surface */, wl_fixed_t /* sx */, wl_fixed_t /* sy */) { - std::cerr << "Pointer::handle_enter" << std::endl; + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_enter"); } /** @@ -85,11 +80,15 @@ void Pointer::handle_enter(void * /* data */, * @param serial The serial number of the event. * @param surface The surface that the pointer left. */ -void Pointer::handle_leave(void * /* data */, - struct wl_pointer * /* pointer */, +void Pointer::handle_leave(void *data, + struct wl_pointer *pointer, uint32_t /* serial */, struct wl_surface * /* surface */) { - std::cerr << "Pointer::handle_leave" << std::endl; + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_leave"); } /** @@ -104,12 +103,16 @@ void Pointer::handle_leave(void * /* data */, * @param sx The X coordinate of the pointer's absolute position. * @param sy The Y coordinate of the pointer's absolute position. */ -void Pointer::handle_motion(void * /* data */, - struct wl_pointer * /* pointer */, +void Pointer::handle_motion(void *data, + struct wl_pointer *pointer, uint32_t /* time */, wl_fixed_t /* sx */, wl_fixed_t /* sy */) { - std::cerr << "Pointer::handle_motion" << std::endl; + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_motion"); } /** @@ -163,13 +166,17 @@ enum xdg_toplevel_resize_edge component_edge(const int width, const int height, * @param button The button that triggered the event * @param state The state of the button (pressed or released) */ -void Pointer::handle_button(void * /* data */, - struct wl_pointer * /* wl_pointer */, +void Pointer::handle_button(void *data, + struct wl_pointer *pointer, uint32_t /* serial */, uint32_t /* time */, uint32_t button, uint32_t state) { - std::cerr << "Pointer::handle_button" << std::endl; + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_button"); if (button == BTN_LEFT) { if (state == WL_POINTER_BUTTON_STATE_PRESSED) { } @@ -189,12 +196,16 @@ void Pointer::handle_button(void * /* data */, * * @details Prints "Pointer::handle_axis" to the standard error output. */ -void Pointer::handle_axis(void * /* data */, - struct wl_pointer * /* wl_pointer */, +void Pointer::handle_axis(void *data, + struct wl_pointer *pointer, uint32_t /* time */, uint32_t /* axis */, wl_fixed_t /* value */) { - std::cerr << "Pointer::handle_axis" << std::endl; + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_axis"); } /** @@ -205,9 +216,13 @@ void Pointer::handle_axis(void * /* data */, * @param data The user data associated with the pointer. * @param wl_pointer The pointer object. */ -void Pointer::handle_frame(void * /* data */, - struct wl_pointer * /* wl_pointer */) { - std::cerr << "Pointer::handle_frame" << std::endl; +void Pointer::handle_frame(void *data, + struct wl_pointer *pointer) { + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_frame"); } /** @@ -220,10 +235,14 @@ void Pointer::handle_frame(void * /* data */, * This function is called when the axis source event is received for the Pointer object. * It prints a message to the standard error stream. */ -void Pointer::handle_axis_source(void * /* data */, - struct wl_pointer * /* wl_pointer */, +void Pointer::handle_axis_source(void *data, + struct wl_pointer *pointer, uint32_t /* axis_source */) { - std::cerr << "Pointer::handle_axis_source" << std::endl; + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_axis_source"); } /** @@ -236,11 +255,15 @@ void Pointer::handle_axis_source(void * /* data */, * @param time The timestamp of the event. * @param axis The axis that stopped. */ -void Pointer::handle_axis_stop(void * /* data */, - struct wl_pointer * /* wl_pointer */, +void Pointer::handle_axis_stop(void *data, + struct wl_pointer *pointer, uint32_t /* time */, uint32_t /* axis */) { - std::cerr << "Pointer::handle_axis_stop" << std::endl; + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_axis_stop"); } /** @@ -253,21 +276,13 @@ void Pointer::handle_axis_stop(void * /* data */, * @param axis The axis value. * @param discrete The discrete value. */ -void Pointer::handle_axis_discrete(void * /* data */, - struct wl_pointer * /* wl_pointer */, +void Pointer::handle_axis_discrete(void *data, + struct wl_pointer *pointer, uint32_t /* axis */, int32_t /* discrete */) { - std::cerr << "Pointer::handle_axis_discrete" << std::endl; + auto obj = static_cast(data); + if (obj->pointer_ != pointer) { + return; + } + SPDLOG_DEBUG("Pointer::handle_axis_discrete"); } - -const struct wl_pointer_listener Pointer::listener_ = { - .enter = handle_enter, - .leave = handle_leave, - .motion = handle_motion, - .button = handle_button, - .axis = handle_axis, - .frame = handle_frame, - .axis_source = handle_axis_source, - .axis_stop = handle_axis_stop, - .axis_discrete = handle_axis_discrete, -}; diff --git a/src/seat/pointer.h b/src/seat/pointer.h index 021a701..eb90f1f 100644 --- a/src/seat/pointer.h +++ b/src/seat/pointer.h @@ -14,83 +14,77 @@ * limitations under the License. */ -#ifndef SRC_SEAT_POINTER_H_ -#define SRC_SEAT_POINTER_H_ +#pragma once #include -#include "cursor.h" - -class Cursor; - class Pointer { public: - explicit Pointer(struct wl_pointer *pointer_, struct wl_shm *shm, struct wl_compositor *compositor, - bool enable_cursor = true); + explicit Pointer(struct wl_pointer *pointer); ~Pointer(); - friend class Cursor; - private: struct wl_pointer *pointer_; - struct wl_shm *shm_; - - bool enable_cursor_{}; - std::unique_ptr cursor_; uint32_t serial_{}; - [[nodiscard]] uint32_t get_serial() const { return serial_; } - - static void handle_enter(void * /* data */, - struct wl_pointer * /* pointer */, - uint32_t /* serial */, - struct wl_surface * /* surface */, - wl_fixed_t /* sx */, - wl_fixed_t /* sy */); - - static void handle_leave(void * /* data */, - struct wl_pointer * /* pointer */, - uint32_t /* serial */, - struct wl_surface * /* surface */); - - static void handle_motion(void * /* data */, - struct wl_pointer * /* pointer */, - uint32_t /* time */, - wl_fixed_t /* sx */, - wl_fixed_t /* sy */); - - static void handle_button(void * /* data */, - struct wl_pointer * /* wl_pointer */, - uint32_t /* serial */, - uint32_t /* time */, - uint32_t /* button */, - uint32_t /* state */); - - static void handle_axis(void * /* data */, - struct wl_pointer * /* wl_pointer */, - uint32_t /* time */, - uint32_t /* axis */, - wl_fixed_t /* value */); - - static void handle_frame(void * /* data */, - struct wl_pointer * /* wl_pointer */); - - static void handle_axis_source(void * /* data */, - struct wl_pointer * /* wl_pointer */, - uint32_t /* axis_source */); - - static void handle_axis_stop(void * /* data */, - struct wl_pointer * /* wl_pointer */, - uint32_t /* time */, - uint32_t /* axis */); - - static void handle_axis_discrete(void * /* data */, - struct wl_pointer * /* wl_pointer */, - uint32_t /* axis */, - int32_t /* discrete */); - - static const struct wl_pointer_listener listener_; + static void handle_enter(void *data, + struct wl_pointer *pointer, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t sx, + wl_fixed_t sy); + + static void handle_leave(void *data, + struct wl_pointer *pointer, + uint32_t serial, + struct wl_surface *surface); + + static void handle_motion(void *data, + struct wl_pointer *pointer, + uint32_t time, + wl_fixed_t sx, + wl_fixed_t sy); + + static void handle_button(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state); + + static void handle_axis(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value); + + static void handle_frame(void *data, + struct wl_pointer *wl_pointer); + + static void handle_axis_source(void *data, + struct wl_pointer *wl_pointer, + uint32_t axis_source); + + static void handle_axis_stop(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis); + + static void handle_axis_discrete(void *data, + struct wl_pointer *wl_pointer, + uint32_t axis, + int32_t discrete); + + static constexpr struct wl_pointer_listener pointer_listener_ = { + .enter = handle_enter, + .leave = handle_leave, + .motion = handle_motion, + .button = handle_button, + .axis = handle_axis, + .frame = handle_frame, + .axis_source = handle_axis_source, + .axis_stop = handle_axis_stop, + .axis_discrete = handle_axis_discrete, + }; }; - -#endif // SRC_SEAT_POINTER_H_ \ No newline at end of file diff --git a/src/seat/seat.cc b/src/seat/seat.cc index 617cff5..c6a1770 100644 --- a/src/seat/seat.cc +++ b/src/seat/seat.cc @@ -16,7 +16,7 @@ #include "seat.h" -#include +#include "logging.h" /** * @class Seat @@ -25,13 +25,8 @@ * The Seat class provides a representation of a seat in a Wayland compositor. It is used to handle input events from * devices such as keyboards, pointers, and touchscreens. */ -Seat::Seat(struct wl_seat *seat, struct wl_shm *shm, struct wl_compositor *compositor, bool enable_cursor, - uint32_t version) : +Seat::Seat(struct wl_seat *seat) : wl_seat_(seat), - wl_shm_(shm), - wl_compositor_(compositor), - enable_cursor_(enable_cursor), - version_(version), capabilities_() { wl_seat_add_listener(seat, &listener_, this); } @@ -47,12 +42,10 @@ void Seat::handle_capabilities(void *data, struct wl_seat *seat, uint32_t caps) { const auto obj = static_cast(data); - assert(obj->wl_seat_ == seat); obj->capabilities_ = caps; if (caps & WL_SEAT_CAPABILITY_POINTER && !obj->pointer_) { - obj->pointer_ = std::make_unique(wl_seat_get_pointer(seat), obj->wl_shm_, obj->wl_compositor_, - obj->enable_cursor_); + obj->pointer_ = std::make_unique(wl_seat_get_pointer(seat)); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && obj->pointer_) { obj->pointer_.reset(); } @@ -85,11 +78,9 @@ void Seat::handle_name(void *data, struct wl_seat *seat, const char *name) { const auto obj = static_cast(data); - assert(obj->wl_seat_ == seat); + if (obj->wl_seat_ != seat) { + return; + } obj->name_ = name; + SPDLOG_DEBUG("Seat: {}", obj->name_); } - -const struct wl_seat_listener Seat::listener_ = { - .capabilities = handle_capabilities, - .name = handle_name, -}; diff --git a/src/seat/seat.h b/src/seat/seat.h index 7a3928f..48423bd 100644 --- a/src/seat/seat.h +++ b/src/seat/seat.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef SRC_SEAT_SEAT_H_ -#define SRC_SEAT_SEAT_H_ +#pragma once #include #include @@ -34,8 +33,7 @@ class Touch; class Seat { public: - explicit Seat(struct wl_seat *seat, struct wl_shm *shm, struct wl_compositor *compositor, bool enable_cursor, - uint32_t version); + explicit Seat(struct wl_seat *seat); [[nodiscard]] struct wl_seat *get_seat() const { return wl_seat_; }; @@ -43,14 +41,8 @@ class Seat { [[nodiscard]] const std::string &get_name() const { return name_; }; - [[nodiscard]] const uint32_t get_version() const { return version_; } - private: struct wl_seat *wl_seat_; - struct wl_shm *wl_shm_; - struct wl_compositor *wl_compositor_; - bool enable_cursor_; - uint32_t version_; uint32_t capabilities_; std::string name_; @@ -58,15 +50,16 @@ class Seat { std::unique_ptr pointer_; std::unique_ptr touch_; - static void handle_capabilities(void * /* data */, - struct wl_seat * /* seat */, - uint32_t /* caps */); + static void handle_capabilities(void *data, + struct wl_seat *seat, + uint32_t caps); - static void handle_name(void * /* data */, - struct wl_seat * /* seat */, - const char * /* name */); + static void handle_name(void *data, + struct wl_seat *seat, + const char *name); - static const struct wl_seat_listener listener_; + static constexpr struct wl_seat_listener listener_ = { + .capabilities = handle_capabilities, + .name = handle_name, + }; }; - -#endif // SRC_SEAT_SEAT_H_ \ No newline at end of file diff --git a/src/seat/touch.cc b/src/seat/touch.cc index efdb8e2..42dc652 100644 --- a/src/seat/touch.cc +++ b/src/seat/touch.cc @@ -16,15 +16,16 @@ #include "touch.h" -#include +#include "logging.h" /** * @class Touch * * The Touch class represents a touch input device. */ -Touch::Touch(struct wl_touch *touch) : touch_(touch) { - wl_touch_add_listener(touch, &listener_, this); +Touch::Touch(struct wl_touch *wl_touch) : touch_(wl_touch) { + SPDLOG_DEBUG("Touch"); + wl_touch_add_listener(wl_touch, &listener_, this); } /** @@ -34,7 +35,6 @@ Touch::Touch(struct wl_touch *touch) : touch_(touch) { */ Touch::~Touch() { wl_touch_release(touch_); - wl_touch_destroy(touch_); } /** @@ -51,15 +51,19 @@ Touch::~Touch() { * @param x_w The X coordinate of the touch point in wl_fixed_t format. * @param y_w The Y coordinate of the touch point in wl_fixed_t format. */ -void Touch::handle_down(void * /* data */, - struct wl_touch * /* wl_touch */, +void Touch::handle_down(void *data, + struct wl_touch *touch, uint32_t /* serial */, uint32_t /* time */, struct wl_surface * /* surface */, int32_t /* id */, wl_fixed_t /* x_w */, wl_fixed_t /* y_w */) { - std::cerr << "Touch::handle_down" << std::endl; + const auto obj = static_cast(data); + if (obj->touch_ != touch) { + return; + } + SPDLOG_DEBUG("Touch::handle_down"); } /** @@ -75,12 +79,16 @@ void Touch::handle_down(void * /* data */, * * @return None. */ -void Touch::handle_up(void * /* data */, - struct wl_touch * /* wl_touch */, +void Touch::handle_up(void *data, + struct wl_touch *touch, uint32_t /* serial */, uint32_t /* time */, int32_t /* id */) { - std::cerr << "Touch::handle_up" << std::endl; + const auto obj = static_cast(data); + if (obj->touch_ != touch) { + return; + } + SPDLOG_DEBUG("Touch::handle_up"); } /** @@ -98,13 +106,17 @@ void Touch::handle_up(void * /* data */, * * @return None. */ -void Touch::handle_motion(void * /* data */, - struct wl_touch * /* wl_touch */, +void Touch::handle_motion(void *data, + struct wl_touch *touch, uint32_t /* time */, int32_t /* id */, wl_fixed_t /* x_w */, wl_fixed_t /* y_w */) { - std::cerr << "Touch::handle_motion" << std::endl; + const auto obj = static_cast(data); + if (obj->touch_ != touch) { + return; + } + SPDLOG_DEBUG("Touch::handle_motion"); } /** @@ -118,8 +130,12 @@ void Touch::handle_motion(void * /* data */, * * @return void */ -void Touch::handle_cancel(void * /* data */, struct wl_touch * /* wl_touch */) { - std::cerr << "Touch::handle_cancel" << std::endl; +void Touch::handle_cancel(void *data, struct wl_touch *touch) { + const auto obj = static_cast(data); + if (obj->touch_ != touch) { + return; + } + SPDLOG_DEBUG("Touch::handle_cancel"); } /** @@ -128,15 +144,11 @@ void Touch::handle_cancel(void * /* data */, struct wl_touch * /* wl_touch */) { * * It handles touch events from a wl_touch object and provides callback functions for various touch events. */ -void Touch::handle_frame(void * /* data */, - struct wl_touch * /* wl_touch */) { - std::cerr << "Touch::handle_frame" << std::endl; +void Touch::handle_frame(void *data, + struct wl_touch *touch) { + const auto obj = static_cast(data); + if (obj->touch_ != touch) { + return; + } + SPDLOG_DEBUG("Touch::handle_frame"); } - -const struct wl_touch_listener Touch::listener_ = { - .down = handle_down, - .up = handle_up, - .motion = handle_motion, - .frame = handle_frame, - .cancel = handle_cancel, -}; \ No newline at end of file diff --git a/src/seat/touch.h b/src/seat/touch.h index e23fc8e..aab96ba 100644 --- a/src/seat/touch.h +++ b/src/seat/touch.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef SRC_SEAT_TOUCH_H_ -#define SRC_SEAT_TOUCH_H_ +#pragma once #include @@ -23,7 +22,7 @@ class Touch { public: - explicit Touch(struct wl_touch *touch); + explicit Touch(struct wl_touch *wl_touch); ~Touch(); @@ -31,33 +30,37 @@ class Touch { struct wl_touch *touch_; static void handle_down(void *data, - struct wl_touch * /* wl_touch */, - uint32_t /* serial */, - uint32_t /* time */, + struct wl_touch *wl_touch, + uint32_t serial, + uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w); static void handle_up(void *data, - struct wl_touch * /* wl_touch */, - uint32_t /* serial */, - uint32_t /* time */, + struct wl_touch *wl_touch, + uint32_t serial, + uint32_t time, int32_t id); static void handle_motion(void *data, - struct wl_touch * /* wl_touch */, - uint32_t /* time */, + struct wl_touch *wl_touch, + uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w); - static void handle_cancel(void *data, struct wl_touch * /* wl_touch */); + static void handle_cancel(void *data, struct wl_touch *wl_touch); - static void handle_frame(void * /* data */, - struct wl_touch * /* wl_touch */); + static void handle_frame(void *data, + struct wl_touch *wl_touch); - static const struct wl_touch_listener listener_; + static constexpr struct wl_touch_listener listener_ = { + .down = handle_down, + .up = handle_up, + .motion = handle_motion, + .frame = handle_frame, + .cancel = handle_cancel, + }; }; - -#endif // SRC_SEAT_TOUCH_H_ \ No newline at end of file diff --git a/src/window/egl.cc b/src/window/egl.cc index 3ada1e5..4f34b3d 100644 --- a/src/window/egl.cc +++ b/src/window/egl.cc @@ -16,10 +16,10 @@ #include "egl.h" -#include - -#include #include +#include + +#include "logging.h" /** @@ -28,64 +28,55 @@ * This class provides functionality for initializing EGL, choosing an EGL configuration, * creating an EGL context, and managing various EGL extensions. */ -Egl::Egl(struct wl_display *display) { - dpy_ = eglGetDisplay(display); +Egl::Egl(struct wl_display *display, struct wl_surface *wl_surface, int width, int height, + const int32_t *context_attribs, size_t context_attribs_size, + const int32_t *config_attribs, size_t config_attribs_size, + int buffer_bpp) : + dpy_(eglGetDisplay(display)), + context_attribs_(context_attribs, context_attribs + context_attribs_size), + config_attribs_(config_attribs, config_attribs + config_attribs_size), + buffer_bpp_(buffer_bpp), + wl_surface_(wl_surface), + width_(width), height_(height) { + SPDLOG_TRACE("++Egl::Egl()"); EGLBoolean ret = eglInitialize(dpy_, &major_, &minor_); if (ret == EGL_FALSE) { throw std::runtime_error("eglInitialize failed."); } + ret = eglBindAPI(EGL_OPENGL_ES_API); - if (ret == EGL_FALSE) { + if (ret != EGL_TRUE) { throw std::runtime_error("eglBindAPI failed."); } EGLint count; - ret = eglGetConfigs(dpy_, nullptr, 0, &count); - if (ret == EGL_FALSE) { - throw std::runtime_error("eglGetConfigs failed."); - } + eglGetConfigs(dpy_, nullptr, 0, &count); + SPDLOG_DEBUG("EGL has {} configs", count); - auto *configs = static_cast( + auto *configs = reinterpret_cast( calloc(static_cast(count), sizeof(EGLConfig))); EGLint n; - ret = eglChooseConfig(dpy_, kEglConfigAttribs.data(), configs, count, &n); - if (ret == EGL_FALSE) { - throw std::runtime_error("eglChooseConfig failed"); - } + ret = eglChooseConfig(dpy_, config_attribs_.data(), configs, count, &n); - EGLint size; + EGLint red_size; for (EGLint i = 0; i < n; i++) { - ret = eglGetConfigAttrib(dpy_, configs[i], EGL_BUFFER_SIZE, &size); - if (ret == EGL_FALSE) { - throw std::runtime_error("eglGetConfigAttrib failed"); - } - if (buffer_size_ <= size) { - std::memcpy(&config_, &configs[i], sizeof(EGLConfig)); + eglGetConfigAttrib(dpy_, configs[i], EGL_BUFFER_SIZE, &buffer_bpp); + eglGetConfigAttrib(dpy_, configs[i], EGL_RED_SIZE, &red_size); + if ((buffer_bpp_ == 0 || buffer_bpp_ == buffer_bpp) && red_size < 10) { + config_ = configs[i]; break; } } free(configs); - - context_ = eglCreateContext(dpy_, config_, EGL_NO_CONTEXT, - kEglContextAttribs.data()); - - resource_context_ = - eglCreateContext(dpy_, config_, context_, kEglContextAttribs.data()); - - texture_context_ = - eglCreateContext(dpy_, config_, context_, kEglContextAttribs.data()); - - // frame callback requires non-blocking -#if 0 // Failing - ret = eglSwapInterval(dpy_, 0); - if (ret == EGL_FALSE) { - throw std::runtime_error("eglSwapInterval failed"); + if (config_ == nullptr) { + spdlog::critical("did not find config with buffer size {}", buffer_bpp_); + exit(EXIT_FAILURE); } -#endif - (void) make_current(); + context_ = eglCreateContext(dpy_, config_, EGL_NO_CONTEXT, + context_attribs_.data()); #if !defined(NDEBUG) egl_khr_debug_init(); @@ -104,14 +95,22 @@ Egl::Egl(struct wl_display *display) { eglGetProcAddress("eglSwapBuffersWithDamageKHR")); } - if (has_egl_extension(extensions, "EGL_KHR_partial_update")) { + if (has_egl_extension(extensions, "EGL_EXT_partial_update")) { + pfSetDamageRegion_ = reinterpret_cast( + eglGetProcAddress("eglSetDamageRegionEXT")); + + } else if (has_egl_extension(extensions, "EGL_KHR_partial_update")) { pfSetDamageRegion_ = reinterpret_cast( eglGetProcAddress("eglSetDamageRegionKHR")); } - has_egl_ext_buffer_age_ = has_egl_extension(extensions, "EGL_EXT_buffer_age"); - (void) clear_current(); + wl_egl_window_ = wl_egl_window_create(wl_surface_, width_, height_); + eglMakeCurrent(dpy_, egl_surface_, egl_surface_, context_); + egl_surface_ = eglCreateWindowSurface(dpy_, config_, reinterpret_cast(wl_egl_window_), + nullptr); + eglMakeCurrent(dpy_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + SPDLOG_TRACE("--Egl::Egl()"); } /** @@ -120,8 +119,10 @@ Egl::Egl(struct wl_display *display) { * This destructor terminates the EGL display and releases resources associated with the EGL thread. */ Egl::~Egl() { + SPDLOG_TRACE("++Egl::~Egl()"); eglTerminate(dpy_); eglReleaseThread(); + SPDLOG_TRACE("--Egl::~Egl()"); } /** @@ -132,11 +133,12 @@ Egl::~Egl() { * * \return True if the context was made current successfully, false otherwise. */ -bool Egl::make_current() const { +void Egl::make_current() { + SPDLOG_TRACE("++Egl::make_current()"); if (eglGetCurrentContext() != context_) { eglMakeCurrent(dpy_, egl_surface_, egl_surface_, context_); } - return true; + SPDLOG_TRACE("--Egl::make_current()"); } /** @@ -148,11 +150,12 @@ bool Egl::make_current() const { * * @return true if the current context was cleared successfully, false otherwise. */ -bool Egl::clear_current() const { +void Egl::clear_current() { + SPDLOG_TRACE("++Egl::clear_current()"); if (eglGetCurrentContext() != EGL_NO_CONTEXT) { eglMakeCurrent(dpy_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } - return true; + SPDLOG_TRACE("--Egl::clear_current()"); } /** @@ -162,40 +165,10 @@ bool Egl::clear_current() const { * * @return True if the swap was successful, false otherwise. */ -bool Egl::swap_buffers() const { +void Egl::swap_buffers() { + SPDLOG_TRACE("++Egl::swap_buffers()"); eglSwapBuffers(dpy_, egl_surface_); - return true; -} - -/** - * @brief Checks if the resource context is currently active and makes it current if not. - * - * This function checks if the resource context is currently active using eglGetCurrentContext(). - * If the current context is not equal to the resource context, it calls eglMakeCurrent() to make - * the resource context current. Finally, it returns true to indicate that the operation was successful. - * - * @return true if the resource context is successfully made current, false otherwise. - */ -bool Egl::make_resource_current() const { - if (eglGetCurrentContext() != context_) { - eglMakeCurrent(dpy_, EGL_NO_SURFACE, EGL_NO_SURFACE, resource_context_); - } - return true; -} - -/** - * @brief Checks if the texture context is the current context and makes it the current context if not. - * - * This function checks if the texture context is the current context using eglGetCurrentContext() function. If it is not, - * it calls eglMakeCurrent() function to set the texture context as the current context. - * - * @return true if the texture context is made the current context or if it is already the current context, false otherwise. - */ -bool Egl::make_texture_current() const { - if (eglGetCurrentContext() != texture_context_) { - eglMakeCurrent(dpy_, EGL_NO_SURFACE, EGL_NO_SURFACE, texture_context_); - } - return true; + SPDLOG_TRACE("--Egl::swap_buffers()"); } /** @@ -237,54 +210,54 @@ void Egl::debug_callback(EGLenum error, EGLLabelKHR threadLabel, EGLLabelKHR objectLabel, const char *message) { - std::cerr << "**** EGL Error" << std::endl; - std::cerr << "\terror: " << error << std::endl; - std::cerr << "\tcommand: " << command << std::endl; + spdlog::error("**** EGL Error"); + spdlog::error("\terror: {}", error); + spdlog::error("\tcommand: {}", command); switch (error) { case EGL_BAD_ACCESS: - std::cerr << "\terror: EGL_BAD_ACCESS" << std::endl; + spdlog::error("\terror: EGL_BAD_ACCESS"); break; case EGL_BAD_ALLOC: - std::cerr << "\terror: EGL_BAD_ALLOC" << std::endl; + spdlog::error("\terror: EGL_BAD_ALLOC"); break; case EGL_BAD_ATTRIBUTE: - std::cerr << "\terror: EGL_BAD_ATTRIBUTE" << std::endl; + spdlog::error("\terror: EGL_BAD_ATTRIBUTE"); break; case EGL_BAD_CONFIG: - std::cerr << "\terror: EGL_BAD_CONFIG" << std::endl; + spdlog::error("\terror: EGL_BAD_CONFIG"); break; case EGL_BAD_CONTEXT: - std::cerr << "\terror: EGL_BAD_CONTEXT" << std::endl; + spdlog::error("\terror: EGL_BAD_CONTEXT"); break; case EGL_BAD_CURRENT_SURFACE: - std::cerr << "\terror: EGL_BAD_CURRENT_SURFACE" << std::endl; + spdlog::error("\terror: EGL_BAD_CURRENT_SURFACE"); break; case EGL_BAD_DISPLAY: - std::cerr << "\terror: EGL_BAD_DISPLAY" << std::endl; + spdlog::error("\terror: EGL_BAD_DISPLAY"); break; case EGL_BAD_MATCH: - std::cerr << "\terror: EGL_BAD_MATCH" << std::endl; + spdlog::error("\terror: EGL_BAD_MATCH"); break; case EGL_BAD_NATIVE_PIXMAP: - std::cerr << "\terror: EGL_BAD_NATIVE_PIXMAP" << std::endl; + spdlog::error("\terror: EGL_BAD_NATIVE_PIXMAP"); break; case EGL_BAD_NATIVE_WINDOW: - std::cerr << "\terror: EGL_BAD_NATIVE_WINDOW" << std::endl; + spdlog::error("\terror: EGL_BAD_NATIVE_WINDOW"); break; case EGL_BAD_PARAMETER: - std::cerr << "\terror: EGL_BAD_PARAMETER" << std::endl; + spdlog::error("\terror: EGL_BAD_PARAMETER"); break; case EGL_BAD_SURFACE: - std::cerr << "\terror: EGL_BAD_SURFACE" << std::endl; + spdlog::error("\terror: EGL_BAD_SURFACE"); break; default: - std::cerr << "\terror: " << error << std::endl; + spdlog::error("\terror: {}", error); break; } - std::cerr << "\tmessageType: " << messageType << std::endl; - std::cerr << "\tthreadLabel: " << threadLabel << std::endl; - std::cerr << "\tobjectLabel: " << objectLabel << std::endl; - std::cerr << "\tmessage: " << ((message == nullptr) ? "" : message) << std::endl; + spdlog::error("\tmessageType: {}", messageType); + spdlog::error("\tthreadLabel: {}", threadLabel); + spdlog::error("\tobjectLabel: {}", objectLabel); + spdlog::error("\tmessage: {}", ((message == nullptr) ? "" : message)); } /** @@ -318,3 +291,19 @@ void Egl::egl_khr_debug_init() { pfDebugMessageControl(debug_callback, sDebugAttribList); } } + +void Egl::set_swap_interval(int interval) { + make_current(); + + EGLBoolean ret = eglSwapInterval(dpy_, interval); + if (ret == EGL_FALSE) { + throw std::runtime_error("eglSwapInterval failed"); + } + clear_current(); +} + +void Egl::resize(int width, int height, int dx, int dy) { + width_ = width; + height_ = height; + wl_egl_window_resize(wl_egl_window_, width, height, dx, dy); +} diff --git a/src/window/egl.h b/src/window/egl.h index ddaa287..623db50 100644 --- a/src/window/egl.h +++ b/src/window/egl.h @@ -14,89 +14,63 @@ * limitations under the License. */ -#ifndef SRC_WINDOW_EGL_H_ -#define SRC_WINDOW_EGL_H_ +#pragma once -#include +#include +#include #include #include +class SurfaceEgl; + class Egl { public: - explicit Egl(struct wl_display *display); + explicit Egl(struct wl_display *display, struct wl_surface *wl_surface, int width, int height, + const int32_t *context_attribs, size_t context_attribs_size, + const int32_t *config_attribs, size_t config_attribs_size, int buffer_bpp); ~Egl(); - [[nodiscard]] bool clear_current() const; + void set_swap_interval(int interval); - [[nodiscard]] bool make_current() const; - [[nodiscard]] bool swap_buffers() const; + void make_current(); - [[nodiscard]] bool make_resource_current() const; + void clear_current(); - [[nodiscard]] bool make_texture_current() const; + void swap_buffers(); - [[nodiscard]] PFNEGLSETDAMAGEREGIONKHRPROC get_set_damage_region() const { - return pfSetDamageRegion_; - } + void resize(int width, int height, int dx, int dy); - [[nodiscard]] PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC - get_swap_buffers_with_damage() const { - return pfSwapBufferWithDamage_; - } + // Disallow copy and assign. + Egl(const Egl &) = delete; - [[maybe_unused]] [[nodiscard]] bool has_ext_buffer_age() const { return has_egl_ext_buffer_age_; } + Egl &operator=(const Egl &) = delete; - [[maybe_unused]] EGLDisplay get_display() { return dpy_; } +private: + friend class SurfaceEgl; - [[maybe_unused]] EGLContext get_texture_context() { return texture_context_; } + std::vector context_attribs_; + std::vector config_attribs_; + int buffer_bpp_; - friend class WindowEgl; + EGLContext context_{}; -private: - static constexpr std::array kEglContextAttribs = { - { - EGL_CONTEXT_MAJOR_VERSION, 3, - EGL_CONTEXT_MAJOR_VERSION, 2, - EGL_NONE - } - }; - - static constexpr std::array kEglConfigAttribs = { - { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_STENCIL_SIZE, 8, - EGL_DEPTH_SIZE, 16, - EGL_SAMPLE_BUFFERS, 1, - EGL_SAMPLES, 4, - EGL_NONE // termination sentinel - } - }; + EGLint major_{}, minor_{}; - EGLSurface egl_surface_{}; + EGLDisplay dpy_; EGLConfig config_{}; - EGLContext texture_context_{}; - int buffer_size_ = 24; - - EGLDisplay dpy_{}; - EGLContext context_{}; - EGLContext resource_context_{}; + struct wl_surface *wl_surface_; + struct wl_egl_window *wl_egl_window_{}; + EGLSurface egl_surface_{}; - EGLint major_{}; - EGLint minor_{}; + int width_; + int height_; PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC pfSwapBufferWithDamage_{}; PFNEGLSETDAMAGEREGIONKHRPROC pfSetDamageRegion_{}; - bool has_egl_ext_buffer_age_{}; static bool has_egl_extension(const char *extensions, const char *name); @@ -109,5 +83,3 @@ class Egl { static void egl_khr_debug_init(); }; - -#endif // SRC_WINDOW_EGL_H_ \ No newline at end of file diff --git a/src/window/window.cc b/src/window/window.cc index 8dd1ff9..5b3c718 100644 --- a/src/window/window.cc +++ b/src/window/window.cc @@ -1,68 +1,235 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ #include "window.h" -#include +#include -/** - * @class Window - * @brief Class representing a window in a compositor-based system - * - * This class represents a window in a compositor-based system. It provides - * methods for creating and managing a surface, as well as starting and - * stopping frame updates. - */ -Window::Window(struct wl_compositor *compositor, ShellType shell_type, - const std::function &draw_callback) : - shell_type_(shell_type), - draw_callback_(draw_callback) { - wl_surface_ = wl_compositor_create_surface(compositor); - start_frames(); +#include "logging.h" + +Window::Window(WindowManager *wm, struct wl_compositor *wl_compositor, + const std::optional &wl_viewporter, + const std::optional &fractional_scale_manager, + const std::optional &tearing_control_manager, + const std::map> &outputs, + const char *name, const std::function &draw_frame_callback, + int width, int height, wl_output_transform buffer_transform, bool fullscreen, + bool maximized, bool fullscreen_ratio, bool tearing, + const int32_t *context_attribs, size_t context_attribs_size, + const int32_t *config_attribs, size_t config_attribs_size, + int buffer_bpp, int swap_interval) : + wm_(wm), buffer_transform_(buffer_transform), draw_frame_callback_(draw_frame_callback), + name_(name), fullscreen_(fullscreen), maximized_(maximized), fullscreen_ratio_(fullscreen_ratio), + outputs_(outputs), logical_size_{.width=width, .height=height}, + buffer_bpp_(buffer_bpp), swap_interval_(swap_interval), buffer_size_{.width=width, .height=height}, + window_size_{.width=buffer_size_.width, .height=buffer_size_.height}, needs_buffer_geometry_update_(false) { + + wl_surface_ = wl_compositor_create_surface(wl_compositor); + wl_surface_add_listener(wl_surface_, &surface_listener_, this); + + if (context_attribs_size && config_attribs_size) { + + egl_ = std::make_unique(wm->get_display(), wl_surface_, width, height, context_attribs, + context_attribs_size, config_attribs, config_attribs_size, buffer_bpp); + egl_->set_swap_interval(swap_interval); + } + + if (wl_viewporter.has_value()) { +#if defined(WAYLAND_PROTOCOL_HAS_VIEWPORTER) + viewport_ = wp_viewporter_get_viewport(wl_viewporter.value(), wl_surface_); +#endif + } + + if (fractional_scale_manager.has_value()) { +#if defined(WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE) + fractional_scale_ = wp_fractional_scale_manager_v1_get_fractional_scale(fractional_scale_manager.value(), + wl_surface_); + wp_fractional_scale_v1_add_listener(fractional_scale_, &fractional_scale_listener_, this); +#endif + } + + if (tearing_control_manager.has_value()) { +#if defined(WAYLAND_PROTOCOL_HAS_TEARING_CONTROL) + tearing_control_ = wp_tearing_control_manager_v1_get_tearing_control( + tearing_control_manager.value(), wl_surface_); + if (tearing) { + SPDLOG_DEBUG("[Surface] Set Presentation Hint: ASYNC"); + wp_tearing_control_v1_set_presentation_hint(tearing_control_, + WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC); + } else { + SPDLOG_DEBUG("[Surface] Set Presentation Hint: VSYNC"); + wp_tearing_control_v1_set_presentation_hint(tearing_control_, + WP_TEARING_CONTROL_V1_PRESENTATION_HINT_VSYNC); + } +#else + (void)tearing; +#endif + } } -/** - * @brief Destructor for the Window class. - * - * This destructor stops any active frames by calling the `stop_frames()` function. - * It is responsible for destroying the `wl_callback` if it exists. - * - * @see stop_frames() - */ Window::~Window() { - stop_frames(); + if (viewport_) { +#if defined(WAYLAND_PROTOCOL_HAS_VIEWPORTER) + wp_viewport_destroy(viewport_); +#endif + } + if (fractional_scale_) { +#if defined(WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE) + wp_fractional_scale_v1_destroy(fractional_scale_); +#endif + } + if (wl_callback_) { + wl_callback_destroy(wl_callback_); + } + if (wl_surface_) { + wl_surface_destroy(wl_surface_); + } +} + +void Window::update_buffer_geometry() { + if (!needs_buffer_geometry_update_) { + return; + } + + enum wl_output_transform new_buffer_transform; + struct { + int width; + int height; + } new_buffer_size{}; + struct { + int width; + int height; + } new_viewport_dest_size{}; + + new_buffer_transform = wm_->get_output_buffer_transform(wl_output_); + if (buffer_transform_ != new_buffer_transform) { + buffer_transform_ = new_buffer_transform; + wl_surface_set_buffer_transform(wl_surface_, buffer_transform_); + } + + switch (buffer_transform_) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + new_buffer_size.width = logical_size_.width; + new_buffer_size.height = logical_size_.height; + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + new_buffer_size.width = logical_size_.height; + new_buffer_size.height = logical_size_.width; + break; + } + + buffer_scale_ = wm_->get_output_buffer_scale(wl_output_); + + if (fractional_buffer_scale_ > 0.0) { + if (buffer_scale_ > 1) { + buffer_scale_ = 1; + wl_surface_set_buffer_scale(wl_surface_, buffer_scale_); + } + + new_buffer_size.width = static_cast(ceil(new_buffer_size.width * fractional_buffer_scale_)); + new_buffer_size.height = static_cast(ceil(new_buffer_size.height * fractional_buffer_scale_)); + } else { + int32_t new_buffer_scale; + + new_buffer_scale = wm_->get_output_buffer_scale(wl_output_); + if (buffer_scale_ != new_buffer_scale) { + buffer_scale_ = new_buffer_scale; + wl_surface_set_buffer_scale(wl_surface_, buffer_scale_); + } + + new_buffer_size.width *= buffer_scale_; + new_buffer_size.height *= buffer_scale_; + } + + if (fullscreen_ && fullscreen_ratio_) { + int new_buffer_size_min; + int new_viewport_dest_size_min; + + new_buffer_size_min = std::min(new_buffer_size.width, new_buffer_size.height); + new_buffer_size.width = new_buffer_size_min; + new_buffer_size.height = new_buffer_size_min; + + new_viewport_dest_size_min = std::min(logical_size_.width, logical_size_.height); + new_viewport_dest_size.width = new_viewport_dest_size_min; + new_viewport_dest_size.height = new_viewport_dest_size_min; + } else { + new_viewport_dest_size.width = logical_size_.width; + new_viewport_dest_size.height = logical_size_.height; + } + + if (buffer_size_.width != new_buffer_size.width || buffer_size_.height != new_buffer_size.height) { + buffer_size_.width = new_buffer_size.width; + buffer_size_.height = new_buffer_size.height; + if (egl_) { + egl_->resize(buffer_size_.width, buffer_size_.height, 0, 0); + } + } + + if (fractional_buffer_scale_ > 0.0) + wp_viewport_set_destination(viewport_, + new_viewport_dest_size.width, + new_viewport_dest_size.height); + + needs_buffer_geometry_update_ = false; +} + +void Window::handle_preferred_scale(void *data, + struct wp_fractional_scale_v1 *wp_fractional_scale_v1, + uint32_t scale) { + auto *w = static_cast(data); + if (w->fractional_scale_ != wp_fractional_scale_v1) { + return; + } + w->fractional_buffer_scale_ = static_cast(scale) / 120; + w->needs_buffer_geometry_update_ = true; +} + +void Window::handle_surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) { + auto obj = static_cast(data); + if (obj->wl_surface_ != wl_surface) { + return; + } + SPDLOG_DEBUG("handle_surface_enter: {} [{}]", fmt::ptr(wl_output), obj->name_); + obj->wl_output_ = wl_output; +} + +void Window::handle_surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *output) { + auto obj = static_cast(data); + if (obj->wl_surface_ != wl_surface) { + return; + } +#if !defined(NDEBUG) + SPDLOG_DEBUG("handle_surface_leave: {} [{}]", fmt::ptr(output), obj->name_); +#else + (void)output; +#endif + obj->wl_output_ = nullptr; } /** - * @brief Start rendering frames for the window. + * @brief Start rendering frames for the surface. * * This function stops the current frames (if any) and starts rendering new frames by calling the `on_frame` function. * - * @note This function assumes that the window has been initialized properly. + * @note This function assumes that the surface has been initialized properly. */ void Window::start_frames() { - stop_frames(); - on_frame(this, nullptr, 0); + handle_frame_callback(this, nullptr, 0); } /** * Stops the frame rendering by destroying the wl_callback object if it exists. * This function is intended to be called from outside the Window class. */ -void Window::stop_frames() const { +void Window::stop_frames() { if (wl_callback_) { wl_callback_destroy(wl_callback_); } @@ -72,15 +239,16 @@ void Window::stop_frames() const { * @brief Callback function for frame completion event * * This function is called when a frame completion event is received. - * It updates the state of the Window object and invokes the draw_callback_ function. + * It updates the state of the Window object and invokes the draw_frame_callback_ function. * - * @param data Pointer to the Window object - * @param callback Pointer to the wl_callback object (unused) + * @param data Pointer to the Surface object + * @param callback Pointer to the wl_callback object * @param time Timestamp of the frame completion event */ -void Window::on_frame(void *data, - struct wl_callback *callback, - const uint32_t time) { +void Window::handle_frame_callback(void *data, + struct wl_callback *callback, + const uint32_t time) { + SPDLOG_TRACE("++Window::handle_frame_callback()"); const auto obj = static_cast(data); obj->wl_callback_ = nullptr; @@ -89,16 +257,62 @@ void Window::on_frame(void *data, wl_callback_destroy(callback); } - if (obj->draw_callback_) { - obj->draw_callback_(data, time); + if (obj->draw_frame_callback_) { + obj->draw_frame_callback_(data, time); } - obj->wl_callback_ = wl_surface_frame(obj->wl_surface_); - wl_callback_add_listener(obj->wl_callback_, &Window::frame_listener_, data); + if (obj->wl_surface_) { + obj->wl_callback_ = wl_surface_frame(obj->wl_surface_); + wl_callback_add_listener(obj->wl_callback_, &Window::frame_callback_listener_, data); + + wl_surface_commit(obj->wl_surface_); + } + SPDLOG_TRACE("--Window::handle_frame_callback()"); +} + +void Window::handle_preferred_buffer_scale(void *data, + struct wl_surface *wl_surface, + int32_t factor) { + auto w = static_cast(data); + if (w->wl_surface_ != wl_surface) { + return; + } + w->preferred_buffer_scale_ = factor; + w->needs_buffer_geometry_update_ = true; +} - wl_surface_commit(obj->wl_surface_); +void Window::handle_preferred_buffer_transform(void *data, + struct wl_surface *wl_surface, + uint32_t transform) { + auto w = static_cast(data); + if (w->wl_surface_ != wl_surface) { + return; + } + w->preferred_buffer_transform_ = static_cast(transform); } -const struct wl_callback_listener Window::frame_listener_ = { - .done = on_frame -}; +void Window::resize(int width, int height) { + if (egl_) { + logical_size_.width = width; + logical_size_.height = height; + egl_->resize(width, height, 0, 0); + } +} + +void Window::make_current() { + if (egl_) { + egl_->make_current(); + } +} + +void Window::clear_current() { + if (egl_) { + egl_->clear_current(); + } +} + +void Window::swap_buffers() { + if (egl_) { + egl_->swap_buffers(); + } +} diff --git a/src/window/window.h b/src/window/window.h index b388f4f..8545899 100644 --- a/src/window/window.h +++ b/src/window/window.h @@ -14,50 +14,190 @@ * limitations under the License. */ -#ifndef SRC_WINDOW_WINDOW_H_ -#define SRC_WINDOW_WINDOW_H_ +#pragma once -#include +#include +#include +#include +#include #include -class Display; +#include "window_manager/xdg_window_manager.h" +#include "egl.h" + +class Egl; + +class Output; + +class WindowManager; + +class XdgTopLevel; class Window { public: - typedef enum { - AGL, - IVI, - XDG, - NONE, - } ShellType; - explicit Window(struct wl_compositor *compositor, ShellType shell_type = XDG, - const std::function &draw_callback = nullptr); + enum WindowState { + WINDOW_STATE_NONE = 0, + WINDOW_STATE_ACTIVE = 1 << 0, + WINDOW_STATE_MAXIMIZED = 1 << 1, + WINDOW_STATE_FULLSCREEN = 1 << 2, + WINDOW_STATE_TILED_LEFT = 1 << 3, + WINDOW_STATE_TILED_RIGHT = 1 << 4, + WINDOW_STATE_TILED_TOP = 1 << 5, + WINDOW_STATE_TILED_BOTTOM = 1 << 6, + WINDOW_STATE_SUSPENDED = 1 << 7, + WINDOW_STATE_RESIZING = 1 << 8, + }; + + Window(WindowManager *wm, struct wl_compositor *wl_compositor, + const std::optional &viewporter, + const std::optional &fractional_scale_manager, + const std::optional &tearing_control_manager, + const std::map> &outputs, + const char *name, const std::function &draw_frame_callback, + int width, int height, wl_output_transform buffer_transform, bool fullscreen, + bool maximized, bool fullscreen_ratio, bool tearing, + const int32_t *context_attribs, size_t context_attribs_size, + const int32_t *config_attribs, size_t config_attribs_size, + int buffer_bpp, int swap_interval); + + ~Window(); + + void resize(int width, int height); + + void update_buffer_geometry(); + + void set_fullscreen(bool fullscreen) { fullscreen_ = fullscreen; } + + [[nodiscard]] bool get_fullscreen() const { return fullscreen_; } + + void set_maximized(bool maximized) { maximized_ = maximized; } + + [[nodiscard]] bool get_maximized() const { return maximized_; } + + void set_fullscreen_ratio(bool fullscreen_ratio) { fullscreen_ratio_ = fullscreen_ratio; } + + [[nodiscard]] bool get_fullscreen_ratio() const { return maximized_; } + + [[nodiscard]] enum wl_output_transform get_buffer_transform() const { return buffer_transform_; } + + [[nodiscard]] int32_t get_buffer_scale() const { return buffer_scale_; } + + [[nodiscard]] double get_fractional_buffer_scale() const { return fractional_buffer_scale_; } + + [[nodiscard]] struct wl_surface* get_surface() const { return wl_surface_; } + + [[nodiscard]] int get_width() const { return buffer_size_.width; } + + [[nodiscard]] int get_height() const { return buffer_size_.height; } + + void start_frames(); + + void stop_frames(); - virtual ~Window() = 0; + void make_current(); - friend class WindowEgl; + void clear_current(); - friend class WindowVulkan; + void swap_buffers(); - friend class WindowManager; + void set_needs_buffer_geometry_update() { needs_buffer_geometry_update_ = true; } private: - struct wl_surface *wl_surface_{}; + friend XdgTopLevel; + + WindowManager *wm_; + const std::map> &outputs_; + struct wp_tearing_control_v1 *tearing_control_; + wl_output_transform buffer_transform_; + struct wl_output *wl_output_{}; + + std::string name_; + struct wl_surface *wl_surface_; struct wl_callback *wl_callback_{}; + std::function draw_frame_callback_; - ShellType shell_type_; + std::unique_ptr egl_; - std::function draw_callback_; + bool fullscreen_; + bool maximized_; + bool fullscreen_ratio_; - void start_frames(); + int buffer_bpp_ = 0; + int swap_interval_ = 1; + int delay_ = 0; - void stop_frames() const; + int32_t buffer_scale_ = 1; + int32_t preferred_buffer_scale_ = 1; + enum wl_output_transform preferred_buffer_transform_ = WL_OUTPUT_TRANSFORM_NORMAL; + double fractional_buffer_scale_ = 1.0; - static void on_frame(void *data, struct wl_callback *callback, uint32_t time); + struct { + int width; + int height; + } buffer_size_; - static const struct wl_callback_listener frame_listener_; -}; + struct { + int width; + int height; + } window_size_; + + + + struct { + int width; + int height; + } logical_size_; + + bool needs_buffer_geometry_update_; + + static void handle_surface_enter(void *data, + struct wl_surface *surface, + struct wl_output *output); -#endif // SRC_WINDOW_WINDOW_H_ \ No newline at end of file + static void handle_surface_leave(void *data, + struct wl_surface *surface, + struct wl_output *output); + + static void handle_preferred_buffer_scale(void *data, + struct wl_surface *wl_surface, + int32_t factor); + + static void handle_preferred_buffer_transform(void *data, + struct wl_surface *wl_surface, + uint32_t transform); + + static constexpr struct wl_surface_listener surface_listener_ = { + .enter = handle_surface_enter, + .leave = handle_surface_leave +#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) + , + .preferred_buffer_scale = handle_preferred_buffer_scale +#endif +#if defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION) + , + .preferred_buffer_transform = handle_preferred_buffer_transform, +#endif + }; + + struct wp_viewport *viewport_; + + struct wp_fractional_scale_v1 *fractional_scale_; + + static void handle_preferred_scale(void *data, + struct wp_fractional_scale_v1 *wp_fractional_scale_v1, + uint32_t scale); + +#if defined(WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE) + static constexpr struct wp_fractional_scale_v1_listener fractional_scale_listener_ = { + .preferred_scale = handle_preferred_scale, + }; +#endif + + static void handle_frame_callback(void *data, struct wl_callback *callback, uint32_t time); + + static constexpr struct wl_callback_listener frame_callback_listener_ = { + .done = handle_frame_callback + }; +}; diff --git a/src/window/window_egl.cc b/src/window/window_egl.cc deleted file mode 100644 index f1eb81f..0000000 --- a/src/window/window_egl.cc +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "window_egl.h" - -#include - -#include - -/** - * @class WindowEgl - * @brief The WindowEgl class represents a window using EGL for rendering. - * - * This class is responsible for managing an EGL window surface for rendering on a Wayland compositor. It creates an EGL window surface and initializes the EGL context. It also provides -* a callback for rendering the window contents. - */ -WindowEgl::WindowEgl(struct wl_display *display, struct wl_compositor * /* compositor */, struct wl_surface *surface, - int width, int height, - Window::ShellType /* shell_type */, - const std::function & /* draw_callback */) : - Egl(display) { - - std::cout << "width: " << width << std::endl; - std::cout << "height: " << height << std::endl; - - if (egl_window_) { - wl_egl_window_destroy(egl_window_); - } - egl_window_ = wl_egl_window_create(surface, width, height); - - if (egl_surface_) { - eglDestroySurface(dpy_, egl_surface_); - } - - auto create_platform_window = - reinterpret_cast( - eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT")); - - if (create_platform_window) { - egl_surface_ = create_platform_window(dpy_, config_, egl_window_, nullptr); - } else { - egl_surface_ = eglCreateWindowSurface( - dpy_, config_, reinterpret_cast(egl_window_), nullptr); - } -} - -/** - * @class WindowEgl - * @brief Represents an EGL window used by the application. - * - * This class manages the lifecycle of an EGL window created using the Wayland display protocol. - * It provides functionality to destroy the EGL surface and associated resources. - */ -WindowEgl::~WindowEgl() { - eglDestroySurface(dpy_, egl_surface_); - - if (egl_window_) { - wl_egl_window_destroy(egl_window_); - } -} diff --git a/src/window/window_vulkan.cc b/src/window/window_vulkan.cc deleted file mode 100644 index 63fa465..0000000 --- a/src/window/window_vulkan.cc +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "window_vulkan.h" - -/** - * @class WindowVulkan - * @brief Class representing a Vulkan window. - * - * This class is a specialized subclass of Window that is specifically designed for Vulkan rendering. - * It provides functionality to create and manage a Vulkan surface. - */ -WindowVulkan::WindowVulkan(struct wl_display * /* display */, struct wl_compositor *compositor, int /* width */, - int /* height */, - Window::ShellType shell_type, const std::function &draw_callback) : - Window(compositor, shell_type, draw_callback) { -} - -/** - * @class WindowVulkan - * @brief Class representing a Vulkan window - * - * The WindowVulkan class provides functionality to create and manage a Vulkan window. - * It is primarily used for rendering graphics using the Vulkan API. - */ -WindowVulkan::~WindowVulkan() = default; \ No newline at end of file diff --git a/src/window_manager/agl_shell.cc b/src/window_manager/agl_shell.cc new file mode 100644 index 0000000..f431d36 --- /dev/null +++ b/src/window_manager/agl_shell.cc @@ -0,0 +1,223 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "agl_shell.h" + +#include "logging.h" + + +/** + * @class AglShell + * + * @brief AglShell represents a Shell for a Wayland-based display. + * + * The AglShell class is responsible for managing application windows using the XDG Shell protocol. + */ +AglShell::AglShell(const char * /* title */, const char * /* app_id */, bool /* fullscreen */, bool /* maximized */, + GMainContext *context, bool enable_cursor, const char *name) : XdgWindowManager(context, + enable_cursor, + name), + wait_for_bound_(true), + bound_ok_(false) { + auto agl_shell = get_agl_shell(); + if (!agl_shell.has_value()) { + spdlog::critical("{} is required.", agl_shell_interface.name); + exit(EXIT_FAILURE); + } + agl_shell_ = agl_shell.value(); + + agl_shell_add_listener(agl_shell_, &agl_shell_listener_, + this); +} + +AglShell::~AglShell() = default; + +void AglShell::handle_bound_ok(void *data, + struct agl_shell *agl_shell) { + auto *obj = static_cast(data); + if (obj->agl_shell_ != agl_shell) { + return; + } + obj->wait_for_bound_ = false; + obj->bound_ok_ = true; +} + +struct wl_output *AglShell::find_output_by_name(const std::string &output_name) { + for (auto &it: output_.outputs) { + if (it.second->get_name() == output_name) { + return it.first; + } + } + return nullptr; +} + +void AglShell::activate_app(const std::string &app_id) { + + SPDLOG_DEBUG("got app_id {}", app_id); + + // search for a pending application which might have a different output + auto iter = pending_app_list_.begin(); + bool found_pending_app = false; + while (iter != pending_app_list_.end()) { + auto app_to_search = iter->first; + SPDLOG_DEBUG("searching for {}", app_to_search); + + if (app_to_search == app_id) { + found_pending_app = true; + break; + } + + iter++; + } + + std::string output_name; + struct wl_output *wl_output{}; + if (found_pending_app) { + output_name = iter->second; + wl_output = find_output_by_name(output_name); + + SPDLOG_DEBUG("Found app_id {} at all", app_id); + + if (!wl_output) { + // try with remoting-remote-X which is the streaming + wl_output = find_output_by_name("remoting-" + output_name); + if (!wl_output) { + SPDLOG_DEBUG("Not activating app_id {} at all", app_id); + return; + } + } + + pending_app_list_.erase(iter); + } + + SPDLOG_DEBUG("Activating app_id {} on output {}", app_id, output_name); + agl_shell_activate_app(agl_shell_, app_id.c_str(), wl_output); + wl_display_flush(get_display()); +} + +void AglShell::deactivate_app(const std::string &app_id) { + for (auto &i: apps_stack_) { + if (i == app_id) { + // remove it from apps_stack + apps_stack_.remove(i); + if (!apps_stack_.empty()) + activate_app(apps_stack_.back()); + break; + } + } +} + +void AglShell::add_app_to_stack(const std::string &app_id) { + bool found_app = false; + for (auto &i: apps_stack_) { + if (i == app_id) { + found_app = true; + break; + } + } + + if (!found_app) { + apps_stack_.push_back(app_id); + } +} + +void AglShell::process_app_status_event(const char *app_id, const std::string &event_type) { + + if (event_type == "started") { + activate_app(std::string(app_id)); + } else if (event_type == "terminated") { + deactivate_app(std::string(app_id)); + } else if (event_type == "deactivated") { + // not handled + } +} + +void AglShell::handle_bound_fail(void *data, + struct agl_shell *agl_shell) { + auto *obj = static_cast(data); + if (obj->agl_shell_ != agl_shell) { + return; + } + obj->wait_for_bound_ = false; + obj->bound_ok_ = false; +} + +void AglShell::handle_app_state(void *data, + struct agl_shell *agl_shell, + const char *app_id, + uint32_t state) { + auto *obj = static_cast(data); + if (obj->agl_shell_ != agl_shell) { + return; + } + switch (state) { + case AGL_SHELL_APP_STATE_STARTED: + SPDLOG_DEBUG("[AGL] AGL_SHELL_APP_STATE_STARTED for app_id {}", app_id); + obj->process_app_status_event(app_id, std::string("started")); + break; + case AGL_SHELL_APP_STATE_TERMINATED: + SPDLOG_DEBUG("[AGL] AGL_SHELL_APP_STATE_TERMINATED for app_id {}", app_id); + break; + case AGL_SHELL_APP_STATE_ACTIVATED: + SPDLOG_DEBUG("[AGL] AGL_SHELL_APP_STATE_ACTIVATED for app_id {}", app_id); + obj->add_app_to_stack(std::string(app_id)); + break; + case AGL_SHELL_APP_STATE_DEACTIVATED: + obj->process_app_status_event(app_id, std::string("deactivated")); + break; + default: + break; + } +} + +void AglShell::handle_app_on_output(void *data, + struct agl_shell *agl_shell, + const char *app_id, + const char *output_name) { + auto *obj = static_cast(data); + if (obj->agl_shell_ != agl_shell) { + return; + } + + SPDLOG_DEBUG("[AGL] app_on_out app_id {} output name {}", app_id, output_name); + + // a couple of use-cases, if there is no app_id in the app_list then it + // means this is a request to map the application, from the start to a + // different output that the default one. We'd get an + // AGL_SHELL_APP_STATE_STARTED which will handle activation. + // + // if there's an app_id then it means we might have gotten an event to + // move the application to another output; so we'd need to process it + // by explicitly calling processAppStatusEvent() which would ultimately + // activate the application on other output. We'd have to pick-up the + // last activated surface and activate the default output. + // + // finally if the outputs are identical probably that's an user-error - + // but the compositor won't activate it again, so we don't handle that. + std::pair new_pending_app = + std::pair(std::string(app_id), std::string(output_name)); + obj->pending_app_list_.emplace_back(new_pending_app); + + auto iter = obj->apps_stack_.begin(); + while (iter != obj->apps_stack_.end()) { + if (*iter == std::string(app_id)) { + SPDLOG_DEBUG("[AGL] move {} to another output {}", app_id, output_name); + obj->process_app_status_event(app_id, std::string("started")); + break; + } + iter++; + } +} diff --git a/src/window_manager/agl_shell.h b/src/window_manager/agl_shell.h new file mode 100644 index 0000000..1b05534 --- /dev/null +++ b/src/window_manager/agl_shell.h @@ -0,0 +1,140 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "registrar.h" +#include "seat/cursor.h" +#include "xdg_window_manager.h" + +class XdgWindowManager; + +class AglShell : public XdgWindowManager { +public: + struct Geometry { + int width; + int height; + }; + + explicit AglShell(const char *title, + const char *app_id, + bool fullscreen, + bool maximized, + GMainContext *context = nullptr, + bool enable_cursor = true, + const char *name = nullptr); + + ~AglShell(); + + int dispatch_pending() { return wl_display_dispatch_pending(get_display()); } + + void activate_app(const std::string &app_id); + + void deactivate_app(const std::string &app_id); + + struct wl_output *find_output_by_name(const std::string &output_name); + + void process_app_status_event(const char *app_id, const std::string &event_type); + + // Disallow copy and assign. + AglShell(const AglShell &) = delete; + + AglShell &operator=(const AglShell &) = delete; + +private: + struct agl_shell *agl_shell_; + bool wait_for_bound_; + bool bound_ok_; + + std::list apps_stack_; + std::list> pending_app_list_; + + volatile bool wait_for_configure_; + + struct { + int32_t width; + int32_t height; + } geometry_{}; + + struct { + int32_t width; + int32_t height; + } window_size_{}; + + /** + * event sent if binding was ok + * + * Informs the client that it was able to bind the agl_shell + * interface succesfully. Clients are required to wait for this + * event before continuing further. + * @since 2 + */ + static void handle_bound_ok(void *data, + struct agl_shell *agl_shell); + + /** + * event sent if binding was nok + * + * Informs the client that binding to the agl_shell interface was + * unsuccesfull. Clients are required to wait for this event for + * continuing further. + * @since 2 + */ + static void handle_bound_fail(void *data, + struct agl_shell *agl_shell); + + /** + * event sent when an application suffered state modification + * + * Informs the client that an application has changed its state + * to another, specified by the app_state enum. Client can use this + * event to track current application state. For instance to know + * when the application has started, or when terminated/stopped. + * @since 3 + */ + static void handle_app_state(void *data, + struct agl_shell *agl_shell, + const char *app_id, + uint32_t state); + + /** + * Event sent as a reponse to set_app_output + * + * Clients can use this event to be notified when an application + * wants to be displayed on a certain output. This event is sent in + * response to the set_app_output request. + * + * See xdg_toplevel.set_app_id from the xdg-shell protocol for a + * description of app_id. + * @since 8 + */ + static void handle_app_on_output(void *data, + struct agl_shell *agl_shell, + const char *app_id, + const char *output_name); + + + void add_app_to_stack(const std::string &app_id); + + static constexpr struct agl_shell_listener agl_shell_listener_ = { + .bound_ok = handle_bound_ok, + .bound_fail = handle_bound_fail, + .app_state = handle_app_state, + .app_on_output = handle_app_on_output, + }; +}; diff --git a/src/window_manager/buffer.cc b/src/window_manager/buffer.cc new file mode 100644 index 0000000..2951fa9 --- /dev/null +++ b/src/window_manager/buffer.cc @@ -0,0 +1,17 @@ + +#include "buffer.h" + +Buffer::Buffer(wl_shm_pool *wl_shm_pool, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) { + wl_buffer_ = wl_shm_pool_create_buffer(wl_shm_pool, offset, width, height, stride, format); + wl_buffer_add_listener(wl_buffer_, &buffer_liestener_, this); +} + +Buffer::~Buffer() { + if (wl_buffer_) { + wl_buffer_destroy(wl_buffer_) + } +} + +void Buffer::handle_release(void *data, struct wl_buffer *wl_buffer) { + +} \ No newline at end of file diff --git a/src/window_manager/buffer.h b/src/window_manager/buffer.h new file mode 100644 index 0000000..eadba31 --- /dev/null +++ b/src/window_manager/buffer.h @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class XdgWindowManager; + +class Buffer { +public: + explicit Buffer(wl_shm_pool *wl_shm_pool, int32_t offset, int32_t width, int32_t height, int32_t stride, + uint32_t format); + + ~Buffer(); + +private: + struct wl_buffer *wl_buffer_; + + /** + * compositor releases buffer + * + * Sent when this wl_buffer is no longer used by the compositor. + * The client is now free to reuse or destroy this buffer and its + * backing storage. + * + * If a client receives a release event before the frame callback + * requested in the same wl_surface.commit that attaches this + * wl_buffer to a surface, then the client is immediately free to + * reuse the buffer and its backing storage, and does not need a + * second buffer for the next surface content update. Typically + * this is possible, when the compositor maintains a copy of the + * wl_surface contents, e.g. as a GL texture. This is an important + * optimization for GL(ES) compositors with wl_shm clients. + */ + static void handle_release(void *data, struct wl_buffer *wl_buffer); + + static constexpr wl_buffer_listener buffer_liestener_ = { + .release = handle_release, + }; +}; diff --git a/src/window_manager/display.cc b/src/window_manager/display.cc deleted file mode 100644 index 1496bb3..0000000 --- a/src/window_manager/display.cc +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "display.h" -#include "output.h" - -#include -#include -#include - - -/** - * @class Display - * Represents a Wayland display connection. - */ -Display::Display(GMainContext *context, bool enable_cursor, const char *name) : - wl_display_(wl_display_connect(name)), - context_(context), - enable_cursor_(enable_cursor) { - if (wl_display_ == nullptr) { - std::cerr << "Failed to connect to Wayland display. " << strerror(errno) << std::endl; - exit(EXIT_FAILURE); - } - wl_registry_ = wl_display_get_registry(wl_display_); - wl_registry_add_listener(wl_registry_, &listener_, this); - wl_display_roundtrip(wl_display_); -} - -/** - * @brief Destructor for the Display class. - * - * This destructor destroys the wl_registry_, wl_shm_, wl_subcompositor_, and wl_compositor_ objects if they exist. - * - * @note The Display::Display() function sets the pointer values of wl_registry_, wl_shm_, wl_subcompositor_, and wl_compositor_ to valid instances of their respective structures. This -* destructor handles the cleanup of these instances. - * - * @see Display(), wl_registry_destroy(), wl_shm_destroy(), wl_subcompositor_destroy(), wl_compositor_destroy() - */ -Display::~Display() { - if (wl_registry_) { - wl_registry_destroy(wl_registry_); - } - - if (wl_shm_) { - wl_shm_destroy(wl_shm_); - } - - if (wl_subcompositor_) { - wl_subcompositor_destroy(wl_subcompositor_); - } - - if (wl_compositor_) { - wl_compositor_destroy(wl_compositor_); - } -} - -/** - * @class Display - * @brief The Display class represents a Wayland display. - * - * This class provides functionality for interacting with a Wayland display, - * such as managing various objects and handling events. - */ -void Display::shm_format(void *data, - struct wl_shm * /* wl_shm */, - uint32_t format) { - const auto obj = static_cast(data); - if (format == WL_SHM_FORMAT_XRGB8888) { - obj->has_xrgb_ = true; - } -} - -const struct wl_shm_listener Display::shm_listener_ = { - .format = shm_format -}; - -/** - * @brief Handles the global objects registered with the Wayland display. - * - * This method is called when a global object is added to the display registry. It checks - * the interface of the object and binds it to the appropriate Wayland client-side object. - * - * @param data A pointer to the Display object. - * @param registry The Wayland registry object. - * @param name The name of the global object. - * @param interface The interface name of the global object. - * @param version The version of the global object. - */ -void Display::registry_handle_global(void *data, - struct wl_registry *registry, - uint32_t name, - const char *interface, - uint32_t version) { - const auto obj = static_cast(data); - - if (strcmp(interface, wl_compositor_interface.name) == 0) { - obj->compositor_version_ = version; - obj->wl_compositor_ = static_cast( - wl_registry_bind(registry, name, &wl_compositor_interface, - std::min(static_cast(1), version))); - obj->buffer_scaling_enabled_ = (obj->compositor_version_ >= 3); - - } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { - obj->subcompositor_version_ = version; - obj->wl_subcompositor_ = static_cast( - wl_registry_bind(registry, name, &wl_subcompositor_interface, - std::min(static_cast(1), version))); - - } else if (strcmp(interface, wl_shm_interface.name) == 0) { - obj->wl_shm_ = static_cast( - wl_registry_bind(registry, name, &wl_shm_interface, - std::min(static_cast(1), version))); - wl_shm_add_listener(obj->wl_shm_, &shm_listener_, obj); - - } else if (strcmp(interface, wl_output_interface.name) == 0) { - auto output = static_cast( - wl_registry_bind(registry, name, &wl_output_interface, - std::min(static_cast(2), version))); - obj->wl_outputs_[output] = std::make_unique(output, version); - - } else if (strcmp(interface, wl_seat_interface.name) == 0) { - auto seat = static_cast( - wl_registry_bind(registry, name, &wl_seat_interface, - std::min(static_cast(5), version))); - obj->wl_seats_[seat] = std::make_unique(seat, obj->wl_shm_, obj->wl_compositor_, obj->enable_cursor_, - version); - } - - for (const auto &callback: obj->callbacks_) { - callback.first(callback.second, registry, name, interface, version); - } -} - -/** - * @brief Handles the removal of global objects from the registry. - * - * This function is invoked when a global object is removed from the registry. - * - * @param data Pointer to user data (unused). - * @param reg Pointer to the registry object. - * @param id The ID of the removed global object. - * - * @return None. - */ -void Display::registry_handle_global_remove(void * /* data */, - struct wl_registry * /* reg */, - uint32_t /* id */) { -} - -const struct wl_registry_listener Display::listener_ = { - registry_handle_global, - registry_handle_global_remove, -}; - -/** - * @brief Adds a registrar callback with associated data. - * - * This function allows you to register a callback function that will be called - * when a new global object is added to the Wayland registry. The callback - * function will be provided with the data parameter, the registry, the name of - * the interface, and the version of the interface. - * - * @param callback The callback function to register. - * @param data The data to associate with the callback. - * - * @see wl_registry_add_listener - * @see struct wl_registry_listener - */ -void Display::add_registrar_callback(const std::function &callback, void *data) { - callbacks_.emplace_back(std::move(std::make_pair(callback, data))); -} diff --git a/src/window_manager/display.h b/src/window_manager/display.h deleted file mode 100644 index 3ea448c..0000000 --- a/src/window_manager/display.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_DISPLAY_H_ -#define SRC_DISPLAY_H_ - -#include -#include -#include -#include -#include - -#include -#include - -#include "output.h" -#include "seat/seat.h" - -class Output; - -class Seat; - -class Display { -public: - explicit Display(GMainContext *context = nullptr, bool enable_cursor = true, const char *name = nullptr); - - ~Display(); - - [[nodiscard]] struct wl_display *get_display() const { return wl_display_; } - - [[nodiscard]] const std::map> &get_seats() const { return wl_seats_; } - - [[nodiscard]] const std::map> & - get_outputs() const { return wl_outputs_; } - - struct wl_compositor *get_compositor() { return wl_compositor_; } - - void add_registrar_callback(const std::function &callback, void *data); - - friend class Cursor; - - friend class Window; - - friend class WindowEgl; - - friend class WindowVulkan; - - friend class WindowManager; - -private: - struct wl_compositor *wl_compositor_{}; - uint32_t compositor_version_{}; - struct wl_subcompositor *wl_subcompositor_{}; - uint32_t subcompositor_version_{}; - struct wl_display *wl_display_{}; - struct wl_registry *wl_registry_{}; - struct wl_shm *wl_shm_{}; - - GMainContext *context_; - bool enable_cursor_; - - std::map> wl_outputs_; - std::map> wl_seats_; - - bool has_xrgb_{}; - std::optional buffer_scaling_enabled_; - - std::vector, void * /* data */>> callbacks_; - - struct wl_compositor *get_compositor() const { return wl_compositor_; } - - static void registry_handle_global(void *data, - struct wl_registry *registry, - uint32_t name, - const char *interface, - uint32_t version); - - static void registry_handle_global_remove(void *data, - struct wl_registry *reg, - uint32_t id); - - static const struct wl_registry_listener listener_; - - static void shm_format(void * /* data */, - struct wl_shm * /* wl_shm */, - uint32_t /* format */); - - static const struct wl_shm_listener shm_listener_; - -}; - - -#endif //SRC_DISPLAY_H_ \ No newline at end of file diff --git a/src/window_manager/ivi_window_manager.cc b/src/window_manager/ivi_window_manager.cc new file mode 100644 index 0000000..af76d5e --- /dev/null +++ b/src/window_manager/ivi_window_manager.cc @@ -0,0 +1,300 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ivi_window_manager.h" + +#include "logging.h" + +IviWindowManager::IviWindowManager(const char *title, + const char *app_id, + bool /* fullscreen */, + bool /* maximized */, + GMainContext *context, + bool enable_cursor, + const char *name) : WindowManager(context, enable_cursor, + name), app_title_(title), + app_id_(app_id) { + auto ivi_wm = get_ivi_wm(); + if (!ivi_wm.has_value()) { + spdlog::critical("{} is not present.", ivi_wm_interface.name); + exit(EXIT_FAILURE); + } + ivi_wm_ = ivi_wm.value(); +} + +IviWindowManager::~IviWindowManager() = default; + +void IviWindowManager::ivi_wm_surface_visibility(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + int32_t visibility) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) surface_id; + (void) visibility; + SPDLOG_DEBUG("ivi_wm_surface_visibility: {}, visibility: {}", surface_id, + visibility); +} + +void IviWindowManager::ivi_wm_layer_visibility(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + int32_t visibility) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) layer_id; + (void) visibility; + SPDLOG_DEBUG("ivi_wm_layer_visibility: {}, visibility: {}", layer_id, + visibility); +} + +void IviWindowManager::ivi_wm_surface_opacity(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + wl_fixed_t opacity) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) surface_id; + (void) opacity; + SPDLOG_DEBUG("ivi_wm_surface_opacity: {}, opacity: {}", surface_id, opacity); +} + +void IviWindowManager::ivi_wm_layer_opacity(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + wl_fixed_t opacity) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) layer_id; + (void) opacity; + SPDLOG_DEBUG("ivi_wm_layer_opacity: {}, opacity: {}", layer_id, opacity); +} + +void IviWindowManager::ivi_wm_surface_source_rectangle(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) surface_id; + (void) x; + (void) y; + (void) width; + (void) height; + SPDLOG_DEBUG( + "ivi_wm_surface_source_rectangle: {}, x: {}, y: {}, width: {}, height: " + "{}", + surface_id, x, y, width, height); +} + +void IviWindowManager::ivi_wm_layer_source_rectangle(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) layer_id; + (void) x; + (void) y; + (void) width; + (void) height; + SPDLOG_DEBUG( + "ivi_wm_layer_source_rectangle: {}, x: {}, y: {}, width: {}, height: {}", + layer_id, x, y, width, height); +} + +void IviWindowManager::ivi_wm_surface_destination_rectangle(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) surface_id; + (void) x; + (void) y; + (void) width; + (void) height; + SPDLOG_DEBUG( + "ivi_wm_surface_destination_rectangle: {}, x: {}, y: {}, width: {}, " + "height: {}", + surface_id, x, y, width, height); +} + +void IviWindowManager::ivi_wm_layer_destination_rectangle(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) layer_id; + (void) x; + (void) y; + (void) width; + (void) height; + SPDLOG_DEBUG( + "ivi_wm_surface_destination_rectangle: {}, , x: {}, y: {}, width: {}, " + "height: {}", + layer_id, x, y, width, height); +} + +void IviWindowManager::ivi_wm_surface_created(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) surface_id; + SPDLOG_DEBUG("ivi_wm_surface_created: {}", surface_id); +} + +void IviWindowManager::ivi_wm_layer_created(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) layer_id; + SPDLOG_DEBUG("ivi_wm_layer_created: {}", layer_id); +} + +void IviWindowManager::ivi_wm_surface_destroyed(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) surface_id; + SPDLOG_DEBUG("ivi_wm_surface_destroyed: {}", surface_id); +} + +void IviWindowManager::ivi_wm_layer_destroyed(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) layer_id; + SPDLOG_DEBUG("ivi_wm_layer_destroyed: {}", layer_id); +} + +void IviWindowManager::ivi_wm_surface_error(void *data, + struct ivi_wm *ivi_wm, + uint32_t object_id, + uint32_t error, + const char *message) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) object_id; + (void) error; + (void) message; + SPDLOG_DEBUG("ivi_wm_surface_error: {}, error ({}), {}", object_id, error, + message); +} + +void IviWindowManager::ivi_wm_layer_error(void *data, + struct ivi_wm *ivi_wm, + uint32_t object_id, + uint32_t error, + const char *message) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) object_id; + (void) error; + (void) message; + SPDLOG_DEBUG("ivi_wm_layer_error: {}, error ({}), {}", object_id, error, + message); +} + +void IviWindowManager::ivi_wm_surface_size(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + int32_t width, + int32_t height) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) surface_id; + (void) width; + (void) height; + SPDLOG_DEBUG("ivi_wm_surface_size: {}, width {}, height {}", surface_id, + width, height); +} + +void IviWindowManager::ivi_wm_surface_stats(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + uint32_t frame_count, + uint32_t pid) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) surface_id; + (void) frame_count; + (void) pid; + SPDLOG_DEBUG("ivi_wm_surface_stats: {}, frame_count {}, pid {}", surface_id, + frame_count, pid); +} + +void IviWindowManager::ivi_wm_layer_surface_added(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + uint32_t surface_id) { + auto obj = static_cast(data); + if (obj->ivi_wm_ != ivi_wm) { + return; + } + (void) layer_id; + (void) surface_id; + SPDLOG_DEBUG("ivi_wm_layer_surface_added: {}, {}", layer_id, surface_id); +} diff --git a/src/window_manager/ivi_window_manager.h b/src/window_manager/ivi_window_manager.h new file mode 100644 index 0000000..c02426d --- /dev/null +++ b/src/window_manager/ivi_window_manager.h @@ -0,0 +1,178 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "wayland-protocols.h" + +#include "window_manager.h" + +class IviWindowManager : public WindowManager { +public: + IviWindowManager(const char *title, + const char *app_id, + bool fullscreen, + bool maximized, + GMainContext *context = nullptr, + bool enable_cursor = true, + const char *name = nullptr); + + ~IviWindowManager(); + + int dispatch_pending() { return wl_display_dispatch_pending(get_display()); } + + // Disallow copy and assign. + IviWindowManager(const IviWindowManager &) = delete; + + IviWindowManager &operator=(const IviWindowManager &) = delete; + +private: + struct ivi_wm *ivi_wm_; + std::string app_title_; + std::string app_id_; + + struct { + int32_t width; + int32_t height; + } geometry_{}; + + struct { + int32_t width; + int32_t height; + } window_size_{}; + + + static void ivi_wm_surface_visibility(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + int32_t visibility); + + static void ivi_wm_layer_visibility(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + int32_t visibility); + + static void ivi_wm_surface_opacity(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + wl_fixed_t opacity); + + static void ivi_wm_layer_opacity(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + wl_fixed_t opacity); + + static void ivi_wm_surface_source_rectangle(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + + static void ivi_wm_layer_source_rectangle(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + + static void ivi_wm_surface_destination_rectangle(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + + static void ivi_wm_layer_destination_rectangle(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + + static void ivi_wm_surface_created(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id); + + static void ivi_wm_layer_created(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id); + + static void ivi_wm_surface_destroyed(void *data, + struct ivi_wm *ivi_wm, + uint32_t surface_id); + + static void ivi_wm_layer_destroyed(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id); + + static void ivi_wm_surface_error(void *data, + struct ivi_wm *ivi_wm, + uint32_t object_id, + uint32_t error, + const char *message); + + static void ivi_wm_layer_error(void *data, + struct ivi_wm *ivi_wm, + uint32_t object_id, + uint32_t error, + const char *message); + + static void ivi_wm_surface_size(void * /* data */, + struct ivi_wm * /* ivi_wm */, + uint32_t surface_id, + int32_t width, + int32_t height); + + static void ivi_wm_surface_stats(void * /* data */, + struct ivi_wm * /* ivi_wm */, + uint32_t surface_id, + uint32_t frame_count, + uint32_t pid); + + static void ivi_wm_layer_surface_added(void *data, + struct ivi_wm *ivi_wm, + uint32_t layer_id, + uint32_t surface_id); + + static constexpr struct ivi_wm_listener ivi_wm_listener_ = { + .surface_visibility = ivi_wm_surface_visibility, + .layer_visibility = ivi_wm_layer_visibility, + .surface_opacity = ivi_wm_surface_opacity, + .layer_opacity = ivi_wm_layer_opacity, + .surface_source_rectangle = ivi_wm_surface_source_rectangle, + .layer_source_rectangle = ivi_wm_layer_source_rectangle, + .surface_destination_rectangle = ivi_wm_surface_destination_rectangle, + .layer_destination_rectangle = ivi_wm_layer_destination_rectangle, + .surface_created = ivi_wm_surface_created, + .layer_created = ivi_wm_layer_created, + .surface_destroyed = ivi_wm_surface_destroyed, + .layer_destroyed = ivi_wm_layer_destroyed, + .surface_error = ivi_wm_surface_error, + .layer_error = ivi_wm_layer_error, + .surface_size = ivi_wm_surface_size, + .surface_stats = ivi_wm_surface_stats, + .layer_surface_added = ivi_wm_layer_surface_added, + }; + +}; diff --git a/src/window_manager/output.cc b/src/window_manager/output.cc index 28e6a0b..b3dc90d 100644 --- a/src/window_manager/output.cc +++ b/src/window_manager/output.cc @@ -18,7 +18,7 @@ #include -#include +#include "logging.h" /** * @class Output @@ -28,40 +28,14 @@ * access to the output's properties such as geometry and mode. It also handles * the events emitted by the output. */ -Output::Output(struct wl_output *output, uint32_t version) : version_( - version), wl_output_(output) { +Output::Output(struct wl_output *wl_output) : wl_output_(wl_output) { + SPDLOG_TRACE("++Output::Output()"); wl_output_add_listener(wl_output_, &listener_, this); + SPDLOG_TRACE("--Output::Output()"); } -/** - * @class Output - * @brief The Output class represents a Wayland output. - * - * The Output class provides methods to manage Wayland outputs, such as releasing and destroying the output. - */ -Output::~Output() { - wl_output_release(wl_output_); - wl_output_destroy(wl_output_); -} +Output::~Output() = default; -/** - * @brief Handle the geometry event of the wl_output interface. - * - * This function is called when the wl_output interface emits the - * geometry event, indicating changes in the output's position, size, - * and physical properties. - * - * @param data Pointer to the Output object. - * @param wl_output The wl_output object. - * @param x The x coordinate of the output's position. - * @param y The y coordinate of the output's position. - * @param physical_width The physical width of the output in millimeters. - * @param physical_height The physical height of the output in millimeters. - * @param subpixel The subpixel arrangement of the output. - * @param make The make of the output device. - * @param model The model of the output device. - * @param transform The transform applied to the output. - */ void Output::handle_geometry(void *data, struct wl_output *wl_output, int x, @@ -72,127 +46,142 @@ void Output::handle_geometry(void *data, const char *make, const char *model, int transform) { - const auto obj = static_cast(data); - assert(obj->wl_output_ == wl_output); - obj->output_ = { - .geometry = { - .x = x, - .y = y, - .physical_width = physical_width, - .physical_height = physical_height, - .subpixel = subpixel, - .make = make, - .model = model, - .transform = transform - }, - .mode = {}, - .done{}, - .name{}, - .description{}, + SPDLOG_TRACE("++Output::handle_geometry()"); + auto obj = static_cast(data); + if (obj->wl_output_ != wl_output) { + return; + } + obj->output_.geometry = { + .x = x, + .y = y, + .physical_width = physical_width, + .physical_height = physical_height, + .subpixel = subpixel, + .make = make, + .model = model, + .transform = static_cast(transform), }; + SPDLOG_TRACE("--Output::handle_geometry()"); } -/** -* @brief This function is responsible for handling the mode of the output. -* -* The handle_mode function is called when the mode of the output is updated. It sets the mode of the output -* structure to the provided values. -* -* @param data A pointer to the instance of the Output class. -* @param wl_output A pointer to the wl_output structure. -* @param flags The flags of the mode. -* @param width The width of the mode. -* @param height The height of the mode. -* @param refresh The refresh rate of the mode. -*/ void Output::handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width, int height, int refresh) { - const auto obj = static_cast(data); - assert(obj->wl_output_ == wl_output); + SPDLOG_TRACE("++Output::handle_mode()"); + auto obj = static_cast(data); + if (obj->wl_output_ != wl_output) { + return; + } obj->output_.mode = { .flags = flags, .width = width, .height = height, - .refresh = refresh, + .refresh = refresh }; + SPDLOG_TRACE("--Output::handle_mode()"); } -/** - * @brief Handle the completion of an output event. - * - * This function is a callback that is invoked when an output event is completed. - * - * @param data A pointer to the associated Output object. - * @param wl_output The Wayland output object. - */ -void Output::handle_done(void *data, struct wl_output *wl_output) { - const auto obj = static_cast(data); - assert(obj->wl_output_ == wl_output); - obj->output_.done = true; -} - -/** - * @brief Callback function for handling output scale change. - * - * This function is called when the scale of the output is changed. - * It updates the scale value of the Output object. - * - * @param data The user data associated with the Output object. - * @param wl_output The wl_output object associated with the event. - * @param scale The new scale value. - */ void Output::handle_scale(void *data, struct wl_output *wl_output, - int scale) { - const auto obj = static_cast(data); - assert(obj->wl_output_ == wl_output); - obj->output_.scale = scale; + int32_t factor) { + SPDLOG_TRACE("++Output::handle_scale()"); + auto obj = static_cast(data); + if (obj->wl_output_ != wl_output) { + return; + } + obj->output_.factor = factor; + SPDLOG_TRACE("++Output::handle_scale()"); +} + +void Output::handle_done(void *data, + struct wl_output *wl_output) { + SPDLOG_TRACE("++Output::handle_done()"); + auto obj = static_cast(data); + if (wl_output != obj->wl_output_) { + return; + } + + auto output = obj->output_; + + output.done = true; + SPDLOG_TRACE("--Output::handle_done()"); } -/** - * @brief Handle the name event from the wl_output interface. - * - * This function is called when the name property of the output is updated. - * - * @param data A pointer to the Output object. - * @param wl_output A pointer to the wl_output object. - * @param name The new name of the output. - */ void Output::handle_name(void *data, struct wl_output *wl_output, const char *name) { - const auto obj = static_cast(data); - assert(obj->wl_output_ == wl_output); + SPDLOG_TRACE("++Output::handle_name()"); + auto obj = static_cast(data); + if (obj->wl_output_ != wl_output) { + return; + } obj->output_.name = name; + SPDLOG_TRACE("--Output::handle_name()"); } -/** - * @brief Handles the description of an output. - * - * This function is invoked when a description is received for a specific output. It updates the description in the - * `output_` member of the `Output` object. - * - * @param data A pointer to the `Output` object. - * @param wl_output A pointer to the `wl_output` object. - * @param description The description of the output. - */ -void Output::handle_description(void *data, - struct wl_output *wl_output, - const char *description) { - const auto obj = static_cast(data); - assert(obj->wl_output_ == wl_output); - obj->output_.description = description; +void Output::handle_desc(void *data, + struct wl_output *wl_output, + const char *desc) { + SPDLOG_TRACE("++Output::handle_desc()"); + auto obj = static_cast(data); + if (obj->wl_output_ != wl_output) { + return; + } + obj->output_.description = desc; + SPDLOG_TRACE("--Output::handle_desc()"); +} + +std::string Output::transform_to_string(enum wl_output_transform transform) { + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + return "WL_OUTPUT_TRANSFORM_NORMAL"; + case WL_OUTPUT_TRANSFORM_90: + return "WL_OUTPUT_TRANSFORM_90"; + case WL_OUTPUT_TRANSFORM_180: + return "WL_OUTPUT_TRANSFORM_180"; + case WL_OUTPUT_TRANSFORM_270: + return "WL_OUTPUT_TRANSFORM_270"; + case WL_OUTPUT_TRANSFORM_FLIPPED: + return "WL_OUTPUT_TRANSFORM_FLIPPED"; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + return "WL_OUTPUT_TRANSFORM_FLIPPED_90"; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + return "WL_OUTPUT_TRANSFORM_FLIPPED_180"; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + return "WL_OUTPUT_TRANSFORM_FLIPPED_270"; + } + return {}; } -const struct wl_output_listener Output::listener_ = { - .geometry = handle_geometry, - .mode = handle_mode, - .done = handle_done, - .scale = handle_scale, - .name = handle_name, - .description = handle_description, -}; +void Output::print() { + spdlog::info("Output"); +#if defined(WL_OUTPUT_NAME_SINCE_VERSION) + spdlog::info("\tName: {}", output_.name); +#endif +#if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION) + spdlog::info("\tDescription: {}", output_.description); +#endif + spdlog::info("\tMode"); + spdlog::info("\t\tSize: {}x{}", output_.mode.width, output_.mode.height); + spdlog::info("\t\tRefresh: {}", output_.mode.refresh); + spdlog::info("\t\tFlags: "); + if ((output_.mode.flags & WL_OUTPUT_MODE_CURRENT) == WL_OUTPUT_MODE_CURRENT) { + spdlog::info("\t\t\tWL_OUTPUT_MODE_CURRENT"); + } + if ((output_.mode.flags & WL_OUTPUT_MODE_PREFERRED) == WL_OUTPUT_MODE_PREFERRED) { + spdlog::info("\t\t\tWL_OUTPUT_MODE_PREFERRED"); + } + spdlog::info("\tGeometry"); + spdlog::info("\t\tMake: {}", output_.geometry.make); + spdlog::info("\t\tModel: {}", output_.geometry.model); + spdlog::info("\t\tPhysical: {}x{}", output_.geometry.physical_width, output_.geometry.physical_height); + spdlog::info("\t\tSubpixel: {}", output_.geometry.subpixel); + spdlog::info("\t\tTransform: {}", transform_to_string(output_.geometry.transform)); + spdlog::info("\t\tx: {}, y: {}", output_.geometry.x, output_.geometry.y); +#if defined(WL_OUTPUT_SCALE_SINCE_VERSION) + spdlog::info("\tScaling factor: {}", output_.factor); +#endif +} diff --git a/src/window_manager/output.h b/src/window_manager/output.h index 5ba082f..0b2cb7a 100644 --- a/src/window_manager/output.h +++ b/src/window_manager/output.h @@ -14,59 +14,68 @@ * limitations under the License. */ -#ifndef SRC_OUTPUT_H_ -#define SRC_OUTPUT_H_ +#pragma once #include #include #include #include +#include #include class Output { public: - struct geometry { - int x; - int y; - int physical_width; - int physical_height; - int subpixel; - const char *make; - const char *model; - int transform; - }; + explicit Output(struct wl_output *output); - struct mode { - uint32_t flags; - int width; - int height; - int refresh; - }; + ~Output(); - Output(struct wl_output *output, uint32_t version); + [[nodiscard]] int32_t get_scale_factor() const { return output_.factor; } - ~Output(); + [[nodiscard]] enum wl_output_transform get_transform() const { return output_.geometry.transform; } - [[nodiscard]] const struct geometry &get_geometry() const { return output_.geometry; } + [[nodiscard]] const std::string &get_name() const { return output_.name; } - [[nodiscard]] const struct mode &get_mode() const { return output_.mode; } + void print(); - [[nodiscard]] uint32_t get_version() const { return version_; } + static std::string transform_to_string(enum wl_output_transform transform); + + // Disallow copy and assign. + Output(const Output &) = delete; + + Output &operator=(const Output &) = delete; private: - struct { - struct geometry geometry; - struct mode mode; - bool done; - std::optional scale; + struct wl_output *wl_output_; + + typedef struct { + struct { + int x; + int y; + int physical_width; + int physical_height; + int subpixel; + std::string make; + std::string model; + enum wl_output_transform transform; + } geometry; + + struct { + uint32_t flags; + int width; + int height; + int refresh; + } mode; + + int32_t factor; std::string name; std::string description; - } output_; + bool done; - uint32_t version_; - struct wl_output *wl_output_; + } OUTPUT_INFO_T; + + OUTPUT_INFO_T output_; static void handle_geometry(void *data, struct wl_output *wl_output, @@ -86,21 +95,37 @@ class Output { int height, int refresh); - static void handle_done(void *data, struct wl_output *wl_output); + static void handle_done(void *data, + struct wl_output *wl_output); static void handle_scale(void *data, struct wl_output *wl_output, - int scale); + int32_t factor); static void handle_name(void *data, struct wl_output *wl_output, const char *name); - static void handle_description(void *data, - struct wl_output *wl_output, - const char *description); + static void handle_desc(void *data, + struct wl_output *wl_output, + const char *desc); + + static constexpr struct wl_output_listener listener_ = { + handle_geometry, + handle_mode, + handle_done +#if defined(WL_OUTPUT_SCALE_SINCE_VERSION) + , + handle_scale +#endif +#if defined(WL_OUTPUT_NAME_SINCE_VERSION) + , + handle_name +#endif +#if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION) + , + handle_desc +#endif + }; - static const struct wl_output_listener listener_; }; - -#endif //SRC_OUTPUT_H_ \ No newline at end of file diff --git a/src/window_manager/registrar.cc b/src/window_manager/registrar.cc new file mode 100644 index 0000000..44f6774 --- /dev/null +++ b/src/window_manager/registrar.cc @@ -0,0 +1,288 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "registrar.h" + +#include "output.h" +#include "logging.h" + +Registrar::Registrar(struct wl_display *wl_display) : wl_display_(wl_display), + wl_registry_(wl_display_get_registry(wl_display)) { + SPDLOG_TRACE("++Registrar::Registrar()"); + wl_registry_add_listener(wl_registry_, &listener_, + this); + wl_display_roundtrip(wl_display_); + SPDLOG_TRACE("--Registrar::Registrar()"); +} + +Registrar::~Registrar() { + SPDLOG_TRACE("++Registrar::~Registrar()"); + + for (auto &it: output_.outputs) { + it.second.reset(); + wl_output_destroy(it.first); + } + + for (auto &it: seat_.seats) { + it.second.reset(); + wl_seat_destroy(it.first); + } + + if (shm_.wl_shm) { + wl_shm_destroy(shm_.wl_shm); + shm_.formats.clear(); + } + + if (compositor_.wl_compositor) { + wl_compositor_destroy(compositor_.wl_compositor); + } + + if (sub_compositor_.wl_subcompositor) { + wl_subcompositor_destroy(sub_compositor_.wl_subcompositor); + } + + if (xdg_wm_base_.xdg_wm_base.has_value()) { + xdg_wm_base_destroy(xdg_wm_base_.xdg_wm_base.value()); + } + + if (agl_shell_.agl_shell.has_value()) { + agl_shell_destroy(agl_shell_.agl_shell.value()); + } + + if (xdg_decoration_manager_.zxdg_toplevel_decoration_v1.has_value()) { +#if defined(WAYLAND_PROTOCOL_HAS_XDG_DECORATION) + zxdg_toplevel_decoration_v1_destroy(xdg_decoration_manager_.zxdg_toplevel_decoration_v1.value()); +#endif + } + + if (xdg_decoration_manager_.zxdg_decoration_manager_v1.has_value()) { +#if defined(WAYLAND_PROTOCOL_HAS_XDG_DECORATION) + zxdg_decoration_manager_v1_destroy(xdg_decoration_manager_.zxdg_decoration_manager_v1.value()); +#endif + } + + if (tearing_manager_.wp_tearing_control_manager.has_value()) { +#if defined(WAYLAND_PROTOCOL_HAS_TEARING_CONTROL) + wp_tearing_control_manager_v1_destroy(tearing_manager_.wp_tearing_control_manager.value()); +#endif + } + + if (viewporter_.wp_viewporter.has_value()) { +#if defined(WAYLAND_PROTOCOL_HAS_VIEWPORTER) + wp_viewporter_destroy(viewporter_.wp_viewporter.value()); +#endif + } + + if (fractional_scale_manager_.fractional_scale_manager.has_value()) { +#if defined(WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE) + wp_fractional_scale_manager_v1_destroy(fractional_scale_manager_.fractional_scale_manager.value()); +#endif + } + + if (wl_registry_) { + wl_registry_destroy(wl_registry_); + } + SPDLOG_TRACE("--Registrar::~Registrar()"); +} + +void Registrar::registry_handle_global(void *data, + struct wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version) { + SPDLOG_TRACE("++Registrar::registry_handle_global()\t\n\t{}: {}", interface, version); + auto r = static_cast(data); + + std::map> interface_to_process_fn = { + {wl_compositor_interface.name, [&]() { + r->compositor_.wl_compositor = static_cast( + wl_registry_bind(registry, name, &wl_compositor_interface, + std::min(static_cast(r->compositor_.min_version), version))); + SPDLOG_DEBUG("{}: {}", wl_compositor_interface.name, + wl_compositor_get_version(r->compositor_.wl_compositor)); + }}, + {wl_subcompositor_interface.name, [&]() { + r->sub_compositor_.wl_subcompositor = static_cast( + wl_registry_bind(registry, name, &wl_subcompositor_interface, + std::min(static_cast(r->sub_compositor_.min_version), version))); + SPDLOG_DEBUG("{}: {}", wl_subcompositor_interface.name, + wl_subcompositor_get_version(r->sub_compositor_.wl_subcompositor)); + }}, + {wl_shm_interface.name, [&]() { + r->shm_.wl_shm = static_cast( + wl_registry_bind(registry, name, &wl_shm_interface, + std::min(static_cast(r->shm_.min_version), version))); + wl_shm_add_listener(r->shm_.wl_shm, &shm_listener_, r); + SPDLOG_DEBUG("{}: {}", wl_shm_interface.name, wl_shm_get_version(r->shm_.wl_shm)); + }}, + {wl_seat_interface.name, [&]() { + auto wl_seat = static_cast( + wl_registry_bind(registry, name, &wl_seat_interface, + std::min(static_cast(r->seat_.min_version), version))); + r->seat_.seats[wl_seat] = std::make_unique(wl_seat); + SPDLOG_DEBUG("{}: {}", wl_seat_interface.name, wl_seat_get_version(wl_seat)); + }}, + {wl_output_interface.name, [&]() { + auto wl_output = static_cast( + wl_registry_bind(registry, name, &wl_output_interface, + std::min(static_cast(r->output_.min_version), version))); + r->output_.outputs[wl_output] = std::make_unique(wl_output); + SPDLOG_DEBUG("{}: {}", wl_output_interface.name, wl_output_get_version(wl_output)); + }}, + {xdg_wm_base_interface.name, [&]() { + r->xdg_wm_base_.xdg_wm_base = static_cast( + wl_registry_bind(registry, name, &xdg_wm_base_interface, + std::min(static_cast(r->xdg_wm_base_.min_version), version))); + SPDLOG_DEBUG("{}: {}", xdg_wm_base_interface.name, + xdg_wm_base_get_version(r->xdg_wm_base_.xdg_wm_base.value())); + }}, + {agl_shell_interface.name, [&]() { + r->agl_shell_.agl_shell = static_cast( + wl_registry_bind(registry, name, &agl_shell_interface, + std::min(static_cast(r->agl_shell_.min_version), version))); + SPDLOG_DEBUG("{}: {}", agl_shell_interface.name, + agl_shell_get_version(r->agl_shell_.agl_shell.value())); + }}, + {ivi_wm_interface.name, [&]() { + r->ivi_wm_.ivi_wm = static_cast( + wl_registry_bind(registry, name, &ivi_wm_interface, + std::min(static_cast(r->ivi_wm_.min_version), version))); + SPDLOG_DEBUG("{}: {}", ivi_wm_interface.name, + ivi_wm_get_version(r->ivi_wm_.ivi_wm.value())); + }}, +#if defined(WAYLAND_PROTOCOL_HAS_XDG_DECORATION) + { + zxdg_decoration_manager_v1_interface.name, [&]() -> void { + r->xdg_decoration_manager_.zxdg_decoration_manager_v1 = static_cast( + wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, + std::min(static_cast(r->xdg_decoration_manager_.min_version), + version))); + SPDLOG_DEBUG("{}: {}", zxdg_decoration_manager_v1_interface.name, + zxdg_decoration_manager_v1_get_version( + r->xdg_decoration_manager_.zxdg_decoration_manager_v1.value())); + }}, + { + zxdg_toplevel_decoration_v1_interface.name, [&]() -> void { + r->xdg_decoration_manager_.zxdg_toplevel_decoration_v1 = static_cast( + wl_registry_bind(registry, name, &zxdg_toplevel_decoration_v1_interface, + std::min(static_cast(r->xdg_decoration_manager_.min_version), + version))); + SPDLOG_DEBUG("{}: {}", zxdg_toplevel_decoration_v1_interface.name, + zxdg_toplevel_decoration_v1_get_version( + r->xdg_decoration_manager_.zxdg_toplevel_decoration_v1.value())); + }}, +#endif +#if defined(WAYLAND_PROTOCOL_HAS_PRESENTATION_TIME) + { + wp_presentation_interface.name, [&]() { + r->presentation_time_.wp_presentation_time = static_cast( + wl_registry_bind(registry, name, &wp_presentation_interface, + std::min(static_cast(r->presentation_time_.min_version), + version))); + SPDLOG_DEBUG("{}: {}", wp_presentation_interface.name, + wp_presentation_get_version(r->presentation_time_.wp_presentation_time.value())); + }}, +#endif +#if defined(WAYLAND_PROTOCOL_HAS_TEARING_CONTROL) + { + wp_tearing_control_manager_v1_interface.name, [&]() { + auto r = static_cast(data); + r->tearing_manager_.wp_tearing_control_manager = static_cast( + wl_registry_bind(registry, name, &wp_tearing_control_manager_v1_interface, + std::min(static_cast(r->tearing_manager_.min_version), + version))); + SPDLOG_DEBUG("{}: {}", wp_tearing_control_manager_v1_interface.name, + wp_tearing_control_manager_v1_get_version( + r->tearing_manager_.wp_tearing_control_manager.value())); + }}, +#endif +#if defined(WAYLAND_PROTOCOL_HAS_VIEWPORTER) + { + wp_viewporter_interface.name, [&]() { + auto r = static_cast(data); + r->viewporter_.wp_viewporter = static_cast( + wl_registry_bind(registry, name, &wp_viewporter_interface, + std::min(static_cast(r->viewporter_.min_version), version))); + SPDLOG_DEBUG("{}: {}", wp_viewporter_interface.name, + wp_viewporter_get_version(r->viewporter_.wp_viewporter.value())); + }}, +#endif +#if defined(WAYLAND_PROTOCOL_HAS_FRACTIONAL_SCALE) + { + wp_fractional_scale_manager_v1_interface.name, [&]() { + auto r = static_cast(data); + r->fractional_scale_manager_.fractional_scale_manager = static_cast( + wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, + std::min(static_cast(r->fractional_scale_manager_.min_version), + version))); + SPDLOG_DEBUG("{}: {}", wp_fractional_scale_manager_v1_interface.name, + wp_fractional_scale_manager_v1_get_version( + r->fractional_scale_manager_.fractional_scale_manager.value())); + }}, +#endif + }; + + auto found = interface_to_process_fn.find(interface); + if (found != interface_to_process_fn.end()) { + found->second(); // Execute the process function for the found interface. + } + SPDLOG_TRACE("--Registrar::registry_handle_global()"); +} + +void Registrar::registry_handle_global_remove(void *data, + struct wl_registry *reg, + uint32_t id) { + SPDLOG_TRACE("++Registrar::registry_handle_global_remove()"); + auto obj = static_cast(data); + std::scoped_lock lock(obj->registrar_global_remove_mutex_); + if (obj->registrar_remove_.find(id) != obj->registrar_remove_.end()) { + auto p = obj->registrar_remove_[id]; + p.first(p.second, reg, id); + } + SPDLOG_TRACE("--Registrar::registry_handle_global_remove()"); +} + +void Registrar::shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { + SPDLOG_TRACE("++Registrar::shm_format()"); + auto obj = static_cast(data); + if (obj->shm_.wl_shm != wl_shm) { + return; + } + obj->shm_.formats.push_back(format); + SPDLOG_TRACE("--Registrar::shm_format()"); +} + +enum wl_output_transform Registrar::get_output_buffer_transform(struct wl_output *wl_output) { + for (auto &output: output_.outputs) { + if (wl_output == output.first) { + output.second->print(); + return output.second->get_transform(); + break; + } + } + return WL_OUTPUT_TRANSFORM_NORMAL; +} + +int32_t Registrar::get_output_buffer_scale(struct wl_output *wl_output) { + int32_t scale = 1; + for (auto &output: output_.outputs) { + if (wl_output == output.first) { + scale = output.second->get_scale_factor(); + break; + } + } + return scale; +} diff --git a/src/window_manager/registrar.h b/src/window_manager/registrar.h new file mode 100644 index 0000000..df5734c --- /dev/null +++ b/src/window_manager/registrar.h @@ -0,0 +1,225 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include "config.h" +#include "output.h" +#include "wayland-protocols.h" +#include "seat/seat.h" + +class AglShell; + +class WindowManager; + +class Registrar { +public: + typedef void (*RegistrarGlobalCallback)( + void *data, + struct wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version); + + typedef void (*RegistrarGlobalRemoveCallback)( + void *data, + struct wl_registry *registry, + uint32_t id); + + explicit Registrar(struct wl_display *wl_display); + + ~Registrar(); + + // Returns whether the shared memory has a specific format. + [[nodiscard]] std::optional shm_has_format(enum wl_shm_format format) const { + if (shm_.wl_shm == nullptr) { + return {}; + } + if (std::find(shm_.formats.begin(), shm_.formats.end(), format) != shm_.formats.end()) { + return true; + } + return false; + } + + // Returns the compositor. + [[nodiscard]] struct wl_compositor *get_compositor() const { return compositor_.wl_compositor; } + + // Returns the subcompositor if available. + [[nodiscard]] std::optional get_subcompositor() const { + if (sub_compositor_.wl_subcompositor == nullptr) { + return {}; + } + return sub_compositor_.wl_subcompositor; + } + + // Returns the shared memory if it is not null. + [[nodiscard]] std::optional get_shm() const { + if (shm_.wl_shm == nullptr) { + return {}; + } + return shm_.wl_shm; + } + + // Returns the registry. + [[nodiscard]] struct wl_registry *get_registry() const { return wl_registry_; } + + // Returns the xdg surface manager base if it exists. + [[nodiscard]] std::optional get_xdg_wm_base() const { return xdg_wm_base_.xdg_wm_base; } + + // Returns the AGL Shell if it exists. + [[nodiscard]] std::optional get_agl_shell() const { return agl_shell_.agl_shell; } + + // Returns the ivi surface manager if it exists. + [[nodiscard]] std::optional get_ivi_wm() const { return ivi_wm_.ivi_wm; } + + [[nodiscard]] std::optional + get_presentation_time() const { return presentation_time_.wp_presentation_time; } + + [[nodiscard]] std::optional + get_tearing_control_manager() const { return tearing_manager_.wp_tearing_control_manager; } + + [[nodiscard]] std::optional + get_viewporter() const { return viewporter_.wp_viewporter; } + + [[nodiscard]] std::optional + get_fractional_scale_manager() const { return fractional_scale_manager_.fractional_scale_manager; } + + [[nodiscard]] const std::map> & + get_outputs() const { return output_.outputs; } + + enum wl_output_transform get_output_buffer_transform(struct wl_output *wl_output); + + int32_t get_output_buffer_scale(struct wl_output *wl_output); + + // Disallow copy and assign. + Registrar(const Registrar &) = delete; + + Registrar &operator=(const Registrar &) = delete; + +private: + friend AglShell; + friend WindowManager; + + struct wl_display *wl_display_; + struct wl_registry *wl_registry_; + + struct { + uint32_t min_version = kWlSeatMinVersion; + std::map> seats; + } seat_; + + struct { + uint32_t min_version = kWlShmMinVersion; + struct wl_shm *wl_shm; + std::vector formats; + } shm_; + + struct { + uint32_t min_version = kWlCompositorMinVersion; + struct wl_compositor *wl_compositor{}; + } compositor_; + + struct { + uint32_t min_version = kWlSubcompositorMinVersion; + struct wl_subcompositor *wl_subcompositor{}; + } sub_compositor_; + + struct { + uint32_t min_version = kXdgWmBaseMinVersion; + std::optional xdg_wm_base; + } xdg_wm_base_; + + struct { + uint32_t min_version = kAglShellMinVersion; + std::optional agl_shell; + } agl_shell_; + + struct { + uint32_t min_version = kIviWmMinVersion; + std::optional ivi_wm; + } ivi_wm_; + + struct { + uint32_t min_version = kXdgDecorationManagerMinVersion; + std::optional zxdg_decoration_manager_v1; + std::optional zxdg_toplevel_decoration_v1; + } xdg_decoration_manager_; + + struct { + uint32_t min_version = kPresentationTimeMinVersion; + std::optional wp_presentation_time; + } presentation_time_; + + struct { + uint32_t min_version = kTearingControlManagerMinVersion; + std::optional wp_tearing_control_manager; + } tearing_manager_; + + struct { + uint32_t min_version = kViewporterMinVersion; + std::optional wp_viewporter; + } viewporter_; + + struct { + uint32_t min_version = kFractionalScaleManagerMinVersion; + std::optional fractional_scale_manager; + } fractional_scale_manager_; + + std::mutex registrar_global_mutex_; + std::mutex registrar_global_remove_mutex_; + std::map> registrar_add_; + std::map> registrar_remove_; + + // Handles global registry events. + static void registry_handle_global(void *data, + struct wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version); + + // Handles global registry removal events. + static void registry_handle_global_remove(void *data, + struct wl_registry *reg, + uint32_t id); + + static constexpr const wl_registry_listener listener_ = { + .global = registry_handle_global, + .global_remove = registry_handle_global_remove, + }; + + // Handles shm format events. + static void shm_format(void *data, + struct wl_shm *wl_shm, + uint32_t format); + + static constexpr wl_shm_listener shm_listener_ = { + .format = shm_format, + }; + +protected: + struct { + uint32_t min_version = kWlOutputMinVersion; + std::map> outputs; + } output_; +}; diff --git a/src/window_manager/window_manager.cc b/src/window_manager/window_manager.cc index a5d66b1..ad1e37d 100644 --- a/src/window_manager/window_manager.cc +++ b/src/window_manager/window_manager.cc @@ -16,18 +16,18 @@ #include "window_manager.h" -#include - #include #include -#include "window/window_vulkan.h" +#include "logging.h" +#include "registrar.h" +class Registrar; /** * @class WindowManager * - * @brief The WindowManager class is responsible for managing windows and handling window-related operations. + * @brief The WindowManager class is responsible for managing windows and handling surface-related operations. * * The WindowManager class extends the Display and Window classes and is used to create and manage windows in a graphical user interface application. * @@ -35,30 +35,23 @@ * @see Window * @see XdgWm */ -WindowManager::WindowManager(Window::ShellType shell_type, GMainContext *context, bool enable_cursor, - const char *name) : - Display(context, enable_cursor, name), - Window(wl_compositor_, shell_type, - [&](void * /* data */, uint32_t /* time */) { std::cerr << "base draw" << std::endl; }), - shell_type_(shell_type) { - - if (shell_type == XDG) { - xdg_wm_ = std::make_unique(this->wl_display_, this->wl_surface_); - - // this makes the start-up from the beginning with the correct dimensions - // like starting as maximized/fullscreen, rather than starting up as floating - // width, height then performing a resize - while (xdg_wm_->get_wait_for_configure()) { - wl_display_dispatch(wl_display_); - - // wait until xdg_surface::configure ACKs the new dimensions - if (xdg_wm_->get_wait_for_configure()) - continue; +WindowManager::WindowManager(GMainContext *context, + bool enable_cursor, + const char *display_name) : Registrar(get_display(display_name)), + context_(context), + outputs_(get_outputs()), + cursor_{.enable = enable_cursor} { + SPDLOG_TRACE("++WindowManager::WindowManager()"); + if (enable_cursor) { + if (!shm_has_format(WL_SHM_FORMAT_XRGB8888)) { + spdlog::warn("XRGB is not supported. Disabling cursor"); + cursor_.enable = false; + return; } - std::cout << "configured." << std::endl; + cursor_.cursor = std::make_unique(get_shm().value(), get_compositor()); } - - start_frames(); + // start_frames(); + SPDLOG_TRACE("--WindowManager::WindowManager()"); } /** @@ -68,94 +61,21 @@ WindowManager::WindowManager(Window::ShellType shell_type, GMainContext *context * It calls the stop_frames() function to stop rendering frames. */ WindowManager::~WindowManager() { - stop_frames(); -} - -/** - - * @brief Handles the event when a surface enters the window manager - - * - * @param data The data associated with the event - * @param surface The surface that enters the window manager - * @param output The output associated with the surface - * - * This function is called when a surface enters the window manager. It prints a message indicating that the surface has entered. - * - * Example Usage: - * - * ``` - * WindowManager wm; - * wl_surface *surface; - * wl_output *output; - * wm.handle_surface_enter(nullptr, surface, output); - * ``` - * - */ -void WindowManager::handle_surface_enter(void * /* data */, - struct wl_surface * /* surface */, - struct wl_output * /* output */) { - std::cout << "WindowManager::handle_surface_enter" << std::endl; -} - -/** - * @brief Handles the event when a surface leaves an output. - * - * This function is called when a surface leaves an output. It prints a message - * to the console indicating that the surface has left the output. - * - * @param data A pointer to the data associated with the event (unused). - * @param surface The surface that has left the output. - * @param output The output that the surface has left. - */ -void WindowManager::handle_surface_leave(void * /* data */, - struct wl_surface * /* surface */, - struct wl_output * /* output */) { - std::cout << "WindowManager::handle_surface_leave" << std::endl; + SPDLOG_TRACE("++WindowManager::~WindowManager()"); + wl_display_flush(wl_display_); + wl_display_disconnect(wl_display_); + SPDLOG_TRACE("--WindowManager::~WindowManager()"); } -const struct wl_surface_listener WindowManager::surface_listener_ = { - .enter = handle_surface_enter, - .leave = handle_surface_leave, -}; - -/** - * @brief Creates a new window and adds it to the WindowManager's list of windows. - * - * The function creates a new window based on the given parameters and adds it to the list of windows managed by the - * WindowManager. The type of the window can be either EGL or VULKAN. If the window type is EGL, a WindowEgl object is - * created using the provided display, compositor, surface, width, height, shell type, and draw callback. If the shell - * type is XDG, additional actions can be performed. If the window type is VULKAN, a WindowVulkan object can be - * created, but this part of the code is currently commented out. - * - * @param width The width of the window. - * @param height The height of the window. - * @param window_type The type of the window (EGL or VULKAN). - * @param draw_callback The function to be called when the window needs to be drawn. - * @return A pointer to the created window object, or nullptr if no window was created. - */ -WindowEgl *WindowManager::create_window(int width, int height, WindowType window_type, - const std::function &draw_callback) { - WindowEgl *result = nullptr; - - std::unique_ptr window; - if (window_type == EGL) { - window = std::make_unique(this->wl_display_, this->wl_compositor_, this->wl_surface_, width, height, - shell_type_, - draw_callback); - if (shell_type_ == Window::ShellType::XDG) { - } - } else if (window_type == VULKAN) { - //window = std::make_unique(this->wl_display_, this->wl_compositor_, width, height, shell_type_, - //draw_callback); - } - if (window) { - result = window.get(); - windows_.emplace_back(std::move(window)); +struct wl_display *WindowManager::get_display(const char *name) { + SPDLOG_TRACE("++WindowManager::get_display()"); + wl_display_ = (wl_display_connect(name)); + if (wl_display_ == nullptr) { + spdlog::critical("Failed to connect to Wayland display. {}", strerror(errno)); + exit(EXIT_FAILURE); } - - start_frames(); - return result; + SPDLOG_TRACE("--WindowManager::get_display()"); + return wl_display_; } /** @@ -166,7 +86,7 @@ WindowEgl *WindowManager::create_window(int width, int height, WindowType window * @param timeout The maximum amount of time to wait for events, in milliseconds. * @return The number of events dispatched on success, or a negative error code on failure. */ -int WindowManager::dispatch(int timeout) const { +[[maybe_unused]] int WindowManager::dispatch(int timeout) const { struct pollfd fds[1]; int dispatch_count = 0; diff --git a/src/window_manager/window_manager.h b/src/window_manager/window_manager.h index 791a666..b4b19f5 100644 --- a/src/window_manager/window_manager.h +++ b/src/window_manager/window_manager.h @@ -14,62 +14,68 @@ * limitations under the License. */ -#ifndef SRC_WINDOW_WINDOW_MANAGER_H_ -#define SRC_WINDOW_WINDOW_MANAGER_H_ +#pragma once -#include "display.h" +#include -#include +#include "registrar.h" +#include "seat/cursor.h" -#include "window/window.h" -#include "window/window_egl.h" +class Registrar; -#include "xdg_wm.h" +class XdgWindowManager; +class WindowManager : public Registrar { +public: + explicit WindowManager(GMainContext *context = nullptr, + bool enable_cursor = true, + const char *display_name = nullptr); -class Display; + ~WindowManager(); -class WindowEgl; + [[nodiscard]] struct wl_display *get_display() const { return wl_display_; } -class Window; + [[nodiscard]] int poll_events(int timeout) const; -class WindowManager : public Display, public Window { -public: - typedef enum { - EGL, - VULKAN, - } WindowType; + [[maybe_unused]] [[nodiscard]] int dispatch(int timeout) const; - explicit WindowManager(Window::ShellType shell_type = Window::ShellType::XDG, GMainContext *context = nullptr, - bool enable_cursor = true, - const char *name = nullptr); + [[nodiscard]] int dispatch_pending() const { + return wl_display_dispatch_pending( + get_display() + ); + } - ~WindowManager() override; + // Disallow copy and assign. + WindowManager(const WindowManager &) = delete; - WindowEgl * - create_window(int width, int height, WindowType window_type = WindowType::EGL, - const std::function &draw_callback = nullptr); + WindowManager &operator=(const WindowManager &) = delete; - [[nodiscard]] int poll_events(int timeout) const; +private: - [[nodiscard]] int dispatch(int timeout) const; + friend XdgWindowManager; -private: - // list of windows for z-order control - std::list> windows_; - std::unique_ptr xdg_wm_; + GMainContext *context_; - Window::ShellType shell_type_; + bool needs_buffer_geometry_update_{}; - static void handle_surface_enter(void *data, - struct wl_surface *surface, - struct wl_output *output); + struct { + int width; + int height; + } buffer_size_{}; - static void handle_surface_leave(void *data, - struct wl_surface *surface, - struct wl_output *output); + const std::map> &outputs_; - static const struct wl_surface_listener surface_listener_; -}; + struct wl_display *wl_display_{}; + + struct { + bool enable; + std::unique_ptr cursor; + } cursor_; -#endif // SRC_WINDOW_WINDOW_MANAGER_H_ \ No newline at end of file + enum wl_output_transform buffer_transform_; + + int32_t buffer_scale_ = 1; + double fractional_buffer_scale_ = 1.0; + + struct wl_display *get_display(const char *name); +}; diff --git a/src/window_manager/xdg_toplevel.cc b/src/window_manager/xdg_toplevel.cc new file mode 100644 index 0000000..2e2f00d --- /dev/null +++ b/src/window_manager/xdg_toplevel.cc @@ -0,0 +1,253 @@ +#include "xdg_toplevel.h" + +#include "logging.h" + +// workaround for Wayland macro not compiling in C++ +#define WL_ARRAY_FOR_EACH(pos, array, type) \ + for (pos = (type)(array)->data; \ + (const char*)pos < ((const char*)(array)->data + (array)->size); \ + (pos)++) + +XdgTopLevel::XdgTopLevel(WindowManager *wm, + struct wl_compositor *wl_compositor, + const std::optional &viewporter, + const std::optional &fractional_scale_manager, + const std::optional &tearing_control_manager, + const std::map> &outputs, + const char *name, int width, int height, wl_output_transform buffer_transform, + bool fullscreen, bool maximized, bool fullscreen_ratio, bool tearing, + const std::function &draw_frame_callback, + const int32_t *context_attribs, size_t context_attribs_size, + const int32_t *config_attribs, size_t config_attribs_size, + int buffer_bpp, int swap_interval) : Window( + wm, wl_compositor, viewporter, fractional_scale_manager, + tearing_control_manager, outputs, name, draw_frame_callback, width, height, buffer_transform, fullscreen, + maximized, fullscreen_ratio, tearing, context_attribs, context_attribs_size, config_attribs, + config_attribs_size, buffer_bpp, swap_interval), wm_(wm) { + auto xwm = reinterpret_cast(wm_); + auto xdg_wm_base = xwm->get_xdg_wm_base(); + if (!xdg_wm_base.has_value()) { + spdlog::critical("xdg_wm_base is not available"); + exit(EXIT_FAILURE); + } + SPDLOG_DEBUG("XDG Toplevel Surface: {}", fmt::ptr(get_surface())); + xdg_surface_ = xdg_wm_base_get_xdg_surface(xdg_wm_base.value(), get_surface()); + xdg_surface_add_listener(xdg_surface_, &xdg_surface_listener_, this); + + xdg_toplevel_ = xdg_surface_get_toplevel(xdg_surface_); + xdg_toplevel_add_listener(xdg_toplevel_, &xdg_toplevel_listener_, this); + + xdg_toplevel_set_title(xdg_toplevel_, title_.c_str()); + xdg_toplevel_set_app_id(xdg_toplevel_, app_id_.c_str()); + + if (fullscreen) { + xdg_toplevel_set_fullscreen(xdg_toplevel_, nullptr); + } else if (maximized) { + xdg_toplevel_set_maximized(xdg_toplevel_); + } + + wait_for_configure_ = true; + wl_surface_commit(get_surface()); + + // this makes the start-up from the beginning with the correct dimensions + // like starting as maximized/fullscreen, rather than starting up as floating + // width, height then performing a resize + while (wait_for_configure_) { + wl_display_dispatch(wm_->get_display()); + + // wait until xdg_surface::configure ACKs the new dimensions + if (wait_for_configure_) + continue; + } +} + +XdgTopLevel::~XdgTopLevel() { + if (xdg_toplevel_) + xdg_toplevel_destroy(xdg_toplevel_); + + if (xdg_surface_) + xdg_surface_destroy(xdg_surface_); +} + +void XdgTopLevel::resize(int /* width */, int /* height */) { +} + +/** + * @brief Handles the configure event for xdg_surface. + * + * This function is a member function of the XdgWm class. It is called when the xdg_surface + * sends a configure event. It acknowledges the configure request by calling xdg_surface_ack_configure(). + * It also sets the wait_for_configure_ variable to false. + * + * @param data A pointer to the XdgWm instance. + * @param xdg_surface A pointer to the xdg_surface instance. + * @param serial The serial number of the configure event. + */ +void XdgTopLevel::handle_xdg_surface_configure( + void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) { + auto *w = static_cast(data); + if (w->xdg_surface_ != xdg_surface) { + return; + } + xdg_surface_ack_configure(xdg_surface, serial); + w->wait_for_configure_ = false; +} + +/** + * @brief Handles the configure event for a toplevel surface. + * + * This function is called when the configure event is received for a toplevel surface. + * It updates the internal state of the XdgWm object based on the configuration properties + * received from the compositor. + * + * @param data The user data passed to the callback. + * @param toplevel The toplevel surface that triggered the event. + * @param width The width of the surface. + * @param height The height of the surface. + * @param states An array of states associated with the surface. + */ +void XdgTopLevel::handle_toplevel_configure( + void *data, + struct xdg_toplevel *toplevel, + int32_t width, + int32_t height, + struct wl_array *states) { + + if (width == 0 && height == 0) { + return; + } + + auto *w = static_cast(data); + if (w->xdg_toplevel_ != toplevel) { + return; + } + + w->fullscreen_ = false; + w->maximized_ = false; + w->resize_ = false; + w->activated_ = false; + + const uint32_t *state; + WL_ARRAY_FOR_EACH(state, states, const uint32_t*) { + switch (*state) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: + SPDLOG_DEBUG("XDG_TOPLEVEL_STATE_FULLSCREEN"); + w->fullscreen_ = true; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + SPDLOG_DEBUG("XDG_TOPLEVEL_STATE_MAXIMIZED"); + w->maximized_ = true; + break; + case XDG_TOPLEVEL_STATE_RESIZING: + SPDLOG_DEBUG("XDG_TOPLEVEL_STATE_RESIZING"); + w->resize_ = true; + break; + case XDG_TOPLEVEL_STATE_ACTIVATED: + SPDLOG_DEBUG("XDG_TOPLEVEL_STATE_ACTIVATED"); + w->activated_ = true; + break; + } + } + + if (width > 0 && height > 0) { + if (!w->fullscreen_ && !w->maximized_) { + w->window_size_.width = width; + w->window_size_.height = height; + } + w->logical_size_.width = width; + w->logical_size_.height = height; + } else if (!w->fullscreen_ && !w->maximized_) { + w->logical_size_.width = w->window_size_.width; + w->logical_size_.height = w->window_size_.height; + } + + w->set_needs_buffer_geometry_update(); + + SPDLOG_DEBUG("width: {}", width); + SPDLOG_DEBUG("height: {}", height); +} + +/** + * @brief Handles the close event of a toplevel surface. + * + * This function is a callback that is called when the user requests to close + * the toplevel surface. It sets the `running_` member variable to false, which + * will cause the main event loop to exit. + * + * @param data The user data associated with the XdgWm instance. + * @param xdg_toplevel The xdg_toplevel object that received the close request. + */ +void XdgTopLevel::handle_toplevel_close( + void *data, + struct xdg_toplevel *xdg_toplevel) { + SPDLOG_DEBUG("XdgWm::handle_toplevel_close"); + + auto *w = static_cast(data); + if (w->xdg_toplevel_ != xdg_toplevel) { + return; + } + w->running_ = false; +} + +/** + * + * @param data + * @param xdg_toplevel + * @param width + * @param height + */ +#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) + +void XdgTopLevel::handle_configure_bounds(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height) { + auto *w = static_cast(data); + if (w->xdg_toplevel_ != xdg_toplevel) { + return; + } + SPDLOG_DEBUG("Configure Bounds: {}x{}", width, height); + w->window_size_.width = width; + w->window_size_.height = height; +} + +#endif + +/** + * + * @param data + * @param xdg_toplevel + * @param capabilities + */ +#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + +void XdgTopLevel::handle_wm_capabilities(void *data, + struct xdg_toplevel *xdg_toplevel, + struct wl_array *capabilities) { + auto *w = static_cast(data); + if (w->xdg_toplevel_ != xdg_toplevel) { + return; + } + SPDLOG_DEBUG("WM Capabilities:"); + const uint32_t *cap; + WL_ARRAY_FOR_EACH(cap, capabilities, const uint32_t*) { + switch (*cap) { + case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU: + SPDLOG_DEBUG("\tXDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU"); + break; + case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: + SPDLOG_DEBUG("\tXDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE"); + break; + case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: + SPDLOG_DEBUG("\tXDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN"); + break; + case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: + SPDLOG_DEBUG("\tXDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE"); + break; + } + } +} + +#endif diff --git a/src/window_manager/xdg_toplevel.h b/src/window_manager/xdg_toplevel.h new file mode 100644 index 0000000..9f0176d --- /dev/null +++ b/src/window_manager/xdg_toplevel.h @@ -0,0 +1,146 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include "window/window.h" +#include "window_manager/xdg_window_manager.h" + +class Output; + +class Window; + +class XdgWindowManager; + +class XdgTopLevel : public Window { +public: + + XdgTopLevel(WindowManager *wm, + struct wl_compositor *wl_compositor, + const std::optional &viewporter, + const std::optional &fractional_scale_manager, + const std::optional &tearing_control_manager, + const std::map> &outputs, + const char *name, int width, int height, wl_output_transform buffer_transform, + bool fullscreen, bool maximized, bool fullscreen_ratio, bool tearing, + const std::function &draw_frame_callback, + const int32_t *context_attribs = nullptr, size_t context_attribs_size = 0, + const int32_t *config_attribs = nullptr, size_t config_attribs_size = 0, + int buffer_bpp = 0, int swap_interval = 0); + + ~XdgTopLevel(); + + void set_app_id(const char *app_id) { xdg_toplevel_set_app_id(xdg_toplevel_, app_id); } + + void set_title(const char *title) { xdg_toplevel_set_title(xdg_toplevel_, title); } + + void set_fullscreen() { xdg_toplevel_set_fullscreen(xdg_toplevel_, nullptr); } + + void set_minimize() { xdg_toplevel_set_minimized(xdg_toplevel_); } + + void set_maximize() { xdg_toplevel_set_maximized(xdg_toplevel_); } + + void resize(int width, int height); + +private: + WindowManager *wm_; + struct xdg_surface *xdg_surface_; + struct xdg_toplevel *xdg_toplevel_; + + std::string title_; + std::string app_id_; + + volatile bool wait_for_configure_; + + bool fullscreen_{}; + bool maximized_{}; + bool fullscreen_ratio_{}; + + bool resize_{}; + bool activated_{}; + bool running_{}; + + struct { + int32_t width; + int32_t height; + } window_size_{}; + + struct { + int width; + int height; + } buffer_size_{}; + + struct { + int width; + int height; + } logical_size_{}; + + static void handle_xdg_surface_configure( + void *data, + struct xdg_surface *xdg_surface, + uint32_t serial); + + static constexpr struct xdg_surface_listener xdg_surface_listener_ = { + .configure = handle_xdg_surface_configure, + }; + + static void handle_toplevel_configure( + void *data, + struct xdg_toplevel *toplevel, + int32_t width, + int32_t height, + struct wl_array *states); + + static void handle_toplevel_close( + void *data, + struct xdg_toplevel *xdg_toplevel); + +#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) + + static void handle_configure_bounds(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height); + +#endif +#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + + static void handle_wm_capabilities(void *data, + struct xdg_toplevel *xdg_toplevel, + struct wl_array *capabilities); + +#endif + + static constexpr struct xdg_toplevel_listener xdg_toplevel_listener_ = { + .configure = handle_toplevel_configure, + .close = handle_toplevel_close +#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION) + , + .configure_bounds = handle_configure_bounds +#endif +#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + , + .wm_capabilities = handle_wm_capabilities +#endif + }; +}; diff --git a/src/window_manager/xdg_window_manager.cc b/src/window_manager/xdg_window_manager.cc new file mode 100644 index 0000000..3398540 --- /dev/null +++ b/src/window_manager/xdg_window_manager.cc @@ -0,0 +1,93 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "xdg_window_manager.h" + +#include "logging.h" +#include "xdg_toplevel.h" + + +/** + * @class XdgWindowManager + * + * @brief XdgWm represents a surface manager for a Wayland-based display. + * + * The XdgWm class is responsible for managing application windows using the XDG Shell protocol. + */ +XdgWindowManager::XdgWindowManager(GMainContext *context, + bool enable_cursor, + const char *display_name) : WindowManager(context, enable_cursor, display_name) { + SPDLOG_TRACE("++XdgWindowManager::XdgWindowManager()"); + auto xdg_wm_base = get_xdg_wm_base(); + if (!xdg_wm_base.has_value()) { + spdlog::critical("XDG Window Manager is not supported."); + exit(EXIT_FAILURE); + } + xdg_wm_base_add_listener(xdg_wm_base.value(), &xdg_wm_base_listener_, this); + SPDLOG_TRACE("--XdgWindowManager::XdgWindowManager()"); +} + +/** + * @class XdgWindowManager + * + * @brief The XdgWm class represents a Wayland shell surface manager. + * + * The XdgWm class manages the creation, destruction, configuration, and behavior of a Wayland shell surface manager. + * + * It is responsible for handling interactions with the Wayland registry, creating and destroying the surface manager base, + * surface, and toplevel objects, and implementing the necessary event handling functions. + */ +XdgWindowManager::~XdgWindowManager() = default; + +/** + * + * @param data + * @param xdg_wm_base + * @param serial + */ +void XdgWindowManager::xdg_wm_base_ping(void *data, + struct xdg_wm_base *xdg_wm_base, + uint32_t serial) { + auto wm = static_cast(data); + if (wm->get_xdg_wm_base().value() != xdg_wm_base) { + SPDLOG_CRITICAL("wm->get_xdg_wm_base().value() != xdg_wm_base"); + return; + } + SPDLOG_DEBUG("xdg_wm_base_ping"); + xdg_wm_base_pong(xdg_wm_base, serial); +} + +XdgTopLevel * +XdgWindowManager::CreateTopLevel(const char *name, int width, int height, enum wl_output_transform buffer_transform, + bool fullscreen, bool maximized, bool fullscreen_ratio, bool tearing, + const std::function &draw_frame_callback, + const int32_t *context_attribs, size_t context_attribs_size, + const int32_t *config_attribs, size_t config_attribs_size, + int buffer_bpp, int swap_interval) { + auto wm = reinterpret_cast(this); + xdg_top_level_ = std::make_unique(wm, wm->get_compositor(), + wm->get_viewporter(), + wm->get_fractional_scale_manager(), + wm->get_tearing_control_manager(), + wm->get_outputs(), + name, width, height, buffer_transform, fullscreen, maximized, + fullscreen_ratio, tearing, + draw_frame_callback, + context_attribs, context_attribs_size, + config_attribs, config_attribs_size, + buffer_bpp, swap_interval); + return xdg_top_level_.get(); +} diff --git a/src/window_manager/xdg_window_manager.h b/src/window_manager/xdg_window_manager.h new file mode 100644 index 0000000..ccf06bf --- /dev/null +++ b/src/window_manager/xdg_window_manager.h @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Joel Winarske + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "wayland-protocols.h" + +#include "window_manager.h" + +class XdgTopLevel; + +class XdgWindowManager : public WindowManager { +public: + explicit XdgWindowManager(GMainContext *context = nullptr, + bool enable_cursor = true, + const char *display_name = nullptr); + + ~XdgWindowManager(); + + XdgTopLevel *CreateTopLevel(const char *name, int width, int height, enum wl_output_transform buffer_transform, + bool fullscreen, bool maximized, bool fullscreen_ratio, bool tearing, + const std::function &draw_frame_callback, + const int32_t *context_attribs = nullptr, size_t context_attribs_size = 0, + const int32_t *config_attribs = nullptr, size_t config_attribs_size = 0, + int buffer_bpp = 0, int swap_interval = 0); + + // Disallow copy and assign. + XdgWindowManager(const XdgWindowManager &) = delete; + + XdgWindowManager &operator=(const XdgWindowManager &) = delete; + +private: + std::unique_ptr xdg_top_level_; + + static void xdg_wm_base_ping(void *data, + struct xdg_wm_base *xdg_wm_base, + uint32_t serial); + + static constexpr struct xdg_wm_base_listener xdg_wm_base_listener_ = { + .ping = xdg_wm_base_ping, + }; +}; diff --git a/src/window_manager/xdg_wm.cc b/src/window_manager/xdg_wm.cc deleted file mode 100644 index 0620ee2..0000000 --- a/src/window_manager/xdg_wm.cc +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "xdg_wm.h" - -#include -#include - -// workaround for Wayland macro not compiling in C++ -#define WL_ARRAY_FOR_EACH(pos, array, type) \ - for (pos = (type)(array)->data; \ - (const char*)pos < ((const char*)(array)->data + (array)->size); \ - (pos)++) - - -/** - * @class XdgWm - * - * @brief XdgWm represents a window manager for a Wayland-based display. - * - * The XdgWm class is responsible for managing application windows using the XDG Shell protocol. - */ -XdgWm::XdgWm(struct wl_display *display, struct wl_surface *base_surface) : wl_surface_(base_surface) { - wl_registry_ = wl_display_get_registry(display); - wl_registry_add_listener(wl_registry_, ®istry_listener_, this); - - // enables blocking caller until set false - wait_for_configure_ = true; -} - -/** - * @class XdgWm - * - * @brief The XdgWm class represents a Wayland shell window manager. - * - * The XdgWm class manages the creation, destruction, configuration, and behavior of a Wayland shell window manager. - * - * It is responsible for handling interactions with the Wayland registry, creating and destroying the window manager base, - * surface, and toplevel objects, and implementing the necessary event handling functions. - */ -XdgWm::~XdgWm() { - if (wl_registry_) - wl_registry_destroy(wl_registry_); - - if (xdg_toplevel_) - xdg_toplevel_destroy(xdg_toplevel_); - - if (xdg_surface_) - xdg_surface_destroy(xdg_surface_); - - if (xdg_wm_base_) - xdg_wm_base_destroy(xdg_wm_base_); -} - -/** - * @brief Handles global registry events. - * - * This function is called when a new global object is added or removed from the registry. - * If the added global object is of type xdg_wm_base, it sets up the necessary listeners - * and binds the object to the XdgWm instance. - * - * @param data The user data associated with the callback (XdgWm instance). - * @param registry The registry object. - * @param name The name of the global object. - * @param interface The interface name of the global object. - * @param version The version of the global object. - */ -void XdgWm::registry_handle_global(void *data, - struct wl_registry *registry, - uint32_t name, - const char *interface, - uint32_t version) { - - if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - const auto obj = static_cast(data); - - obj->xdg_wm_base_ = static_cast( - wl_registry_bind(registry, name, &xdg_wm_base_interface, - std::min(static_cast(3), version))); - xdg_wm_base_add_listener(obj->xdg_wm_base_, &xdg_wm_base_listener_, obj); - - obj->xdg_surface_ = xdg_wm_base_get_xdg_surface(obj->xdg_wm_base_, obj->wl_surface_); - xdg_surface_add_listener(obj->xdg_surface_, &xdg_surface_listener_, obj); - - obj->xdg_toplevel_ = xdg_surface_get_toplevel(obj->xdg_surface_); - xdg_toplevel_add_listener(obj->xdg_toplevel_, &xdg_toplevel_listener_, obj); - - xdg_toplevel_set_title(obj->xdg_toplevel_, "waypp"); - xdg_toplevel_set_app_id(obj->xdg_toplevel_, "waypp"); - - wl_surface_commit(obj->wl_surface_); - } -} - -/** - * @brief Handles the removal of a global from the registry. - * - * This function is a callback that is triggered when a global object is - * removed from the registry. It does not return anything. - * - * @param data A pointer to user-defined data. - * @param reg A pointer to the registry that triggered the event. - * @param id The ID of the removed global. - */ -void XdgWm::registry_handle_global_remove(void * /* data */, - struct wl_registry * /* reg */, - uint32_t /* id */) { -} - -const struct wl_registry_listener XdgWm::registry_listener_ = { - registry_handle_global, - registry_handle_global_remove, -}; - -/** - * @class XdgWm - * - * The XdgWm class represents a window manager for XDG surfaces. - */ -void XdgWm::xdg_wm_base_ping(void * /* data */, - struct xdg_wm_base *xdg_wm_base, - uint32_t serial) { - std::cout << "XdgWm::xdg_wm_base_ping" << std::endl; - xdg_wm_base_pong(xdg_wm_base, serial); -} - -const struct xdg_wm_base_listener XdgWm::xdg_wm_base_listener_ = { - .ping = xdg_wm_base_ping, -}; - -/** - * @brief Handles the configure event for xdg_surface. - * - * This function is a member function of the XdgWm class. It is called when the xdg_surface - * sends a configure event. It acknowledges the configure request by calling xdg_surface_ack_configure(). - * It also sets the wait_for_configure_ variable to false. - * - * @param data A pointer to the XdgWm instance. - * @param xdg_surface A pointer to the xdg_surface instance. - * @param serial The serial number of the configure event. - */ -void XdgWm::handle_xdg_surface_configure( - void *data, - struct xdg_surface *xdg_surface, - uint32_t serial) { - auto *w = static_cast(data); - xdg_surface_ack_configure(xdg_surface, serial); - w->wait_for_configure_ = false; -} - -const struct xdg_surface_listener XdgWm::xdg_surface_listener_ = { - .configure = handle_xdg_surface_configure}; - -/** - * @brief Handles the configure event for a toplevel surface. - * - * This function is called when the configure event is received for a toplevel surface. - * It updates the internal state of the XdgWm object based on the configuration properties - * received from the compositor. - * - * @param data The user data passed to the callback. - * @param toplevel The toplevel surface that triggered the event. - * @param width The width of the surface. - * @param height The height of the surface. - * @param states An array of states associated with the surface. - */ -void XdgWm::handle_toplevel_configure( - void *data, - struct xdg_toplevel * /* toplevel */, - int32_t width, - int32_t height, - struct wl_array *states) { - - if (width == 0 || height == 0) { - // Compositor is deferring to us - return; - } - - auto *w = static_cast(data); - - w->fullscreen_ = false; - w->maximized_ = false; - w->resize_ = false; - w->activated_ = false; - - const uint32_t *state; - WL_ARRAY_FOR_EACH(state, states, const uint32_t*) { - switch (*state) { - case XDG_TOPLEVEL_STATE_FULLSCREEN: - std::cout << "XDG_TOPLEVEL_STATE_FULLSCREEN" << std::endl; - w->fullscreen_ = true; - break; - case XDG_TOPLEVEL_STATE_MAXIMIZED: - std::cout << "XDG_TOPLEVEL_STATE_MAXIMIZED" << std::endl; - w->maximized_ = true; - break; - case XDG_TOPLEVEL_STATE_RESIZING: - std::cout << "XDG_TOPLEVEL_STATE_RESIZING" << std::endl; - w->resize_ = true; - break; - case XDG_TOPLEVEL_STATE_ACTIVATED: - std::cout << "XDG_TOPLEVEL_STATE_ACTIVATED" << std::endl; - w->activated_ = true; - break; - } - } - - if (width > 0 && height > 0) { - if (!w->fullscreen_ && !w->maximized_) { - w->window_size_.width = width; - w->window_size_.height = height; - } - w->geometry_.width = width; - w->geometry_.height = height; - - } else if (!w->fullscreen_ && !w->maximized_) { - w->geometry_.width = w->window_size_.width; - w->geometry_.height = w->window_size_.height; - } - std::cout << "width: " << width << std::endl; - std::cout << "height: " << height << std::endl; -} - -/** - * @brief Handles the close event of a toplevel window. - * - * This function is a callback that is called when the user requests to close - * the toplevel window. It sets the `running_` member variable to false, which - * will cause the main event loop to exit. - * - * @param data The user data associated with the XdgWm instance. - * @param xdg_toplevel The xdg_toplevel object that received the close request. - */ -void XdgWm::handle_toplevel_close( - void *data, - struct xdg_toplevel * /* xdg_toplevel */) { - std::cout << "XdgWm::handle_toplevel_close" << std::endl; - - auto *w = static_cast(data); - w->running_ = false; -} - -const struct xdg_toplevel_listener XdgWm::xdg_toplevel_listener_ = { - handle_toplevel_configure, - handle_toplevel_close, -}; - -/** -* -*/ -void XdgWm::toplevel_resize(int x, int y, int width, int height, int padding) { - - const bool top = y < padding; - const bool bottom = y > (height - padding); - const bool left = x < padding; - const bool right = x > (width - padding); - - auto edge = XDG_TOPLEVEL_RESIZE_EDGE_NONE; - - if (top) - if (right) - edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; - else if (left) - edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; - else - edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP; - else if (bottom) - if (right) - edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; - else if (left) - edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; - else - edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; - else if (right) - edge = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; - else if (left) - edge = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; - else - edge = XDG_TOPLEVEL_RESIZE_EDGE_NONE; - - if (edge) { -#if 0 - xdg_toplevel_resize( - xdg_toplevel_, - wl_seat_, - 0, - edge); -#endif - } -} \ No newline at end of file diff --git a/src/window_manager/xdg_wm.h b/src/window_manager/xdg_wm.h deleted file mode 100644 index ff95baf..0000000 --- a/src/window_manager/xdg_wm.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2024 Joel Winarske - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_WINDOW_MANAGER_XDG_WM_H_ -#define SRC_WINDOW_MANAGER_XDG_WM_H_ - -#include -#include - -#include "xdg-shell-client-protocol.h" - -class XdgWm { -public: - XdgWm(struct wl_display *display, struct wl_surface *base_surface); - - ~XdgWm(); - - [[nodiscard]] const bool get_wait_for_configure() const { return wait_for_configure_; } - - void set_app_id(const char *app_id) { xdg_toplevel_set_app_id(xdg_toplevel_, app_id); } - - void set_title(const char *title) { xdg_toplevel_set_title(xdg_toplevel_, title); } - - void toplevel_resize(int x, int y, int width, int height, int padding); - -private: - struct wl_surface *wl_surface_; - struct wl_registry *wl_registry_; - struct xdg_wm_base *xdg_wm_base_{}; - struct xdg_surface *xdg_surface_{}; - struct xdg_toplevel *xdg_toplevel_{}; - - std::string app_id_; - - volatile bool wait_for_configure_{}; - - bool fullscreen_{}; - bool maximized_{}; - bool resize_{}; - bool activated_{}; - bool running_{}; - - struct { - int32_t width; - int32_t height; - } geometry_{}; - - struct { - int32_t width; - int32_t height; - } window_size_{}; - - static void handle_xdg_surface_configure( - void * /* data */, - struct xdg_surface * /* xdg_surface */, - uint32_t /* serial */); - - static const struct xdg_surface_listener xdg_surface_listener_; - - static void handle_toplevel_configure( - void * /* data */, - struct xdg_toplevel * /* toplevel */, - int32_t /* width */, - int32_t /* height */, - struct wl_array * /* states */); - - static void handle_toplevel_close( - void * /* data */, - struct xdg_toplevel * /* xdg_toplevel */); - - static const struct xdg_toplevel_listener xdg_toplevel_listener_; - - static void registry_handle_global(void *data, - struct wl_registry *registry, - uint32_t name, - const char *interface, - uint32_t version); - - static void registry_handle_global_remove(void *data, - struct wl_registry *reg, - uint32_t id); - - static const struct wl_registry_listener registry_listener_; - - static void xdg_wm_base_ping(void *data, - struct xdg_wm_base *xdg_wm_base, - uint32_t serial); - - static const struct xdg_wm_base_listener xdg_wm_base_listener_; - -}; - -#endif // SRC_WINDOW_MANAGER_XDG_WM_H_ \ No newline at end of file diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index e69de29..22ad600 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# cxxopts +# +add_subdirectory(cxxopts-3.2.1) + +# +# Speedlog +# +set(SPDLOG_NO_EXCEPTIONS ON) +set(SPDLOG_NO_THREAD_ID ON) +# set(SPDLOG_BUILD_PIC ON) +set(SPDLOG_SANITIZE_ADDRESS ${SANITIZE_ADDRESS}) +add_library(spdlog INTERFACE) +target_include_directories(spdlog INTERFACE spdlog-1.13.0/include) diff --git a/third_party/cxxopts-3.2.1/BUILD.bazel b/third_party/cxxopts-3.2.1/BUILD.bazel new file mode 100644 index 0000000..a6cd021 --- /dev/null +++ b/third_party/cxxopts-3.2.1/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "cxxopts", + hdrs = ["include/cxxopts.hpp"], + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) + +load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test") + +cc_fuzz_test( + name = "cxxopts_fuzz_test", + srcs = ["test/fuzz.cpp"], + deps = [":cxxopts"], +) \ No newline at end of file diff --git a/third_party/cxxopts-3.2.1/CHANGELOG.md b/third_party/cxxopts-3.2.1/CHANGELOG.md new file mode 100644 index 0000000..0c76486 --- /dev/null +++ b/third_party/cxxopts-3.2.1/CHANGELOG.md @@ -0,0 +1,163 @@ +# Changelog + +This is the changelog for `cxxopts`, a C++11 library for parsing command line +options. The project adheres to semantic versioning. + +## 3.2.1 + +### Bug fixes + +* Fix compilation with optional on C++20. + +## 3.2 + +### Bug fixes + +* Fix unannotated fallthrough. +* Fix sign conversion with Unicode output. +* Don't initialize regex in static initialiser. +* Fix incorrect integer overflow checks. + +### Added + +* Add fuzzing to CI + +### Changed + +* Change quote output to '' to match Windows. +* Don't split positional arguments by the list delimiter. +* Order help groups by the order they were added. + +## 3.1.1 + +### Bug Fixes + +* Fixed version number in header. +* Fixed cast warning in Unicode function. + +## 3.1 + +### Added + +* Support for multiple long names for the same option (= multiple long aliases) +* Add a `program()` function to retrieve the program name. +* Added a .clang-format file. +* Added iterator and printing for a ParseResult. + +### Changed + +* Cleanup exception code, add cxxopts::exceptions namespace. +* Renamed several exceptions to be more descriptive, and added to a nested namespace. + +### Bug Fixes + +* Fix `arguments()` having no key for options that only have a short name. + +## 3.0 + +### Changed + +* Only search for a C++ compiler in CMakeLists.txt. +* Allow for exceptions to be disabled. +* Fix duplicate default options when there is a short and long option. +* Add `CXXOPTS_NO_EXCEPTIONS` to disable exceptions. +* Fix char parsing for space and check for length. +* Change argument type in `Options::parse` from `char**` to `const char**`. +* Refactor parser to not change its arguments. +* `ParseResult` doesn't depend on a reference to the parser. +* Fixed several warnings and code quality issues. +* Improved formatting for help descriptions. +* Improve integer parsing. + +### Added + +* A list of unmatched arguments is available in `ParseResult`. +* Support single letter options with argument attached. +* Use if it is present. + +### Bug Fixes + +* Fix missing option name in exception. + +## 2.2 + +### Changed + +* Allow integers to have leading zeroes. +* Build the tests by default. +* Don't check for container when showing positional help. + +### Added + +* Iterator inputs to `parse_positional`. +* Throw an exception if the option in `parse_positional` doesn't exist. +* Parse a delimited list in a single argument for vector options. +* Add an option to disable implicit value on booleans. + +### Bug Fixes + +* Fix a warning about possible loss of data. +* Fix version numbering in CMakeLists.txt +* Remove unused declaration of the undefined `ParseResult::get_option`. +* Throw on invalid option syntax when beginning with a `-`. +* Throw in `as` when option wasn't present. +* Fix catching exceptions by reference. +* Fix out of bounds errors parsing integers. + +## 2.1.1 + +### Bug Fixes + +* Revert the change adding `const` type for `argv`, because most users expect + to pass a non-const `argv` from `main`. + +## 2.1 + +### Changed + +* Options with implicit arguments now require the `--option=value` form if + they are to be specified with an option. This is to remove the ambiguity + when a positional argument could follow an option with an implicit value. + For example, `--foo value`, where `foo` has an implicit value, will be + parsed as `--foo=implicit` and a positional argument `value`. +* Boolean values are no longer special, but are just an option with a default + and implicit value. + +### Added + +* Added support for `std::optional` as a storage type. +* Allow the help string to be customised. +* Use `const` for the type in the `argv` parameter, since the contents of the + arguments is never modified. + +### Bug Fixes + +* Building against GCC 4.9 was broken due to overly strict shadow warnings. +* Fixed an ambiguous overload in the `parse_positional` function when an + `initializer_list` was directly passed. +* Fixed precedence in the Boolean value regex. + +## 2.0 + +### Changed + +* `Options::parse` returns a ParseResult rather than storing the parse + result internally. +* Options with default values now get counted as appearing once if they + were not specified by the user. + +### Added + +* A new `ParseResult` object that is the immutable result of parsing. It + responds to the same `count` and `operator[]` as `Options` of 1.x did. +* The function `ParseResult::arguments` returns a vector of the parsed + arguments to iterate through in the order they were provided. +* The symbol `cxxopts::version` for the version of the library. +* Booleans can be specified with various strings and explicitly set false. + +## 1.x + +The 1.x series was the first major version of the library, with release numbers +starting to follow semantic versioning, after 0.x being unstable. It never had +a changelog maintained for it. Releases mostly contained bug fixes, with the +occasional feature added. diff --git a/third_party/cxxopts-3.2.1/CMakeLists.txt b/third_party/cxxopts-3.2.1/CMakeLists.txt new file mode 100644 index 0000000..4c1a686 --- /dev/null +++ b/third_party/cxxopts-3.2.1/CMakeLists.txt @@ -0,0 +1,82 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +cmake_minimum_required(VERSION 3.1...3.19) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") +include(cxxopts) +set("PROJECT_DESCRIPTION" "A header-only lightweight C++ command line option parser") +set("PROJECT_HOMEPAGE_URL" "https://github.com/jarro2783/cxxopts") + +# Get the version of the library +cxxopts_getversion(VERSION) + +project(cxxopts + VERSION "${VERSION}" + LANGUAGES CXX +) + +# Must include after the project call due to GNUInstallDirs requiring a language be enabled (IE. CXX) +include(GNUInstallDirs) + +# Determine whether this is a standalone project or included by other projects +set(CXXOPTS_STANDALONE_PROJECT OFF) +if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(CXXOPTS_STANDALONE_PROJECT ON) +endif() + +# Establish the project options +option(CXXOPTS_BUILD_EXAMPLES "Set to ON to build examples" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_BUILD_TESTS "Set to ON to build tests" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_ENABLE_INSTALL "Generate the install target" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_ENABLE_WARNINGS "Add warnings to CMAKE_CXX_FLAGS" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_USE_UNICODE_HELP "Use ICU Unicode library" OFF) + +if (CXXOPTS_STANDALONE_PROJECT) + cxxopts_set_cxx_standard() +endif() + +if (CXXOPTS_ENABLE_WARNINGS) + cxxopts_enable_warnings() +endif() + +add_library(cxxopts INTERFACE) +add_library(cxxopts::cxxopts ALIAS cxxopts) +add_subdirectory(include) + +# Link against the ICU library when requested +if(CXXOPTS_USE_UNICODE_HELP) + cxxopts_use_unicode() +endif() + +# Install cxxopts when requested by the user +if (CXXOPTS_ENABLE_INSTALL) + cxxopts_install_logic() +endif() + +# Build examples when requested by the user +if (CXXOPTS_BUILD_EXAMPLES) + add_subdirectory(src) +endif() + +# Enable testing when requested by the user +if (CXXOPTS_BUILD_TESTS) + enable_testing() + add_subdirectory(test) +endif() diff --git a/third_party/cxxopts-3.2.1/INSTALL b/third_party/cxxopts-3.2.1/INSTALL new file mode 100644 index 0000000..de7b0a8 --- /dev/null +++ b/third_party/cxxopts-3.2.1/INSTALL @@ -0,0 +1,46 @@ +== System installation == + +This library is header only. So you can either copy `include/cxxopts.hpp` to `/usr/include` or `/usr/local/include`, or add `include` to your search path. + +== Building the examples and tests == + +It is preferable to build out of source. Make a build directory somewhere, and then +do the following, where `${CXXOPTS_DIR}` is the path that you checked out `cxxopts` +to: + + cmake ${CXXOPTS_DIR} + make + +You can use another build tool, such as ninja. + + cmake -G Ninja ${CXXOPTS_DIR} + ninja + + +To run the tests, you have to configure `cxxopts` with another flag: + cmake -D CXXOPTS_BUILD_TESTS=On ${CXXOPTS_DIR} + make + make test + +== Using cxxopts in tipi.build projects == + +`cxxopts` can be easily used in [tipi.build](https://tipi.build) projects simply by adding the following entry to your `.tipi/deps`: + +```json +{ + "jarro2783/cxxopts": { "@": "v3.0.0" } +} +``` + +To try this you can run the following command in `/src` (change the target name appropriately to `linux` or `macos` or `windows`): + +```bash +tipi . -t +./build/linux-cxx17/bin/test_package -v +``` + +To develop `cxxopts` using tipi run the following command at the root of the repository: + +```bash +tipi . -t --test all -v +``` diff --git a/third_party/cxxopts-3.2.1/LICENSE b/third_party/cxxopts-3.2.1/LICENSE new file mode 100644 index 0000000..324a203 --- /dev/null +++ b/third_party/cxxopts-3.2.1/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/third_party/cxxopts-3.2.1/README.md b/third_party/cxxopts-3.2.1/README.md new file mode 100644 index 0000000..603e435 --- /dev/null +++ b/third_party/cxxopts-3.2.1/README.md @@ -0,0 +1,277 @@ +[![Build Status](https://travis-ci.org/jarro2783/cxxopts.svg?branch=master)](https://travis-ci.org/jarro2783/cxxopts) + +# Release versions + +Note that `master` is generally a work in progress, and you probably want to use a +tagged release version. + +## Version 3 breaking changes + +If you have used version 2, there are a couple of breaking changes in version 3 +that you should be aware of. If you are new to `cxxopts` you can skip this +section. + +The parser no longer modifies its arguments, so you can pass a const `argc` and +`argv` and expect them not to be changed. + +The `ParseResult` object no longer depends on the parser. So it can be returned +from a scope outside the parser and still work. Now that the inputs are not +modified, `ParseResult` stores a list of the unmatched arguments. These are +retrieved like follows: + +```cpp +auto result = options.parse(argc, argv); +result.unmatched(); // get the unmatched arguments +``` + +# Quick start + +This is a lightweight C++ option parser library, supporting the standard GNU +style syntax for options. + +Options can be given as: + + --long + --long=argument + --long argument + -a + -ab + -abc argument + +where c takes an argument, but a and b do not. + +Additionally, anything after `--` will be parsed as a positional argument. + +## Basics + +```cpp +#include +``` + +Create a `cxxopts::Options` instance. + +```cpp +cxxopts::Options options("MyProgram", "One line description of MyProgram"); +``` + +Then use `add_options`. + +```cpp +options.add_options() + ("d,debug", "Enable debugging") // a bool parameter + ("i,integer", "Int param", cxxopts::value()) + ("f,file", "File name", cxxopts::value()) + ("v,verbose", "Verbose output", cxxopts::value()->default_value("false")) + ; +``` + +Options are declared with a long and an optional short option. A description +must be provided. The third argument is the value, if omitted it is boolean. +Any type can be given as long as it can be parsed, with operator>>. + +To parse the command line do: + +```cpp +auto result = options.parse(argc, argv); +``` + +To retrieve an option use `result.count("option")` to get the number of times +it appeared, and + +```cpp +result["opt"].as() +``` + +to get its value. If "opt" doesn't exist, or isn't of the right type, then an +exception will be thrown. + +## Unrecognised arguments + +You can allow unrecognised arguments to be skipped. This applies to both +positional arguments that are not parsed into another option, and `--` +arguments that do not match an argument that you specify. This is done by +calling: + +```cpp +options.allow_unrecognised_options(); +``` + +and in the result object they are retrieved with: + +```cpp +result.unmatched() +``` + +## Exceptions + +Exceptional situations throw C++ exceptions. There are two types of +exceptions: errors defining the options, and errors when parsing a list of +arguments. All exceptions derive from `cxxopts::exceptions::exception`. Errors +defining options derive from `cxxopts::exceptions::specification` and errors +parsing arguments derive from `cxxopts::exceptions::parsing`. + +All exceptions define a `what()` function to get a printable string +explaining the error. + +## Help groups + +Options can be placed into groups for the purposes of displaying help messages. +To place options in a group, pass the group as a string to `add_options`. Then, +when displaying the help, pass the groups that you would like displayed as a +vector to the `help` function. + +## Positional Arguments + +Positional arguments are those given without a preceding flag and can be used +alongside non-positional arguments. There may be multiple positional arguments, +and the final positional argument may be a container type to hold a list of all +remaining positionals. + +To set up positional arguments, first declare the options, then configure a +set of those arguments as positional like: + +```cpp +options.add_options() + ("script", "The script file to execute", cxxopts::value()) + ("server", "The server to execute on", cxxopts::value()) + ("filenames", "The filename(s) to process", cxxopts::value>()); + +options.parse_positional({"script", "server", "filenames"}); + +// Parse options the usual way +options.parse(argc, argv); +``` + +For example, parsing the following arguments: +~~~ +my_script.py my_server.com file1.txt file2.txt file3.txt +~~~ +will result in parsed arguments like the following table: + +| Field | Value | +| ------------- | ----------------------------------------- | +| `"script"` | `"my_script.py"` | +| `"server"` | `"my_server.com"` | +| `"filenames"` | `{"file1.txt", "file2.txt", "file3.txt"}` | + +## Default and implicit values + +An option can be declared with a default or an implicit value, or both. + +A default value is the value that an option takes when it is not specified +on the command line. The following specifies a default value for an option: + +```cpp +cxxopts::value()->default_value("value") +``` + +An implicit value is the value that an option takes when it is given on the +command line without an argument. The following specifies an implicit value: + +```cpp +cxxopts::value()->implicit_value("implicit") +``` + +If an option had both, then not specifying it would give the value `"value"`, +writing it on the command line as `--option` would give the value `"implicit"`, +and writing `--option=another` would give it the value `"another"`. + +Note that the default and implicit value is always stored as a string, +regardless of the type that you want to store it in. It will be parsed as +though it was given on the command line. + +Default values are not counted by `Options::count`. + +## Boolean values + +Boolean options have a default implicit value of `"true"`, which can be +overridden. The effect is that writing `-o` by itself will set option `o` to +`true`. However, they can also be written with various strings using `=value`. +There is no way to disambiguate positional arguments from the value following +a boolean, so we have chosen that they will be positional arguments, and +therefore, `-o false` does not work. + +## `std::vector` values + +Parsing a list of values into a `std::vector` is also supported, as long as `T` +can be parsed. To separate single values in a list the define symbol `CXXOPTS_VECTOR_DELIMITER` +is used, which is ',' by default. Ensure that you use no whitespaces between values because +those would be interpreted as the next command line option. Example for a command line option +that can be parsed as a `std::vector`: + +~~~ +--my_list=1,-2.1,3,4.5 +~~~ + +## Options specified multiple times + +The same option can be specified several times, with different arguments, which will all +be recorded in order of appearance. An example: + +~~~ +--use train --use bus --use ferry +~~~ + +this is supported through the use of a vector of value for the option: + +~~~ +options.add_options() + ("use", "Usable means of transport", cxxopts::value>()) +~~~ + +## Custom help + +The string after the program name on the first line of the help can be +completely replaced by calling `options.custom_help`. Note that you might +also want to override the positional help by calling `options.positional_help`. + + +## Example + +Putting all together: +```cpp +int main(int argc, char** argv) +{ + cxxopts::Options options("test", "A brief description"); + + options.add_options() + ("b,bar", "Param bar", cxxopts::value()) + ("d,debug", "Enable debugging", cxxopts::value()->default_value("false")) + ("f,foo", "Param foo", cxxopts::value()->default_value("10")) + ("h,help", "Print usage") + ; + + auto result = options.parse(argc, argv); + + if (result.count("help")) + { + std::cout << options.help() << std::endl; + exit(0); + } + bool debug = result["debug"].as(); + std::string bar; + if (result.count("bar")) + bar = result["bar"].as(); + int foo = result["foo"].as(); + + return 0; +} +``` + +# Linking + +This is a header only library. + +# Requirements + +The only build requirement is a C++ compiler that supports C++11 features such as: + +* regex +* constexpr +* default constructors + +GCC >= 4.9 or clang >= 3.1 with libc++ are known to work. + +The following compilers are known not to work: + +* MSVC 2013 diff --git a/third_party/cxxopts-3.2.1/WORKSPACE b/third_party/cxxopts-3.2.1/WORKSPACE new file mode 100644 index 0000000..6a0fa27 --- /dev/null +++ b/third_party/cxxopts-3.2.1/WORKSPACE @@ -0,0 +1,16 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "rules_fuzzing", + sha256 = "d9002dd3cd6437017f08593124fdd1b13b3473c7b929ceb0e60d317cb9346118", + strip_prefix = "rules_fuzzing-0.3.2", + urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.2.zip"], +) + +load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies") + +rules_fuzzing_dependencies() + +load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init") + +rules_fuzzing_init() diff --git a/third_party/cxxopts-3.2.1/cmake/cxxopts.cmake b/third_party/cxxopts-3.2.1/cmake/cxxopts.cmake new file mode 100644 index 0000000..0ead543 --- /dev/null +++ b/third_party/cxxopts-3.2.1/cmake/cxxopts.cmake @@ -0,0 +1,163 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +if (CMAKE_VERSION VERSION_GREATER 3.10 OR CMAKE_VERSION VERSION_EQUAL 3.10) + # Use include_guard() added in cmake 3.10 + include_guard() +endif() + +include(CMakePackageConfigHelpers) + +function(cxxopts_getversion version_arg) + # Parse the current version from the cxxopts header + file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/cxxopts.hpp" cxxopts_version_defines + REGEX "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH)") + foreach(ver ${cxxopts_version_defines}) + if(ver MATCHES "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$") + set(CXXOPTS__VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "") + endif() + endforeach() + set(VERSION ${CXXOPTS__VERSION_MAJOR}.${CXXOPTS__VERSION_MINOR}.${CXXOPTS__VERSION_PATCH}) + + # Give feedback to the user. Prefer DEBUG when available since large projects tend to have a lot + # going on already + if (CMAKE_VERSION VERSION_GREATER 3.15 OR CMAKE_VERSION VERSION_EQUAL 3.15) + message(DEBUG "cxxopts version ${VERSION}") + else() + message(STATUS "cxxopts version ${VERSION}") + endif() + + # Return the information to the caller + set(${version_arg} ${VERSION} PARENT_SCOPE) +endfunction() + +# Optionally, enable unicode support using the ICU library +function(cxxopts_use_unicode) + find_package(PkgConfig) + pkg_check_modules(ICU REQUIRED icu-uc) + + target_link_libraries(cxxopts INTERFACE ${ICU_LDFLAGS}) + target_compile_options(cxxopts INTERFACE ${ICU_CFLAGS}) + target_compile_definitions(cxxopts INTERFACE CXXOPTS_USE_UNICODE) +endfunction() + +# Request C++11 without gnu extension for the whole project and enable more warnings +macro(cxxopts_set_cxx_standard) + if (CXXOPTS_CXX_STANDARD) + set(CMAKE_CXX_STANDARD ${CXXOPTS_CXX_STANDARD}) + else() + set(CMAKE_CXX_STANDARD 11) + endif() + + set(CMAKE_CXX_EXTENSIONS OFF) +endmacro() + +# Helper function to enable warnings +function(cxxopts_enable_warnings) + if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W2") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if (${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER_EQUAL 5.0) + set(COMPILER_SPECIFIC_FLAGS "-Wsuggest-override") + endif() + endif() + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wextra -Wshadow -Weffc++ -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion ${COMPILER_SPECIFIC_FLAGS}") + endif() + + set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} PARENT_SCOPE) +endfunction() + +# Helper function to ecapsulate install logic +function(cxxopts_install_logic) + if(CMAKE_LIBRARY_ARCHITECTURE) + string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") + else() + # On some systems (e.g. NixOS), `CMAKE_LIBRARY_ARCHITECTURE` can be empty + set(CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") + endif() + set(CXXOPTS_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/cxxopts" CACHE STRING "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") + set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") + set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") + set(targets_export_name cxxopts-targets) + set(PackagingTemplatesDir "${PROJECT_SOURCE_DIR}/packaging") + + + if(${CMAKE_VERSION} VERSION_GREATER "3.14") + set(OPTIONAL_ARCH_INDEPENDENT "ARCH_INDEPENDENT") + endif() + + # Generate the version, config and target files into the build directory. + write_basic_package_version_file( + ${version_config} + VERSION ${VERSION} + COMPATIBILITY AnyNewerVersion + ${OPTIONAL_ARCH_INDEPENDENT} + ) + configure_package_config_file( + ${PackagingTemplatesDir}/cxxopts-config.cmake.in + ${project_config} + INSTALL_DESTINATION ${CXXOPTS_CMAKE_DIR}) + export(TARGETS cxxopts NAMESPACE cxxopts:: + FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) + + # Install version, config and target files. + install( + FILES ${project_config} ${version_config} + DESTINATION ${CXXOPTS_CMAKE_DIR}) + install(EXPORT ${targets_export_name} DESTINATION ${CXXOPTS_CMAKE_DIR} + NAMESPACE cxxopts::) + + # Install the header file and export the target + install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") + set(CPACK_PACKAGE_VENDOR "cxxopt developers") + set(CPACK_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}") + set(CPACK_DEBIAN_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_RPM_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_PACKAGE_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}") + set(CPACK_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR}") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_MAINTAINER}") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + + set(CPACK_DEBIAN_PACKAGE_NAME "lib${PROJECT_NAME}-dev") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6-dev") + set(CPACK_DEBIAN_PACKAGE_SUGGESTS "cmake, pkg-config, pkg-conf") + + set(CPACK_RPM_PACKAGE_NAME "lib${PROJECT_NAME}-devel") + set(CPACK_RPM_PACKAGE_SUGGESTS "${CPACK_DEBIAN_PACKAGE_SUGGESTS}") + + set(CPACK_DEB_COMPONENT_INSTALL ON) + set(CPACK_RPM_COMPONENT_INSTALL ON) + set(CPACK_NSIS_COMPONENT_INSTALL ON) + set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") + + set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc") + configure_file("${PackagingTemplatesDir}/pkgconfig.pc.in" "${PKG_CONFIG_FILE_NAME}" @ONLY) + install(FILES "${PKG_CONFIG_FILE_NAME}" + DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/pkgconfig" + ) + + include(CPack) +endfunction() diff --git a/third_party/cxxopts-3.2.1/include/CMakeLists.txt b/third_party/cxxopts-3.2.1/include/CMakeLists.txt new file mode 100644 index 0000000..1123c5a --- /dev/null +++ b/third_party/cxxopts-3.2.1/include/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +target_include_directories(cxxopts INTERFACE + $ + $ +) diff --git a/third_party/cxxopts-3.2.1/include/cxxopts.hpp b/third_party/cxxopts-3.2.1/include/cxxopts.hpp new file mode 100644 index 0000000..0b272ac --- /dev/null +++ b/third_party/cxxopts-3.2.1/include/cxxopts.hpp @@ -0,0 +1,2878 @@ +/* + +Copyright (c) 2014-2022 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// vim: ts=2:sw=2:expandtab + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CXXOPTS_NO_EXCEPTIONS +#include +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif +#if defined(_MSC_VER) && !defined(__clang__) +#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern +#define CXXOPTS_LINKONCE __declspec(selectany) extern +#else +#define CXXOPTS_LINKONCE_CONST +#define CXXOPTS_LINKONCE +#endif + +#ifndef CXXOPTS_NO_REGEX +# include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif +#endif + +#define CXXOPTS_FALLTHROUGH +#ifdef __has_cpp_attribute + #if __has_cpp_attribute(fallthrough) + #undef CXXOPTS_FALLTHROUGH + #define CXXOPTS_FALLTHROUGH [[fallthrough]] + #endif +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 1 + +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 + #define CXXOPTS_NULL_DEREF_IGNORE +#endif + +#if defined(__GNUC__) +#define DO_PRAGMA(x) _Pragma(#x) +#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) +#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) +#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) +#else +// define other compilers here if needed +#define CXXOPTS_DIAGNOSTIC_PUSH +#define CXXOPTS_DIAGNOSTIC_POP +#define CXXOPTS_IGNORE_WARNING(x) +#endif + +#ifdef CXXOPTS_NO_RTTI +#define CXXOPTS_RTTI_CAST static_cast +#else +#define CXXOPTS_RTTI_CAST dynamic_cast +#endif + +namespace cxxopts { +static constexpr struct { + uint8_t major, minor, patch; +} version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH +}; +} // namespace cxxopts + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts { + +using String = icu::UnicodeString; + +inline +String +toLocalString(std::string s) +{ + return icu::UnicodeString::fromUTF8(std::move(s)); +} + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") +// This will be ignored under other compilers like LLVM clang. +class UnicodeStringIterator +{ + public: + + using iterator_category = std::forward_iterator_tag; + using value_type = int32_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; +}; +CXXOPTS_DIAGNOSTIC_POP + +inline +String& +stringAppend(String&s, String a) +{ + return s.append(std::move(a)); +} + +inline +String& +stringAppend(String& s, std::size_t n, UChar32 c) +{ + for (std::size_t i = 0; i != n; ++i) + { + s.append(c); + } + + return s; +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; +} + +inline +size_t +stringLength(const String& s) +{ + return static_cast(s.length()); +} + +inline +std::string +toUTF8String(const String& s) +{ + std::string result; + s.toUTF8String(result); + + return result; +} + +inline +bool +empty(const String& s) +{ + return s.isEmpty(); +} + +} // namespace cxxopts + +namespace std { + +inline +cxxopts::UnicodeStringIterator +begin(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, 0); +} + +inline +cxxopts::UnicodeStringIterator +end(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, s.length()); +} + +} // namespace std + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts { + +using String = std::string; + +template +T +toLocalString(T&& t) +{ + return std::forward(t); +} + +inline +std::size_t +stringLength(const String& s) +{ + return s.length(); +} + +inline +String& +stringAppend(String&s, const String& a) +{ + return s.append(a); +} + +inline +String& +stringAppend(String& s, std::size_t n, char c) +{ + return s.append(n, c); +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + return s.append(begin, end); +} + +template +std::string +toUTF8String(T&& t) +{ + return std::forward(t); +} + +inline +bool +empty(const std::string& s) +{ + return s.empty(); +} + +} // namespace cxxopts + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts { + +namespace { +CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); +CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); +} // namespace + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor This will be ignored under other compilers like LLVM clang. +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") + +// some older versions of GCC warn under this warning +CXXOPTS_IGNORE_WARNING("-Weffc++") +class Value : public std::enable_shared_from_this +{ + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + add(const std::string& text) const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; +}; + +CXXOPTS_DIAGNOSTIC_POP + +namespace exceptions { + +class exception : public std::exception +{ + public: + explicit exception(std::string message) + : m_message(std::move(message)) + { + } + + CXXOPTS_NODISCARD + const char* + what() const noexcept override + { + return m_message.c_str(); + } + + private: + std::string m_message; +}; + +class specification : public exception +{ + public: + + explicit specification(const std::string& message) + : exception(message) + { + } +}; + +class parsing : public exception +{ + public: + explicit parsing(const std::string& message) + : exception(message) + { + } +}; + +class option_already_exists : public specification +{ + public: + explicit option_already_exists(const std::string& option) + : specification("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } +}; + +class invalid_option_format : public specification +{ + public: + explicit invalid_option_format(const std::string& format) + : specification("Invalid option format " + LQUOTE + format + RQUOTE) + { + } +}; + +class invalid_option_syntax : public parsing { + public: + explicit invalid_option_syntax(const std::string& text) + : parsing("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } +}; + +class no_such_option : public parsing +{ + public: + explicit no_such_option(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } +}; + +class missing_argument : public parsing +{ + public: + explicit missing_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } +}; + +class option_requires_argument : public parsing +{ + public: + explicit option_requires_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } +}; + +class gratuitous_argument_for_option : public parsing +{ + public: + gratuitous_argument_for_option + ( + const std::string& option, + const std::string& arg + ) + : parsing( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } +}; + +class requested_option_not_present : public parsing +{ + public: + explicit requested_option_not_present(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " not present") + { + } +}; + +class option_has_no_value : public exception +{ + public: + explicit option_has_no_value(const std::string& option) + : exception( + !option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } +}; + +class incorrect_argument_type : public parsing +{ + public: + explicit incorrect_argument_type + ( + const std::string& arg + ) + : parsing( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } +}; + +} // namespace exceptions + + +template +void throw_or_mimic(const std::string& text) +{ + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif +} + +using OptionNames = std::vector; + +namespace values { + +namespace parser_tool { + +struct IntegerDesc +{ + std::string negative = ""; + std::string base = ""; + std::string value = ""; +}; +struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; +}; + +#ifdef CXXOPTS_NO_REGEX +inline IntegerDesc SplitInteger(const std::string &text) +{ + if (text.empty()) + { + throw_or_mimic(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic(text); + } + return desc; +} + +inline bool IsTrueText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; +} + +inline bool IsFalseText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; +} + +inline OptionNames split_option_names(const std::string &text) +{ + OptionNames split_names; + + std::string::size_type token_start_pos = 0; + auto length = text.length(); + + if (length == 0) + { + throw_or_mimic(text); + } + + while (token_start_pos < length) { + const auto &npos = std::string::npos; + auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); + if (next_non_space_pos == npos) { + throw_or_mimic(text); + } + token_start_pos = next_non_space_pos; + auto next_delimiter_pos = text.find(',', token_start_pos); + if (next_delimiter_pos == token_start_pos) { + throw_or_mimic(text); + } + if (next_delimiter_pos == npos) { + next_delimiter_pos = length; + } + auto token_length = next_delimiter_pos - token_start_pos; + // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ + { + const char* option_name_valid_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-.?"; + + if (!std::isalnum(text[token_start_pos], std::locale::classic()) || + text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) { + throw_or_mimic(text); + } + } + split_names.emplace_back(text.substr(token_start_pos, token_length)); + token_start_pos = next_delimiter_pos + 1; + } + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; +} + +#else // CXXOPTS_NO_REGEX + +namespace { +CXXOPTS_LINKONCE +const char* const integer_pattern = + "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; +CXXOPTS_LINKONCE +const char* const truthy_pattern = + "(t|T)(rue)?|1"; +CXXOPTS_LINKONCE +const char* const falsy_pattern = + "(f|F)(alse)?|0"; +CXXOPTS_LINKONCE +const char* const option_pattern = + "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; +CXXOPTS_LINKONCE +const char* const option_specifier_pattern = + "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; +CXXOPTS_LINKONCE +const char* const option_specifier_separator_pattern = ", *"; + +} // namespace + +inline IntegerDesc SplitInteger(const std::string &text) +{ + static const std::basic_regex integer_matcher(integer_pattern); + + std::smatch match; + std::regex_match(text, match, integer_matcher); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; +} + +inline bool IsTrueText(const std::string &text) +{ + static const std::basic_regex truthy_matcher(truthy_pattern); + std::smatch result; + std::regex_match(text, result, truthy_matcher); + return !result.empty(); +} + +inline bool IsFalseText(const std::string &text) +{ + static const std::basic_regex falsy_matcher(falsy_pattern); + std::smatch result; + std::regex_match(text, result, falsy_matcher); + return !result.empty(); +} + +// Gets the option names specified via a single, comma-separated string, +// and returns the separate, space-discarded, non-empty names +// (without considering which or how many are single-character) +inline OptionNames split_option_names(const std::string &text) +{ + static const std::basic_regex option_specifier_matcher(option_specifier_pattern); + if (!std::regex_match(text.c_str(), option_specifier_matcher)) + { + throw_or_mimic(text); + } + + OptionNames split_names; + + static const std::basic_regex option_specifier_separator_matcher(option_specifier_separator_pattern); + constexpr int use_non_matches { -1 }; + auto token_iterator = std::sregex_token_iterator( + text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches); + std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + static const std::basic_regex option_matcher(option_pattern); + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; +} + +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX +} // namespace parser_tool + +namespace detail { + +template +struct SignedCheck; + +template +struct SignedCheck +{ + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } +}; + +template +struct SignedCheck +{ + template + void + operator()(bool, U, const std::string&) const {} +}; + +template +void +check_signed_range(bool negative, U value, const std::string& text) +{ + SignedCheck::is_signed>()(negative, value, text); +} + +} // namespace detail + +template +void +checked_negate(R& r, T&& t, const std::string&, std::true_type) +{ + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t-1)-1); +} + +template +void +checked_negate(R&, T&&, const std::string& text, std::false_type) +{ + throw_or_mimic(text); +} + +template +void +integer_parser(const std::string& text, T& value) +{ + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); + + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::is_signed; + + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; + + US result = 0; + + for (char ch : value_match) + { + US digit = 0; + + if (ch >= '0' && ch <= '9') + { + digit = static_cast(ch - '0'); + } + else if (base == 16 && ch >= 'a' && ch <= 'f') + { + digit = static_cast(ch - 'a' + 10); + } + else if (base == 16 && ch >= 'A' && ch <= 'F') + { + digit = static_cast(ch - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + US limit = 0; + if (negative) + { + limit = static_cast(std::abs(static_cast(std::numeric_limits::min()))); + } + else + { + limit = std::numeric_limits::max(); + } + + if (base != 0 && result > limit / base) + { + throw_or_mimic(text); + } + if (result * base > limit - digit) + { + throw_or_mimic(text); + } + + result = static_cast(result * base + digit); + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + checked_negate(value, result, text, std::integral_constant()); + } + else + { + value = static_cast(result); + } +} + +template +void stringstream_parser(const std::string& text, T& value) +{ + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } +} + +template ::value>::type* = nullptr + > +void parse_value(const std::string& text, T& value) +{ + integer_parser(text, value); +} + +inline +void +parse_value(const std::string& text, bool& value) +{ + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } + + if (parser_tool::IsFalseText(text)) + { + value = false; + return; + } + + throw_or_mimic(text); +} + +inline +void +parse_value(const std::string& text, std::string& value) +{ + value = text; +} + +// The fallback parser. It uses the stringstream parser to parse all types +// that have not been overloaded explicitly. It has to be placed in the +// source code before all other more specialized templates. +template ::value>::type* = nullptr + > +void +parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); +} + +#ifdef CXXOPTS_HAS_OPTIONAL +template +void +parse_value(const std::string& text, std::optional& value) +{ + T result; + parse_value(text, result); + value = std::move(result); +} +#endif + +inline +void parse_value(const std::string& text, char& c) +{ + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; +} + +template +void +parse_value(const std::string& text, std::vector& value) +{ + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } +} + +template +void +add_value(const std::string& text, T& value) +{ + parse_value(text, value); +} + +template +void +add_value(const std::string& text, std::vector& value) +{ + T v; + add_value(text, v); + value.emplace_back(std::move(v)); +} + +template +struct type_is_container +{ + static constexpr bool value = false; +}; + +template +struct type_is_container> +{ + static constexpr bool value = true; +}; + +template +class abstract_value : public Value +{ + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + add(const std::string& text) const override + { + add_value(text, *m_store); + } + + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } + + bool + is_container() const override + { + return type_is_container::value; + } + + void + parse() const override + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const override + { + return m_default; + } + + bool + has_implicit() const override + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const override + { + return m_default_value; + } + + std::string + get_implicit_value() const override + { + return m_implicit_value; + } + + bool + is_boolean() const override + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr m_result{}; + T* m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; +}; + +template +class standard_value : public abstract_value +{ + public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } +}; + +template <> +class standard_value : public abstract_value +{ + public: + ~standard_value() override = default; + + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + m_implicit = true; + m_implicit_value = "true"; + } + + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } +}; + +} // namespace values + +template +std::shared_ptr +value() +{ + return std::make_shared>(); +} + +template +std::shared_ptr +value(T& t) +{ + return std::make_shared>(&t); +} + +class OptionAdder; + +CXXOPTS_NODISCARD +inline +const std::string& +first_or_empty(const OptionNames& long_names) +{ + static const std::string empty{""}; + return long_names.empty() ? empty : long_names.front(); +} + +class OptionDetails +{ + public: + OptionDetails + ( + std::string short_, + OptionNames long_, + String desc, + std::shared_ptr val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) + { + m_hash = std::hash{}(first_long_name() + m_short); + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } + + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } + + CXXOPTS_NODISCARD + const std::string& + first_long_name() const + { + return first_or_empty(m_long); + } + + CXXOPTS_NODISCARD + const std::string& + essential_name() const + { + return m_long.empty() ? m_short : m_long.front(); + } + + CXXOPTS_NODISCARD + const OptionNames & + long_names() const + { + return m_long; + } + + std::size_t + hash() const + { + return m_hash; + } + + private: + std::string m_short{}; + OptionNames m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; + int m_count; + + std::size_t m_hash{}; +}; + +struct HelpOptionDetails +{ + std::string s; + OptionNames l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; +}; + +struct HelpGroupDetails +{ + std::string name{}; + std::string description{}; + std::vector options{}; +}; + +class OptionValue +{ + public: + void + add + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->add(text); + m_long_names = &details->long_names(); + } + + void + parse + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_names = &details->long_names(); + } + + void + parse_default(const std::shared_ptr& details) + { + ensure_value(details); + m_default = true; + m_long_names = &details->long_names(); + m_value->parse(); + } + + void + parse_no_value(const std::shared_ptr& details) + { + m_long_names = &details->long_names(); + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnull-dereference") +#endif + + CXXOPTS_NODISCARD + std::size_t + count() const noexcept + { + return m_count; + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_POP +#endif + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic( + m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); + } + + return CXXOPTS_RTTI_CAST&>(*m_value).get(); + } + + private: + void + ensure_value(const std::shared_ptr& details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + + const OptionNames * m_long_names = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr m_value{}; + std::size_t m_count = 0; + bool m_default = false; +}; + +class KeyValue +{ + public: + KeyValue(std::string key_, std::string value_) noexcept + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + CXXOPTS_NODISCARD + const std::string& + key() const + { + return m_key; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; +}; + +using ParsedHashMap = std::unordered_map; +using NameHashMap = std::unordered_map; + +class ParseResult +{ + public: + class Iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = KeyValue; + using difference_type = void; + using pointer = const KeyValue*; + using reference = const KeyValue&; + + Iterator() = default; + Iterator(const Iterator&) = default; + +// GCC complains about m_iter not being initialised in the member +// initializer list +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Weffc++") + Iterator(const ParseResult *pr, bool end=false) + : m_pr(pr) + { + if (end) + { + m_sequential = false; + m_iter = m_pr->m_defaults.end(); + } + else + { + m_sequential = true; + m_iter = m_pr->m_sequential.begin(); + + if (m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + } + } + } +CXXOPTS_DIAGNOSTIC_POP + + Iterator& operator++() + { + ++m_iter; + if(m_sequential && m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + return *this; + } + return *this; + } + + Iterator operator++(int) + { + Iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(const Iterator& other) const + { + return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); + } + + bool operator!=(const Iterator& other) const + { + return !(*this == other); + } + + const KeyValue& operator*() + { + return *m_iter; + } + + const KeyValue* operator->() + { + return m_iter.operator->(); + } + + private: + const ParseResult* m_pr; + std::vector::const_iterator m_iter; + bool m_sequential = true; + }; + + ParseResult() = default; + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, + std::vector default_opts, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_defaults(std::move(default_opts)) + , m_unmatched(std::move(unmatched_args)) + { + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; + + Iterator + begin() const + { + return Iterator(this); + } + + Iterator + end() const + { + return Iterator(this, true); + } + + std::size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) + { + return 0; + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + return 0; + } + + return viter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); + + if (iter == m_keys.end()) + { + throw_or_mimic(option); + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + throw_or_mimic(option); + } + + return viter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + const std::vector& + unmatched() const + { + return m_unmatched; + } + + const std::vector& + defaults() const + { + return m_defaults; + } + + const std::string + arguments_string() const + { + std::string result; + for(const auto& kv: m_sequential) + { + result += kv.key() + " = " + kv.value() + "\n"; + } + for(const auto& kv: m_defaults) + { + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; + } + return result; + } + + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_defaults{}; + std::vector m_unmatched{}; +}; + +struct Option +{ + Option + ( + std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) + { + } + + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; +}; + +using OptionMap = std::unordered_map>; +using PositionalList = std::vector; +using PositionalListIterator = PositionalList::const_iterator; + +class OptionParser +{ + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + void + add_to_option(const std::shared_ptr& value, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + parse_no_value(const std::shared_ptr& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector m_sequential{}; + std::vector m_defaults{}; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; +}; + +class Options +{ + public: + + explicit Options(std::string program_name, std::string help_string = "") + : m_program(std::move(program_name)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_width(76) + , m_tab_expansion(false) + , m_options(std::make_shared()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + Options& + set_width(std::size_t width) + { + m_width = width; + return *this; + } + + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } + + ParseResult + parse(int argc, const char* const* argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list