diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 0000000..9ec0432 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,75 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v3 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} diff --git a/CMakeLists.txt b/CMakeLists.txt index cc399fb..eb81d6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,3 @@ -# Project Info cmake_minimum_required(VERSION 3.18 FATAL_ERROR) cmake_policy(VERSION 3.18) project(CrossWindow) @@ -7,10 +6,10 @@ enable_language(CXX) # CMake Settings set(CMAKE_SUPPRESS_REGENERATION true) -set(DCMAKE_GENERATOR_PLATFORM "x64") -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") # ============================================================= @@ -22,20 +21,44 @@ set_property( STRINGS AUTO WIN32 UWP COCOA UIKIT XCB XLIB MIR WAYLAND ANDROID WASM NOOP ) -set(XWIN_OS AUTO CACHE STRING "Optional: Choose the OS to build for, defaults to AUTO, but can be WINDOWS, MACOS, LINUX, ANDROID, IOS, WASM.") +set(XWIN_OS AUTO CACHE STRING "Optional: Choose the OS to build for, defaults to AUTO, but can be WINDOWS, MACOS, LINUX, ANDROID, IOS, WASM.") set_property( CACHE XWIN_OS PROPERTY STRINGS AUTO WINDOWS MACOS LINUX ANDROID IOS WASM NOOP ) +function(deduce_linux_display_server) + message( STATUS "XDG session type: " "$ENV{XDG_SESSION_TYPE}" ) + if ("$ENV{XDG_SESSION_TYPE}" STREQUAL "wayland") + set(XWIN_API "WAYLAND" PARENT_SCOPE) + elseif ("$ENV{XDG_SESSION_TYPE}" STREQUAL "x11") + set(XWIN_API "XCB" PARENT_SCOPE) + else() + execute_process ( + COMMAND bash -c "env | awk -F= '$2 ~ /wayland/ {count++} END {print count}'" + OUTPUT_VARIABLE WaylandCount + ) + execute_process ( + COMMAND bash -c "env | awk -F= '$2 ~ /x11/ {count++} END {print count}'" + OUTPUT_VARIABLE X11Count + ) + message(STATUS "Display server counts: " ${X11Count} " " ${WaylandCount}) + if (${X11Count} LESS ${WaylandCount}) + set(XWIN_API "WAYLAND" PARENT_SCOPE) + else() + set(XWIN_API "XCB" PARENT_SCOPE) + endif() + endif() +endfunction() + if( NOT (XWIN_OS STREQUAL "AUTO") AND XWIN_API STREQUAL "AUTO") if(XWIN_OS STREQUAL "WINDOWS") set(XWIN_API "WIN32") elseif(XWIN_OS STREQUAL "MACOS") set(XWIN_API "COCOA") elseif(XWIN_OS STREQUAL "LINUX") - set(XWIN_API "XLIB") + deduce_linux_display_server() elseif(XWIN_OS STREQUAL "ANDROID") set(XWIN_API "ANDROID") elseif(XWIN_OS STREQUAL "IOS") @@ -51,11 +74,11 @@ endif() if(XWIN_API STREQUAL "AUTO") if (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows") - set(XWIN_API "WIN32" CACHE STRING "A more specific platform selector to choose from, choose the exact OS protocol to use, can be WIN32, UWP, COCOA, UIKIT, XCB, XLIB, MIR, WAYLAND, ANDROID, WASM, NOOP." FORCE) + set(XWIN_API "WIN32" CACHE STRING "A more specific platform selector to choose from, choose the exact OS protocol to use, can be WIN32, UWP, COCOA, UIKIT, XCB, XLIB, MIR, WAYLAND, ANDROID, WASM, NOOP." FORCE) elseif (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Darwin") - set(XWIN_API "COCOA" CACHE STRING "A more specific platform selector to choose from, choose the exact OS protocol to use, can be WIN32, UWP, COCOA, UIKIT, XCB, XLIB, MIR, WAYLAND, ANDROID, WASM, NOOP." FORCE) + set(XWIN_API "COCOA" CACHE STRING "A more specific platform selector to choose from, choose the exact OS protocol to use, can be WIN32, UWP, COCOA, UIKIT, XCB, XLIB, MIR, WAYLAND, ANDROID, WASM, NOOP." FORCE) elseif (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Linux") - set(XWIN_API "XCB" CACHE STRING "A more specific platform selector to choose from, choose the exact OS protocol to use, can be WIN32, UWP, COCOA, UIKIT, XCB, XLIB, MIR, WAYLAND, ANDROID, WASM, NOOP." FORCE) + deduce_linux_display_server() endif() endif() @@ -72,7 +95,9 @@ elseif(XWIN_API STREQUAL "UIKIT") elseif(XWIN_API STREQUAL "XCB") set(XWIN_API_PATH "XCB") elseif(XWIN_API STREQUAL "XLIB") - set(XWIN_API_PATH "XLIB") + set(XWIN_API_PATH "XLib") +elseif(XWIN_API STREQUAL "WAYLAND") + set(XWIN_API_PATH "Wayland") elseif(XWIN_API STREQUAL "ANDROID") set(XWIN_API_PATH "Android") elseif(XWIN_API STREQUAL "UIKIT") @@ -82,7 +107,7 @@ elseif(XWIN_API STREQUAL "WASM") elseif(XWIN_API STREQUAL "NOOP") set(XWIN_API_PATH "Noop") else() - message( SEND_ERROR "XWIN_API can only be either AUTO, NOOP, WIN32, UWP, COCOA, UIKIT, XCB, XLIB, MIR, WAYLAND, ANDROID, or WASM.") + message( SEND_ERROR "Detected: ${XWIN_API}; XWIN_API can only be either AUTO, NOOP, WIN32, UWP, COCOA, UIKIT, XCB, XLIB, MIR, WAYLAND, ANDROID, or WASM.") endif() message( STATUS "Building CrossWindow for " ${XWIN_API_PATH} ) @@ -123,58 +148,7 @@ set(XMAIN_SOURCES ${MAIN_SOURCES} CACHE STRING "Global Variable - The source fil # ============================================================= -# CrossWindow Functions -function(xwin_setup versionMajor versionMinor versionPatch versionRevision companyName iconPath) - # @TODO - implement - message("Warning: xwin_setup has not yet been implemented.") -endfunction() - -function(xwin_add_executable targetProject targetSources) - message("Creating CrossWindow executable:") - - foreach(source IN LISTS XMAIN_SOURCES) - source_group("" FILES "${source}") - endforeach() - set(XWIN_FILES "${XMAIN_SOURCES}" "${targetSources}") - - if(XWIN_API STREQUAL "WIN32" OR XWIN_API STREQUAL "UWP") - add_executable( - ${targetProject} - WIN32 - "${XWIN_FILES}" - ) - elseif(XWIN_API STREQUAL "COCOA" OR XWIN_API STREQUAL "UIKIT") - add_executable( - ${targetProject} - MACOSX_BUNDLE - ${XWIN_FILES} - - ) - elseif(XWIN_API STREQUAL "XCB" OR XWIN_API STREQUAL "XLIB") - add_executable( - ${targetProject} - ${XWIN_FILES} - ) - elseif(XWIN_API STREQUAL "ANDROID") - add_executable( - ${targetProject} - ${XWIN_FILES} - ) - elseif(XWIN_API STREQUAL "WASM") - add_executable( - ${targetProject} - ${XWIN_FILES} - ) - elseif(XWIN_API STREQUAL "NOOP") - add_executable( - ${targetProject} - ${XWIN_FILES} - ) - endif() - - target_compile_definitions(${targetProject} PRIVATE XWIN_${XWIN_API}=1) - -endfunction() +set(XWIN_DEFINITIONS XWIN_${XWIN_API}=1 CACHE STRING "Global Variable - The compile definitions for the currently active protocol.") # ============================================================= @@ -211,7 +185,7 @@ elseif(XWIN_API STREQUAL "UIKIT") find_library(MOBILECORESERVICES MobileCoreServices) find_library(CFNETWORK CFNetwork) find_library(SYSTEMCONFIGURATION SystemConfiguration) - + target_link_libraries( ${PROJECT_NAME} ${UIKIT} @@ -234,13 +208,24 @@ elseif(XWIN_API STREQUAL "XLIB") target_link_libraries(${PROJECT_NAME} ${X11_LIBRARIES}) target_include_directories(${PROJECT_NAME} PUBLIC ${X11_INCLUDE_DIR}) endif() +elseif(XWIN_API STREQUAL "WAYLAND") + find_package(Wayland REQUIRED) + if(Wayland_FOUND) + message("Found Wayland Libraries.") + message("Wayland includes = ${Wayland_INCLUDE_DIRS}") + message("Wayland Libraries = ${Wayland_LIBRARIES}") + target_link_libraries(${PROJECT_NAME} ${Wayland_LIBRARIES}) + target_include_directories(${PROJECT_NAME} PUBLIC ${Wayland_INCLUDE_DIRS}) + endif() elseif(XWIN_API STREQUAL "XCB") find_package(X11 REQUIRED) - message("Found XCB Libraries.") - message("XCB Include Path = ${X11_xcb_INCLUDE_PATH}") - message("XCB Lib = ${X11_xcb_LIB}") - target_link_libraries(${PROJECT_NAME} ${X11_xcb_LIB}) - target_include_directories(${PROJECT_NAME} PUBLIC ${X11_xcb_INCLUDE_PATH}) + if(X11_FOUND) + message("Found XCB Libraries.") + message("XCB Include Path = ${X11_xcb_INCLUDE_PATH}") + message("XCB Lib = ${X11_xcb_LIB}") + target_link_libraries(${PROJECT_NAME} ${X11_xcb_LIB}) + target_include_directories(${PROJECT_NAME} PUBLIC ${X11_xcb_INCLUDE_PATH}) + endif() endif() # ============================================================= diff --git a/cmake/ECMFindModuleHelpersStub.cmake b/cmake/ECMFindModuleHelpersStub.cmake new file mode 100644 index 0000000..92c796e --- /dev/null +++ b/cmake/ECMFindModuleHelpersStub.cmake @@ -0,0 +1,277 @@ +# SPDX-FileCopyrightText: 2014 Alex Merry +# +# SPDX-License-Identifier: BSD-3-Clause + +#[=======================================================================[.rst: +ECMFindModuleHelpers +-------------------- + +Helper macros for find modules: ``ecm_find_package_version_check()``, +``ecm_find_package_parse_components()`` and +``ecm_find_package_handle_library_components()``. + +:: + + ecm_find_package_version_check() + +Prints warnings if the CMake version or the project's required CMake version +is older than that required by extra-cmake-modules. + +:: + + ecm_find_package_parse_components( + RESULT_VAR + KNOWN_COMPONENTS [ [...]] + [SKIP_DEPENDENCY_HANDLING]) + +This macro will populate with a list of components found in +_FIND_COMPONENTS, after checking that all those components are in the +list of ``KNOWN_COMPONENTS``; if there are any unknown components, it will print +an error or warning (depending on the value of _FIND_REQUIRED) and call +``return()``. + +The order of components in is guaranteed to match the order they +are listed in the ``KNOWN_COMPONENTS`` argument. + +If ``SKIP_DEPENDENCY_HANDLING`` is not set, for each component the variable +__component_deps will be checked for dependent components. +If is listed in _FIND_COMPONENTS, then all its (transitive) +dependencies will also be added to . + +:: + + ecm_find_package_handle_library_components( + COMPONENTS [ [...]] + [SKIP_DEPENDENCY_HANDLING]) + [SKIP_PKG_CONFIG]) + +Creates an imported library target for each component. The operation of this +macro depends on the presence of a number of CMake variables. + +The __lib variable should contain the name of this library, +and __header variable should contain the name of a header +file associated with it (whatever relative path is normally passed to +'#include'). __header_subdir variable can be used to specify +which subdirectory of the include path the headers will be found in. +``ecm_find_package_components()`` will then search for the library +and include directory (creating appropriate cache variables) and create an +imported library target named ::. + +Additional variables can be used to provide additional information: + +If ``SKIP_PKG_CONFIG``, the __pkg_config variable is set, and +pkg-config is found, the pkg-config module given by +__pkg_config will be searched for and used to help locate the +library and header file. It will also be used to set +__VERSION. + +Note that if version information is found via pkg-config, +__FIND_VERSION can be set to require a particular version +for each component. + +If ``SKIP_DEPENDENCY_HANDLING`` is not set, the ``INTERFACE_LINK_LIBRARIES`` property +of the imported target for will be set to contain the imported +targets for the components listed in __component_deps. +_FOUND will also be set to ``FALSE`` if any of the components in +__component_deps are not found. This requires the components +in __component_deps to be listed before in the +``COMPONENTS`` argument. + +The following variables will be set: + +``_TARGETS`` + the imported targets +``_LIBRARIES`` + the found libraries +``_INCLUDE_DIRS`` + the combined required include directories for the components +``_DEFINITIONS`` + the "other" CFLAGS provided by pkg-config, if any +``_VERSION`` + the value of ``__VERSION`` for the first component that + has this variable set (note that components are searched for in the order + they are passed to the macro), although if it is already set, it will not + be altered + +.. note:: + These variables are never cleared, so if + ``ecm_find_package_handle_library_components()`` is called multiple times with + different components (typically because of multiple ``find_package()`` calls) then + ``_TARGETS``, for example, will contain all the targets found in any + call (although no duplicates). + +Since pre-1.0.0. +#]=======================================================================] + +macro(ecm_find_package_version_check module_name) + if(CMAKE_VERSION VERSION_LESS 3.16.0) + message(FATAL_ERROR "CMake 3.16.0 is required by Find${module_name}.cmake") + endif() + if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 3.16.0) + message(AUTHOR_WARNING "Your project should require at least CMake 3.16.0 to use Find${module_name}.cmake") + endif() +endmacro() + +macro(ecm_find_package_parse_components module_name) + set(ecm_fppc_options SKIP_DEPENDENCY_HANDLING) + set(ecm_fppc_oneValueArgs RESULT_VAR) + set(ecm_fppc_multiValueArgs KNOWN_COMPONENTS DEFAULT_COMPONENTS) + cmake_parse_arguments(ECM_FPPC "${ecm_fppc_options}" "${ecm_fppc_oneValueArgs}" "${ecm_fppc_multiValueArgs}" ${ARGN}) + + if(ECM_FPPC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments to ecm_find_package_parse_components: ${ECM_FPPC_UNPARSED_ARGUMENTS}") + endif() + if(NOT ECM_FPPC_RESULT_VAR) + message(FATAL_ERROR "Missing RESULT_VAR argument to ecm_find_package_parse_components") + endif() + if(NOT ECM_FPPC_KNOWN_COMPONENTS) + message(FATAL_ERROR "Missing KNOWN_COMPONENTS argument to ecm_find_package_parse_components") + endif() + if(NOT ECM_FPPC_DEFAULT_COMPONENTS) + set(ECM_FPPC_DEFAULT_COMPONENTS ${ECM_FPPC_KNOWN_COMPONENTS}) + endif() + + if(${module_name}_FIND_COMPONENTS) + set(ecm_fppc_requestedComps ${${module_name}_FIND_COMPONENTS}) + + if(NOT ECM_FPPC_SKIP_DEPENDENCY_HANDLING) + # Make sure deps are included + foreach(ecm_fppc_comp ${ecm_fppc_requestedComps}) + foreach(ecm_fppc_dep_comp ${${module_name}_${ecm_fppc_comp}_component_deps}) + list(FIND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}" ecm_fppc_index) + if("${ecm_fppc_index}" STREQUAL "-1") + if(NOT ${module_name}_FIND_QUIETLY) + message(STATUS "${module_name}: ${ecm_fppc_comp} requires ${${module_name}_${ecm_fppc_comp}_component_deps}") + endif() + list(APPEND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}") + endif() + endforeach() + endforeach() + else() + message(STATUS "Skipping dependency handling for ${module_name}") + endif() + list(REMOVE_DUPLICATES ecm_fppc_requestedComps) + + # This makes sure components are listed in the same order as + # KNOWN_COMPONENTS (potentially important for inter-dependencies) + set(${ECM_FPPC_RESULT_VAR}) + foreach(ecm_fppc_comp ${ECM_FPPC_KNOWN_COMPONENTS}) + list(FIND ecm_fppc_requestedComps "${ecm_fppc_comp}" ecm_fppc_index) + if(NOT "${ecm_fppc_index}" STREQUAL "-1") + list(APPEND ${ECM_FPPC_RESULT_VAR} "${ecm_fppc_comp}") + list(REMOVE_AT ecm_fppc_requestedComps ${ecm_fppc_index}) + endif() + endforeach() + # if there are any left, they are unknown components + if(ecm_fppc_requestedComps) + set(ecm_fppc_msgType STATUS) + if(${module_name}_FIND_REQUIRED) + set(ecm_fppc_msgType FATAL_ERROR) + endif() + if(NOT ${module_name}_FIND_QUIETLY) + message(${ecm_fppc_msgType} "${module_name}: requested unknown components ${ecm_fppc_requestedComps}") + endif() + return() + endif() + else() + set(${ECM_FPPC_RESULT_VAR} ${ECM_FPPC_DEFAULT_COMPONENTS}) + endif() +endmacro() + +macro(ecm_find_package_handle_library_components module_name) + set(ecm_fpwc_options SKIP_PKG_CONFIG SKIP_DEPENDENCY_HANDLING) + set(ecm_fpwc_oneValueArgs) + set(ecm_fpwc_multiValueArgs COMPONENTS) + cmake_parse_arguments(ECM_FPWC "${ecm_fpwc_options}" "${ecm_fpwc_oneValueArgs}" "${ecm_fpwc_multiValueArgs}" ${ARGN}) + + if(ECM_FPWC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments to ecm_find_package_handle_components: ${ECM_FPWC_UNPARSED_ARGUMENTS}") + endif() + if(NOT ECM_FPWC_COMPONENTS) + message(FATAL_ERROR "Missing COMPONENTS argument to ecm_find_package_handle_components") + endif() + + include(FindPackageHandleStandardArgs) + find_package(PkgConfig QUIET) + foreach(ecm_fpwc_comp ${ECM_FPWC_COMPONENTS}) + set(ecm_fpwc_dep_vars) + set(ecm_fpwc_dep_targets) + if(NOT SKIP_DEPENDENCY_HANDLING) + foreach(ecm_fpwc_dep ${${module_name}_${ecm_fpwc_comp}_component_deps}) + list(APPEND ecm_fpwc_dep_vars "${module_name}_${ecm_fpwc_dep}_FOUND") + list(APPEND ecm_fpwc_dep_targets "${module_name}::${ecm_fpwc_dep}") + endforeach() + endif() + + if(NOT ECM_FPWC_SKIP_PKG_CONFIG AND ${module_name}_${ecm_fpwc_comp}_pkg_config) + pkg_check_modules(PKG_${module_name}_${ecm_fpwc_comp} QUIET + ${${module_name}_${ecm_fpwc_comp}_pkg_config}) + endif() + + find_path(${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + NAMES ${${module_name}_${ecm_fpwc_comp}_header} + HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_INCLUDE_DIRS} + PATH_SUFFIXES ${${module_name}_${ecm_fpwc_comp}_header_subdir} + ) + find_library(${module_name}_${ecm_fpwc_comp}_LIBRARY + NAMES ${${module_name}_${ecm_fpwc_comp}_lib} + HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_LIBRARY_DIRS} + ) + + set(${module_name}_${ecm_fpwc_comp}_VERSION "${PKG_${module_name}_${ecm_fpwc_comp}_VERSION}") + if(NOT ${module_name}_VERSION) + set(${module_name}_VERSION ${${module_name}_${ecm_fpwc_comp}_VERSION}) + endif() + + set(FPHSA_NAME_MISMATCHED 1) + find_package_handle_standard_args(${module_name}_${ecm_fpwc_comp} + FOUND_VAR + ${module_name}_${ecm_fpwc_comp}_FOUND + REQUIRED_VARS + ${module_name}_${ecm_fpwc_comp}_LIBRARY + ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + ${ecm_fpwc_dep_vars} + VERSION_VAR + ${module_name}_${ecm_fpwc_comp}_VERSION + ) + unset(FPHSA_NAME_MISMATCHED) + + mark_as_advanced( + ${module_name}_${ecm_fpwc_comp}_LIBRARY + ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + ) + + if(${module_name}_${ecm_fpwc_comp}_FOUND) + list(APPEND ${module_name}_LIBRARIES + "${${module_name}_${ecm_fpwc_comp}_LIBRARY}") + list(APPEND ${module_name}_INCLUDE_DIRS + "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}") + set(${module_name}_DEFINITIONS + ${${module_name}_DEFINITIONS} + ${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}) + if(NOT TARGET ${module_name}::${ecm_fpwc_comp}) + add_library(${module_name}::${ecm_fpwc_comp} UNKNOWN IMPORTED) + set_target_properties(${module_name}::${ecm_fpwc_comp} PROPERTIES + IMPORTED_LOCATION "${${module_name}_${ecm_fpwc_comp}_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${ecm_fpwc_dep_targets}" + ) + endif() + list(APPEND ${module_name}_TARGETS + "${module_name}::${ecm_fpwc_comp}") + endif() + endforeach() + if(${module_name}_LIBRARIES) + list(REMOVE_DUPLICATES ${module_name}_LIBRARIES) + endif() + if(${module_name}_INCLUDE_DIRS) + list(REMOVE_DUPLICATES ${module_name}_INCLUDE_DIRS) + endif() + if(${module_name}_DEFINITIONS) + list(REMOVE_DUPLICATES ${module_name}_DEFINITIONS) + endif() + if(${module_name}_TARGETS) + list(REMOVE_DUPLICATES ${module_name}_TARGETS) + endif() +endmacro() diff --git a/cmake/FindWayland.cmake b/cmake/FindWayland.cmake new file mode 100644 index 0000000..926fd48 --- /dev/null +++ b/cmake/FindWayland.cmake @@ -0,0 +1,137 @@ +# SPDX-FileCopyrightText: 2014 Alex Merry +# SPDX-FileCopyrightText: 2014 Martin Gräßlin +# +# SPDX-License-Identifier: BSD-3-Clause + +#[=======================================================================[.rst: +FindWayland +----------- + +Try to find Wayland. + +This is a component-based find module, which makes use of the COMPONENTS +and OPTIONAL_COMPONENTS arguments to find_module. The following components +are available:: + + Client Server Cursor Egl + +If no components are specified, this module will act as though all components +were passed to OPTIONAL_COMPONENTS. + +This module will define the following variables, independently of the +components searched for or found: + +``Wayland_FOUND`` + TRUE if (the requested version of) Wayland is available +``Wayland_VERSION`` + Found Wayland version +``Wayland_TARGETS`` + A list of all targets imported by this module (note that there may be more + than the components that were requested) +``Wayland_LIBRARIES`` + This can be passed to target_link_libraries() instead of the imported + targets +``Wayland_INCLUDE_DIRS`` + This should be passed to target_include_directories() if the targets are + not used for linking +``Wayland_DEFINITIONS`` + This should be passed to target_compile_options() if the targets are not + used for linking +``Wayland_DATADIR`` + The core wayland protocols data directory + Since 5.73.0 + +For each searched-for components, ``Wayland__FOUND`` will be set to +TRUE if the corresponding Wayland library was found, and FALSE otherwise. If +``Wayland__FOUND`` is TRUE, the imported target +``Wayland::`` will be defined. This module will also attempt to +determine ``Wayland_*_VERSION`` variables for each imported target, although +``Wayland_VERSION`` should normally be sufficient. + +In general we recommend using the imported targets, as they are easier to use +and provide more control. Bear in mind, however, that if any target is in the +link interface of an exported library, it must be made available by the +package config file. + +Since pre-1.0.0. +#]=======================================================================] + +include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake) + +ecm_find_package_version_check(Wayland) + +set(Wayland_known_components + Client + Server + Cursor + Egl +) +foreach(_comp ${Wayland_known_components}) + string(TOLOWER "${_comp}" _lc_comp) + set(Wayland_${_comp}_component_deps) + set(Wayland_${_comp}_pkg_config "wayland-${_lc_comp}") + set(Wayland_${_comp}_lib "wayland-${_lc_comp}") + set(Wayland_${_comp}_header "wayland-${_lc_comp}.h") +endforeach() +set(Wayland_Egl_component_deps Client) + +ecm_find_package_parse_components(Wayland + RESULT_VAR Wayland_components + KNOWN_COMPONENTS ${Wayland_known_components} +) +ecm_find_package_handle_library_components(Wayland + COMPONENTS ${Wayland_components} +) + +# If pkg-config didn't provide us with version information, +# try to extract it from wayland-version.h +# (Note that the version from wayland-egl.pc will probably be +# the Mesa version, rather than the Wayland version, but that +# version will be ignored as we always find wayland-client.pc +# first). +if(NOT Wayland_VERSION) + find_file(Wayland_VERSION_HEADER + NAMES wayland-version.h + HINTS ${Wayland_INCLUDE_DIRS} + ) + mark_as_advanced(Wayland_VERSION_HEADER) + if(Wayland_VERSION_HEADER) + file(READ ${Wayland_VERSION_HEADER} _wayland_version_header_contents) + string(REGEX REPLACE + "^.*[ \t]+WAYLAND_VERSION[ \t]+\"([0-9.]*)\".*$" + "\\1" + Wayland_VERSION + "${_wayland_version_header_contents}" + ) + unset(_wayland_version_header_contents) + endif() +endif() + +find_package_handle_standard_args(Wayland + FOUND_VAR + Wayland_FOUND + REQUIRED_VARS + Wayland_LIBRARIES + VERSION_VAR + Wayland_VERSION + HANDLE_COMPONENTS +) + +pkg_get_variable(Wayland_DATADIR wayland-scanner pkgdatadir) +if (CMAKE_CROSSCOMPILING AND (NOT EXISTS "${Wayland_DATADIR}/wayland.xml")) + # PKG_CONFIG_SYSROOT_DIR only applies to -I and -L flags, so pkg-config + # does not prepend CMAKE_SYSROOT when cross-compiling unless you pass + # --define-prefix explicitly. Therefore we have to manually do prepend + # it here when cross-compiling. + # See https://gitlab.kitware.com/cmake/cmake/-/issues/16647#note_844761 + set(Wayland_DATADIR ${CMAKE_SYSROOT}${Wayland_DATADIR}) +endif() +if (NOT EXISTS "${Wayland_DATADIR}/wayland.xml") + message(WARNING "Could not find wayland.xml in ${Wayland_DATADIR}") +endif() + +include(FeatureSummary) +set_package_properties(Wayland PROPERTIES + URL "https://wayland.freedesktop.org/" + DESCRIPTION "C library implementation of the Wayland protocol: a protocol for a compositor to talk to its clients" +) diff --git a/src/CrossWindow/Common/Event.h b/src/CrossWindow/Common/Event.h index 1a3e16e..df3289b 100644 --- a/src/CrossWindow/Common/Event.h +++ b/src/CrossWindow/Common/Event.h @@ -14,7 +14,7 @@ class Window; enum class EventType : size_t { - None = 0, + None, // Closing a window Close, @@ -89,8 +89,8 @@ struct ResizeData // New height of window viewport unsigned height; - // In the process of resizing - bool resizing; + // In the process of resizing + bool resizing; ResizeData(unsigned width, unsigned height, bool resizing); @@ -480,7 +480,8 @@ struct GamepadData * SDL does something similar: * */ -union EventData { +union EventData +{ FocusData focus; ResizeData resize; DpiData dpi; @@ -508,8 +509,10 @@ class Event // Inner data of the event EventData data; - - Event(EventType type = EventType::None, Window* window = nullptr); + + Event(Window* window = nullptr) { Event(EventType::None, window); } + + Event(EventType type, Window* window = nullptr); Event(FocusData data, Window* window = nullptr); @@ -519,7 +522,7 @@ class Event Event(MouseMoveData data, Window* window = nullptr); - Event(MouseRawData data, Window* window = nullptr); + Event(MouseRawData data, Window* window = nullptr); Event(MouseInputData data, Window* window = nullptr); @@ -532,12 +535,10 @@ class Event Event(DpiData data, Window* window = nullptr); ~Event(); - + bool operator==(const Event& other) const { return type == other.type && window == other.window; } - - }; } diff --git a/src/CrossWindow/Common/EventQueue.h b/src/CrossWindow/Common/EventQueue.h index 630c01d..23c2151 100644 --- a/src/CrossWindow/Common/EventQueue.h +++ b/src/CrossWindow/Common/EventQueue.h @@ -14,6 +14,8 @@ #include "../XCB/XCBEventQueue.h" #elif XWIN_XLIB #include "../XLib/XLibEventQueue.h" +#elif XWIN_WAYLAND +#include "../Wayland/WaylandEventQueue.h" #elif XWIN_ANDROID #include "../Android/AndroidEventQueue.h" #elif XWIN_UIKIT @@ -22,4 +24,4 @@ #include "../WASM/WASMEventQueue.h" #elif XWIN_NOOP #include "../Noop/NoopEventQueue.h" -#endif \ No newline at end of file +#endif diff --git a/src/CrossWindow/Common/Init.h b/src/CrossWindow/Common/Init.h index 7a8c8e0..9726b62 100644 --- a/src/CrossWindow/Common/Init.h +++ b/src/CrossWindow/Common/Init.h @@ -19,8 +19,8 @@ #define MainArgsVars argc, argv, connection, screen #elif defined(XWIN_XLIB) || defined(XWIN_MIR) || defined(XWIN_WAYLAND) || \ defined(XWIN_WASM) || defined(XWIN_NOOP) -#define MainArgs int argc, const char *argv[] -#define MainArgsVars argc, argv +#define MainArgs int argc, const char** argv, struct wl_display* display +#define MainArgsVars argc, argv, display #endif namespace xwin diff --git a/src/CrossWindow/Common/State.h b/src/CrossWindow/Common/State.h index 8479c85..e1fd53d 100644 --- a/src/CrossWindow/Common/State.h +++ b/src/CrossWindow/Common/State.h @@ -6,6 +6,9 @@ #include #elif defined(XWIN_XLIB) #include +#elif defined(XWIN_WAYLAND) +#include +#include #endif namespace xwin @@ -46,6 +49,17 @@ struct XWinState : argc(argc), argv(argv), connection(connection), screen(screen) { } +#elif defined(XWIN_WAYLAND) + int argc; + const char** argv; + struct wl_display* display; + struct wl_registry* registry; + struct wl_compositor* compositor; + + XWinState(int argc, const char** argv, struct wl_display* display) + : argc(argc), argv(argv), display(display) + { + } #elif defined(XWIN_ANDROID) diff --git a/src/CrossWindow/Common/Window.h b/src/CrossWindow/Common/Window.h index 84ecf5e..5a03a35 100644 --- a/src/CrossWindow/Common/Window.h +++ b/src/CrossWindow/Common/Window.h @@ -10,6 +10,8 @@ #include "../XCB/XCBWindow.h" #elif XWIN_XLIB #include "../XLib/XLibWindow.h" +#elif XWIN_WAYLAND +#include "../Wayland/WaylandWindow.h" #elif XWIN_ANDROID #include "../Android/AndroidWindow.h" #elif XWIN_UIKIT @@ -26,4 +28,4 @@ namespace xwin { typedef std::shared_ptr WindowPtr; typedef std::weak_ptr WindowWeakPtr; -} \ No newline at end of file +} diff --git a/src/CrossWindow/Main/WaylandMain.cpp b/src/CrossWindow/Main/WaylandMain.cpp new file mode 100644 index 0000000..9c2eff7 --- /dev/null +++ b/src/CrossWindow/Main/WaylandMain.cpp @@ -0,0 +1,31 @@ +#include "../Common/Init.h" +#include "Main.h" + +#include +#include +#include + +int main(int argc, const char** argv) +{ + struct wl_display* display = wl_display_connect(nullptr); + if (!display) return 1; + + struct wl_registry* registry = wl_display_get_registry(display); + const struct wl_registry_listener registry_listener + { + nullptr, nullptr // callbacks + }; + struct wl_compositor* compositor; + + wl_registry_add_listener(registry, ®istry_listener, &compositor); + wl_display_roundtrip(display); + + xwin::init(argc, argv, display); + xmain(argc, argv); + + wl_registry_destroy(registry); + wl_compositor_destroy(compositor); + wl_display_disconnect(display); + + return 0; +} diff --git a/src/CrossWindow/Main/XLibMain.cpp b/src/CrossWindow/Main/XLibMain.cpp index ae8900b..1fe2ecc 100644 --- a/src/CrossWindow/Main/XLibMain.cpp +++ b/src/CrossWindow/Main/XLibMain.cpp @@ -1,16 +1,11 @@ #include "../Common/Init.h" #include "Main.h" -int main(int argc, char** argv) +int main(int argc, const char** argv) { xwin::init(argc, argv); - - xmain(argc, argv); - XDestroyWindow(display, xlib_window); - XCloseDisplay(display); - return 0; -} \ No newline at end of file +} diff --git a/src/CrossWindow/Wayland/WaylandEventQueue.cpp b/src/CrossWindow/Wayland/WaylandEventQueue.cpp new file mode 100644 index 0000000..b9ccd73 --- /dev/null +++ b/src/CrossWindow/Wayland/WaylandEventQueue.cpp @@ -0,0 +1,20 @@ +#include "WaylandEventQueue.h" +#include "../Common/Window.h" +#include "CrossWindow/Common/WindowDesc.h" + +namespace xwin +{ +EventQueue::EventQueue() +{ + const XWinState& state = getXWinState(); + event_queue = wl_display_create_queue(state.display); +} + +void EventQueue::update() {} + +const Event& EventQueue::front() { return mQueue.front(); } + +void EventQueue::pop() { mQueue.pop(); } + +bool EventQueue::empty() { return mQueue.empty(); } +} diff --git a/src/CrossWindow/Wayland/WaylandEventQueue.h b/src/CrossWindow/Wayland/WaylandEventQueue.h new file mode 100644 index 0000000..b10a96b --- /dev/null +++ b/src/CrossWindow/Wayland/WaylandEventQueue.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../Common/Event.h" + +#include +#include + +namespace xwin +{ +class Window; + +/** + * Events - https://xcb.freedesktop.org/tutorial/events/ + */ +class EventQueue +{ + public: + EventQueue(); + + void update(); + + const Event& front(); + + void pop(); + + bool empty(); + + protected: + // void pushEvent(); + struct wl_event_queue* event_queue; + + std::queue mQueue; +}; +} diff --git a/src/CrossWindow/Wayland/WaylandWindow.cpp b/src/CrossWindow/Wayland/WaylandWindow.cpp new file mode 100644 index 0000000..0508651 --- /dev/null +++ b/src/CrossWindow/Wayland/WaylandWindow.cpp @@ -0,0 +1,55 @@ +#include "WaylandWindow.h" +#include "CrossWindow/Common/WindowDesc.h" + +#include + +namespace xwin +{ +Window::Window() { + create(mDesc, mEventQueue); +} + +Window::~Window() { close(); } + +void Window::minimize() {} + +void Window::maximize() {} + +const WindowDesc Window::getDesc() { return mDesc; } + +void Window::trackEventsAsync( + const std::function& fun) +{ + mCallback = fun; +} +// Executes an event callback asynchronously, use this for non-blocking +// events (resizing while rendering, etc.) +void Window::executeEventCallback(const xwin::Event e) +{ + if (mCallback) { + mCallback(e); + } +} + +bool Window::create(const WindowDesc& desc, EventQueue& eventQueue) { + const XWinState &xwinState = getXWinState(); + + surface = wl_compositor_create_surface(xwinState.compositor); + if (surface == nullptr) { + return false; + } + + shell_surface = wl_shell_get_shell_surface(shell, surface); + if (shell_surface == nullptr) { + return false; + } + + wl_shell_surface_set_toplevel(shell_surface); + wl_shell_surface_set_title(shell_surface, mDesc.title.c_str()); + + return true; +} + +void Window::close() { +} +} diff --git a/src/CrossWindow/Wayland/WaylandWindow.h b/src/CrossWindow/Wayland/WaylandWindow.h new file mode 100644 index 0000000..27ba0a0 --- /dev/null +++ b/src/CrossWindow/Wayland/WaylandWindow.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../Common/EventQueue.h" +#include "../Common/Init.h" +#include "../Common/WindowDesc.h" + +#include + +namespace xwin +{ +class Window +{ + public: + Window(); + ~Window(); + + // Initialize this window with the XCB API. + bool create(const WindowDesc& desc, EventQueue& eventQueue); + + void close(); + + // Request that this window be minimized. + void minimize(); + + // Request that this window be maximized. + void maximize(); + + // Set callback func + void trackEventsAsync(const std::function& fun); + + // Get window description + const WindowDesc getDesc(); + + public: + // Executes an event callback asynchronously, use this for non-blocking + // events (resizing while rendering, etc.) + void executeEventCallback(const xwin::Event e); + + std::function mCallback; + + struct wl_surface* surface; + struct wl_shell* shell; + struct wl_shell_surface *shell_surface; + + // Window description + WindowDesc mDesc; + // Window's event queue + EventQueue mEventQueue; +}; +} diff --git a/src/CrossWindow/Win32/Win32Dialogs.cpp b/src/CrossWindow/Win32/Win32Dialogs.cpp index d3d4397..f380648 100644 --- a/src/CrossWindow/Win32/Win32Dialogs.cpp +++ b/src/CrossWindow/Win32/Win32Dialogs.cpp @@ -94,7 +94,7 @@ bool showOpenDialog(const OpenSaveDialogDesc& odesc, std::string& outPath) hr = pFileOpen->GetResult(&pItem); if (SUCCEEDED(hr)) { - PWSTR pszFilePath = L""; + PWSTR pszFilePath = (PWSTR)L""; hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath); if (pszFilePath != 0x0) { @@ -186,7 +186,7 @@ bool showSaveDialog(const OpenSaveDialogDesc& sdesc, std::string& outPath) hr = pFileSave->GetResult(&pItem); if (SUCCEEDED(hr)) { - PWSTR pszFilePath = L""; + PWSTR pszFilePath = (PWSTR)L""; hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath); if (pszFilePath != 0x0) { diff --git a/src/CrossWindow/Win32/Win32EventQueue.h b/src/CrossWindow/Win32/Win32EventQueue.h index b940893..4e78703 100644 --- a/src/CrossWindow/Win32/Win32EventQueue.h +++ b/src/CrossWindow/Win32/Win32EventQueue.h @@ -23,7 +23,7 @@ class EventQueue bool empty(); - size_t size(); + size_t size(); enum class ProcessingMode { @@ -58,4 +58,4 @@ class EventQueue /*VK_RBUTTON 0x02*/ Key::KeysMax, /*VK_CANCEL 0x03*/ Key::KeysMax}; }; -} \ No newline at end of file +} diff --git a/src/CrossWindow/Win32/Win32Window.cpp b/src/CrossWindow/Win32/Win32Window.cpp index 3c02e10..f5e8de3 100644 --- a/src/CrossWindow/Win32/Win32Window.cpp +++ b/src/CrossWindow/Win32/Win32Window.cpp @@ -265,17 +265,20 @@ void Window::setSize(unsigned width, unsigned height) { RECT rect, frame, border; GetWindowRect(hwnd, &rect); - DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT)); + DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, + sizeof(RECT)); border.left = frame.left - rect.left; border.top = frame.top - rect.top; border.right = rect.right - frame.right; border.bottom = rect.bottom - frame.bottom; - int titlebarHeight = (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) + - GetSystemMetrics(SM_CXPADDEDBORDER)); + int titlebarHeight = + (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) + + GetSystemMetrics(SM_CXPADDEDBORDER)); - SetWindowPos(hwnd, nullptr, -1, -1, width + border.right + border.left, height + border.top + border.bottom + titlebarHeight, + SetWindowPos(hwnd, nullptr, -1, -1, width + border.right + border.left, + height + border.top + border.bottom + titlebarHeight, SWP_NOMOVE | SWP_NOREDRAW); } @@ -302,9 +305,13 @@ UVec2 Window::getPosition() const UVec2 Window::getWindowSize() const { RECT lpRect; - DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &lpRect, sizeof(lpRect)); - int titlebarHeight = (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CXPADDEDBORDER)); - return UVec2(lpRect.right - lpRect.left, lpRect.bottom - lpRect.top - titlebarHeight); + DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &lpRect, + sizeof(lpRect)); + int titlebarHeight = + (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) + + GetSystemMetrics(SM_CXPADDEDBORDER)); + return UVec2(lpRect.right - lpRect.left, + lpRect.bottom - lpRect.top - titlebarHeight); } UVec2 Window::getCurrentDisplaySize() const @@ -368,4 +375,4 @@ LRESULT Window::WindowProc(UINT msg, WPARAM wparam, LPARAM lparam) if (result > 0) return result; return DefWindowProc(hwnd, msg, wparam, lparam); } -} \ No newline at end of file +} diff --git a/src/CrossWindow/Win32/Win32Window.h b/src/CrossWindow/Win32/Win32Window.h index 7ce7c7e..5b03c48 100644 --- a/src/CrossWindow/Win32/Win32Window.h +++ b/src/CrossWindow/Win32/Win32Window.h @@ -146,12 +146,14 @@ class Window LRESULT WindowProc(UINT msg, WPARAM wparam, LPARAM lparam); + public: // This window handle HWND hwnd = nullptr; // This window's application instance. HINSTANCE hinstance = nullptr; + protected: // Pointer to this window's event queue EventQueue* mEventQueue = nullptr; diff --git a/src/CrossWindow/XCB/XCBEventQueue.h b/src/CrossWindow/XCB/XCBEventQueue.h index c40fe43..ca23b1b 100644 --- a/src/CrossWindow/XCB/XCBEventQueue.h +++ b/src/CrossWindow/XCB/XCBEventQueue.h @@ -8,27 +8,27 @@ namespace xwin { - class Window; +class Window; - /** - * Events - https://xcb.freedesktop.org/tutorial/events/ - */ - class EventQueue - { - public: - EventQueue(); +/** + * Events - https://xcb.freedesktop.org/tutorial/events/ + */ +class EventQueue +{ + public: + EventQueue(); - void update(); + void update(); - const Event &front(); + const Event& front(); - void pop(); + void pop(); - bool empty(); + bool empty(); - protected: - void pushEvent(const xcb_generic_event_t* e); + protected: + void pushEvent(const xcb_generic_event_t* e); - std::queue mQueue; - }; -} \ No newline at end of file + std::queue mQueue; +}; +} diff --git a/src/CrossWindow/XCB/XCBWindow.cpp b/src/CrossWindow/XCB/XCBWindow.cpp index 43f1a10..fe67f21 100644 --- a/src/CrossWindow/XCB/XCBWindow.cpp +++ b/src/CrossWindow/XCB/XCBWindow.cpp @@ -4,39 +4,95 @@ namespace xwin { Window::Window() {} +// Request that this window be minimized. +void Window::minimize() {} + +// Request that this window be maximized. +void Window::maximize() {} + +// Set callback func +void Window::trackEventsAsync( + const std::function& fun) +{ + mCallback = fun; +} + +// Executes an event callback asynchronously, use this for non-blocking +// events (resizing while rendering, etc.) +void Window::executeEventCallback(const xwin::Event e) +{ + switch (e.type) + { + case EventType::Create: + break; + case EventType::Close: + break; + case EventType::Focus: + break; + case EventType::Paint: + break; + case EventType::Resize: + break; + case EventType::DPI: + break; + case EventType::Keyboard: + break; + case EventType::MouseMove: + break; + case EventType::MouseRaw: + break; + case EventType::MouseWheel: + break; + case EventType::MouseInput: + break; + case EventType::Touch: + break; + case EventType::Gamepad: + break; + case EventType::DropFile: + break; + case EventType::HoverFile: + break; + default: + // EventType::None + // EventType::EventTypeMax + break; + } +} + bool Window::create(const WindowDesc& desc, EventQueue& eventQueue) { const XWinState& xwinState = getXWinState(); - mConnection = xwinState.connection; - mScreen = xwinState.screen; + connection = xwinState.connection; + screen = xwinState.screen; - mXcbWindowId = xcb_generate_id(mConnection); + window = xcb_generate_id(connection); uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; uint32_t value_list[2] = { - mScreen->black_pixel, + screen->black_pixel, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE}; - xcb_create_window(mConnection, XCB_COPY_FROM_PARENT, mXcbWindowId, - mScreen->root, desc.x, desc.y, desc.width, desc.height, 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, mScreen->root_visual, mask, + xcb_create_window(connection, XCB_COPY_FROM_PARENT, window, screen->root, + desc.x, desc.y, desc.width, desc.height, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, value_list); - xcb_map_window(mConnection, mXcbWindowId); + xcb_map_window(connection, window); const unsigned coords[] = {static_cast(desc.x), static_cast(desc.y)}; - xcb_configure_window(mConnection, mXcbWindowId, + xcb_configure_window(connection, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords); - xcb_flush(mConnection); + xcb_flush(connection); return true; } -void Window::close() { xcb_destroy_window(mConnection, mXcbWindowId); } +void Window::close() { xcb_destroy_window(connection, window); } -} \ No newline at end of file +} diff --git a/src/CrossWindow/XCB/XCBWindow.h b/src/CrossWindow/XCB/XCBWindow.h index 6eb36d6..6c28db8 100644 --- a/src/CrossWindow/XCB/XCBWindow.h +++ b/src/CrossWindow/XCB/XCBWindow.h @@ -6,6 +6,8 @@ #include +#include + namespace xwin { /** @@ -16,18 +18,44 @@ namespace xwin */ class Window { -public: + public: Window(); // Initialize this window with the XCB API. bool create(const WindowDesc& desc, EventQueue& eventQueue); + // Request that this window be closed. void close(); + // Request that this window be minimized. + void minimize(); + + // Request that this window be maximized. + void maximize(); + + // Set callback func + void trackEventsAsync(const std::function& fun); + + // Get window description + const WindowDesc getDesc(); + protected: - xcb_connection_t* mConnection = nullptr; - xcb_screen_t* mScreen = nullptr; - unsigned mXcbWindowId = 0; - unsigned mDisplay = 0; + // Pointer to this window's event queue + EventQueue* mEventQueue = nullptr; + + // Executes an event callback asynchronously, use this for non-blocking + // events (resizing while rendering, etc.) + void executeEventCallback(const xwin::Event e); + + std::function mCallback; + + // Window description + WindowDesc mDesc; + + public: + xcb_connection_t* connection = nullptr; + xcb_screen_t* screen = nullptr; + xcb_window_t window = 0; + unsigned display = 0; }; } diff --git a/src/CrossWindow/XLib/XLibEventQueue.cpp b/src/CrossWindow/XLib/XLibEventQueue.cpp index 0982374..68947f4 100644 --- a/src/CrossWindow/XLib/XLibEventQueue.cpp +++ b/src/CrossWindow/XLib/XLibEventQueue.cpp @@ -1,5 +1,7 @@ #include "XLibEventQueue.h" #include "../Common/Window.h" +#include "CrossWindow/Common/WindowDesc.h" +#include namespace xwin { @@ -7,59 +9,76 @@ void EventQueue::update() { XEvent event; - while (XPending(demo->display) > 0) + while (XPending(mParent->display) > 0) { - XNextEvent(demo->display, &event); - pushEvent(event, mParent); + XNextEvent(mParent->display, &event); + pushEvent(&event, mParent); } } +EventQueue::EventQueue() {} + +EventQueue::EventQueue(Window* parent) : mParent(parent) {} + +const Event& EventQueue::front() { return mQueue.front(); } + +void EventQueue::pop() { mQueue.pop(); } + +bool EventQueue::empty() { return mQueue.empty(); } + void EventQueue::pushEvent(const XEvent* event, Window* window) { + xwin::Event e = xwin::Event(window); // xwin::EventType::None, window); + switch (event->type) { - case ConfigureNotify: + case CreateNotify: { - WindowDesc desc = window->getDesc(); - if ((desc.width != event->xconfigure.width) || - (desc.height != event->xconfigure.height)) - { - unsigned width, height; - width = static_cast(event->xconfigure.width); - height = static_cast(event->xconfigure.height); + e = xwin::Event(xwin::EventType::Create, window); + break; + } + case ResizeRequest: + { + unsigned width, height; + width = static_cast(event->xconfigure.width); + height = static_cast(event->xconfigure.height); - mQueue.emplace(ResizeData(width, height, true), window); - } + e = xwin::Event(ResizeData(width, height, false), window); break; } - case ClientMessage: + case DestroyNotify: { - mQueue.emplace(xwin::EventType::Close, window); + e = xwin::Event(xwin::EventType::Close, window); break; } + case KeyRelease: case KeyPress: { Key d = Key::KeysMax; switch (event->xkey.keycode) { - case 0x9: // Escape + case XK_Escape: // Escape d = Key::Escape; break; case XK_KP_Left: // left arrow key d = Key::Left; break; - case 0x72: // right arrow key + case XK_KP_Right: // right arrow key d = Key::Right; break; - case 0x41: // space bar + case XK_KP_Space: // space bar d = Key::Space; break; } break; - mQueue.emplace(KeyboardData(d, ButtonState::Pressed, ModifierState()), - window); + e = xwin::Event(KeyboardData(d, + event->type == KeyPress + ? ButtonState::Pressed + : ButtonState::Released, + ModifierState()), + window); } } } -} \ No newline at end of file +} diff --git a/src/CrossWindow/XLib/XLibEventQueue.h b/src/CrossWindow/XLib/XLibEventQueue.h index 6015b77..57bafcb 100644 --- a/src/CrossWindow/XLib/XLibEventQueue.h +++ b/src/CrossWindow/XLib/XLibEventQueue.h @@ -14,9 +14,21 @@ class Window; class EventQueue { public: + EventQueue(); + EventQueue(Window* parent); + + void update(); + + const Event& front(); + + void pop(); + + bool empty(); + void pushEvent(const XEvent* event, Window* window); protected: std::queue mQueue; + Window* mParent; }; -} \ No newline at end of file +} diff --git a/src/CrossWindow/XLib/XLibWindow.cpp b/src/CrossWindow/XLib/XLibWindow.cpp index e8b376b..1eb8dbc 100644 --- a/src/CrossWindow/XLib/XLibWindow.cpp +++ b/src/CrossWindow/XLib/XLibWindow.cpp @@ -1,7 +1,12 @@ #include "XLibWindow.h" +#include "CrossWindow/Common/WindowDesc.h" namespace xwin { +Window::Window() {} + +const WindowDesc Window::getDesc() { return mDesc; } + bool Window::create(const WindowDesc& desc, EventQueue& eventQueue) { XInitThreads(); @@ -29,7 +34,12 @@ bool Window::create(const WindowDesc& desc, EventQueue& eventQueue) XSelectInput(display, window, ExposureMask | KeyPressMask); XMapWindow(display, window); XFlush(display); + + return window; } -bool Window::destroy() { XDestroyWindow(display, window); } -} \ No newline at end of file +bool Window::close() +{ + return (XDestroyWindow(display, window)) || XCloseDisplay(display); +} +} diff --git a/src/CrossWindow/XLib/XLibWindow.h b/src/CrossWindow/XLib/XLibWindow.h index ee9476c..64c3589 100644 --- a/src/CrossWindow/XLib/XLibWindow.h +++ b/src/CrossWindow/XLib/XLibWindow.h @@ -6,6 +6,8 @@ #include +#include + typedef Window XLibWindow; namespace xwin @@ -17,9 +19,33 @@ class Window bool create(const WindowDesc& desc, EventQueue& eventQueue); - bool destroy(); + bool close(); + + // Request that this window be minimized. + void minimize(); + + // Request that this window be maximized. + void maximize(); + + // Set callback func + void trackEventsAsync(const std::function& fun); + + // Get window description + const WindowDesc getDesc(); protected: + // Pointer to this window's event queue + EventQueue* mEventQueue = nullptr; + + // Executes an event callback asynchronously, use this for non-blocking + // events (resizing while rendering, etc.) + void executeEventCallback(const xwin::Event e); + + std::function mCallback; + + WindowDesc mDesc; + + public: Display* display = nullptr; XLibWindow window; };