Переглянути джерело

Merge branch 'vaxerski-main'

Alexis Maiquez 3 роки тому
батько
коміт
5016a9d873
52 змінених файлів з 1970 додано та 301 видалено
  1. 24 0
      .github/workflows/cmake.yml
  2. 7 1
      .gitignore
  3. 63 0
      CMakeLists.txt
  4. 285 0
      protocols/wlr-layer-shell-unstable-v1.xml
  5. 2 2
      src/External/Android/fft.cpp
  6. 18 18
      src/WallpaperEngine/Application/CApplicationContext.cpp
  7. 2 2
      src/WallpaperEngine/Application/CApplicationContext.h
  8. 96 81
      src/WallpaperEngine/Application/CWallpaperApplication.cpp
  9. 33 0
      src/WallpaperEngine/Application/CWallpaperApplication.h
  10. 2 0
      src/WallpaperEngine/Assets/CFileEntry.h
  11. 2 2
      src/WallpaperEngine/Audio/Drivers/Detectors/CAudioPlayingDetector.cpp
  12. 3 3
      src/WallpaperEngine/Audio/Drivers/Detectors/CAudioPlayingDetector.h
  13. 2 2
      src/WallpaperEngine/Audio/Drivers/Detectors/CPulseAudioPlayingDetector.cpp
  14. 1 1
      src/WallpaperEngine/Audio/Drivers/Detectors/CPulseAudioPlayingDetector.h
  15. 4 5
      src/WallpaperEngine/Input/CInputContext.cpp
  16. 7 4
      src/WallpaperEngine/Input/CInputContext.h
  17. 0 14
      src/WallpaperEngine/Input/CMouseInput.cpp
  18. 2 16
      src/WallpaperEngine/Input/CMouseInput.h
  19. 24 0
      src/WallpaperEngine/Input/Drivers/CGLFWMouseInput.cpp
  20. 42 0
      src/WallpaperEngine/Input/Drivers/CGLFWMouseInput.h
  21. 21 0
      src/WallpaperEngine/Input/Drivers/CWaylandMouseInput.cpp
  22. 41 0
      src/WallpaperEngine/Input/Drivers/CWaylandMouseInput.h
  23. 19 38
      src/WallpaperEngine/Render/CRenderContext.cpp
  24. 5 5
      src/WallpaperEngine/Render/CRenderContext.h
  25. 1 1
      src/WallpaperEngine/Render/CScene.cpp
  26. 2 2
      src/WallpaperEngine/Render/CVideo.cpp
  27. 2 6
      src/WallpaperEngine/Render/CWallpaper.cpp
  28. 1 1
      src/WallpaperEngine/Render/CWallpaper.h
  29. 14 0
      src/WallpaperEngine/Render/Drivers/CVideoDriver.cpp
  30. 40 8
      src/WallpaperEngine/Render/Drivers/CVideoDriver.h
  31. 401 0
      src/WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.cpp
  32. 114 0
      src/WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.h
  33. 60 7
      src/WallpaperEngine/Render/Drivers/CX11OpenGLDriver.cpp
  34. 16 6
      src/WallpaperEngine/Render/Drivers/CX11OpenGLDriver.h
  35. 20 0
      src/WallpaperEngine/Render/Drivers/Detectors/CWaylandFullScreenDetector.cpp
  36. 28 0
      src/WallpaperEngine/Render/Drivers/Detectors/CWaylandFullScreenDetector.h
  37. 3 2
      src/WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.cpp
  38. 27 24
      src/WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.h
  39. 6 6
      src/WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.cpp
  40. 1 3
      src/WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.h
  41. 3 3
      src/WallpaperEngine/Render/Drivers/Output/COutput.cpp
  42. 31 30
      src/WallpaperEngine/Render/Drivers/Output/COutput.h
  43. 16 0
      src/WallpaperEngine/Render/Drivers/Output/COutputViewport.cpp
  44. 30 0
      src/WallpaperEngine/Render/Drivers/Output/COutputViewport.h
  45. 66 0
      src/WallpaperEngine/Render/Drivers/Output/CWaylandOutput.cpp
  46. 36 0
      src/WallpaperEngine/Render/Drivers/Output/CWaylandOutput.h
  47. 224 0
      src/WallpaperEngine/Render/Drivers/Output/CWaylandOutputViewport.cpp
  48. 75 0
      src/WallpaperEngine/Render/Drivers/Output/CWaylandOutputViewport.h
  49. 13 5
      src/WallpaperEngine/Render/Drivers/Output/CX11Output.cpp
  50. 2 3
      src/WallpaperEngine/Render/Drivers/Output/CX11Output.h
  51. 18 0
      src/WallpaperEngine/Render/Drivers/Output/CX11OutputViewport.cpp
  52. 15 0
      src/WallpaperEngine/Render/Drivers/Output/CX11OutputViewport.h

+ 24 - 0
.github/workflows/cmake.yml

@@ -34,3 +34,27 @@ jobs:
     - name: Build
       # Build your program with the given configuration
       run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+
+  build-wayland:
+    strategy:
+      matrix:
+        os: [ubuntu-22.04]
+    # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
+    # You can convert this to a matrix build if you need cross-platform coverage.
+    # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
+    runs-on: ${{ matrix.os }}
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Install dependencies
+      run: sudo apt-get update && sudo apt-get -y install libxrandr-dev libfreeimage-dev libxinerama-dev libxcursor-dev libxi-dev libgl-dev libglew-dev freeglut3-dev libsdl2-dev liblz4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libxxf86vm-dev libglm-dev libglfw3-dev libmpv-dev mpv libmpv1 libpulse-dev libpulse0 wayland-scanner++ wayland-protocols libwayland-dev libwayland-egl-backend-dev
+
+    - 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 ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_WAYLAND=True
+
+    - name: Build
+      # Build your program with the given configuration
+      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}

+ 7 - 1
.gitignore

@@ -1,2 +1,8 @@
 cmake-build-debug*
-.idea
+.idea
+build/
+.vscode/
+
+*-protocol.o
+*-protocol.c
+*-protocol.h

+ 63 - 0
CMakeLists.txt

@@ -41,8 +41,55 @@ include_directories(
     ${FREEIMAGE_INCLUDE_DIR}
     ${PULSEAUDIO_INCLUDE_DIR}
     src
+    ${CMAKE_SOURCE_DIR}
     include)
 
+if (ENABLE_WAYLAND)
+    message(STATUS "Wayland support is enabled!")
+
+    pkg_check_modules(wayland-cursor wayland-protocols egl wayland-egl)
+
+    find_program(WaylandScanner NAMES wayland-scanner)
+    message(STATUS "Found WaylandScanner at ${WaylandScanner}")
+    execute_process(
+        COMMAND pkg-config --variable=pkgdatadir wayland-protocols
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+        OUTPUT_VARIABLE WAYLAND_PROTOCOLS_DIR
+        OUTPUT_STRIP_TRAILING_WHITESPACE)
+        message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
+
+    message(STATUS "Building protocols...")
+    execute_process(
+        COMMAND ${WaylandScanner} client-header ${CMAKE_SOURCE_DIR}/protocols/wlr-layer-shell-unstable-v1.xml wlr-layer-shell-unstable-v1-protocol.h
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+    execute_process(
+        COMMAND ${WaylandScanner} private-code ${CMAKE_SOURCE_DIR}/protocols/wlr-layer-shell-unstable-v1.xml wlr-layer-shell-unstable-v1-protocol.c
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+    execute_process(
+        COMMAND ${WaylandScanner} client-header ${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml xdg-shell-protocol.h
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+    execute_process(
+        COMMAND ${WaylandScanner} private-code ${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml xdg-shell-protocol.c
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+
+    add_compile_definitions(ENABLE_WAYLAND)
+    set(WAYLAND_SOURCES
+            "src/WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.h"
+            "src/WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.cpp"
+            "src/WallpaperEngine/Render/Drivers/Detectors/CWaylandFullScreenDetector.cpp"
+            "src/WallpaperEngine/Render/Drivers/Detectors/CWaylandFullScreenDetector.h"
+            "src/WallpaperEngine/Render/Drivers/Output/CWaylandOutput.cpp"
+            "src/WallpaperEngine/Render/Drivers/Output/CWaylandOutput.h"
+            "src/WallpaperEngine/Render/Drivers/Output/CWaylandOutputViewport.cpp"
+            "src/WallpaperEngine/Render/Drivers/Output/CWaylandOutputViewport.h"
+            "src/WallpaperEngine/Input/Drivers/CWaylandMouseInput.cpp"
+            "src/WallpaperEngine/Input/Drivers/CWaylandMouseInput.h"
+            "xdg-shell-protocol.c"
+            "wlr-layer-shell-unstable-v1-protocol.c")
+else()
+    set(WAYLAND_SOURCES "")
+endif()
+
 add_executable(
     linux-wallpaperengine
     main.cpp
@@ -105,6 +152,8 @@ add_executable(
     src/WallpaperEngine/Input/CInputContext.h
     src/WallpaperEngine/Input/CMouseInput.cpp
     src/WallpaperEngine/Input/CMouseInput.h
+    src/WallpaperEngine/Input/Drivers/CGLFWMouseInput.cpp
+    src/WallpaperEngine/Input/Drivers/CGLFWMouseInput.h
 
     src/WallpaperEngine/Render/Shaders/Variables/CShaderVariable.h
     src/WallpaperEngine/Render/Shaders/Variables/CShaderVariable.cpp
@@ -136,6 +185,10 @@ add_executable(
     src/WallpaperEngine/Render/Drivers/Output/CX11Output.h
     src/WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.cpp
     src/WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.h
+    src/WallpaperEngine/Render/Drivers/Output/COutputViewport.cpp
+    src/WallpaperEngine/Render/Drivers/Output/COutputViewport.h
+    src/WallpaperEngine/Render/Drivers/Output/CX11OutputViewport.cpp
+    src/WallpaperEngine/Render/Drivers/Output/CX11OutputViewport.h
     src/WallpaperEngine/Render/Drivers/CX11OpenGLDriver.h
     src/WallpaperEngine/Render/Drivers/CX11OpenGLDriver.cpp
     src/WallpaperEngine/Render/Drivers/CVideoDriver.h
@@ -268,6 +321,8 @@ add_executable(
 
     src/WallpaperEngine/Core/Objects/Images/Materials/CPass.cpp
     src/WallpaperEngine/Core/Objects/Images/Materials/CPass.h
+
+    ${WAYLAND_SOURCES}
 )
 
 target_link_libraries(linux-wallpaperengine
@@ -286,6 +341,14 @@ target_link_libraries(linux-wallpaperengine
     ${PULSEAUDIO_LIBRARY}
     glfw)
 
+if (ENABLE_WAYLAND)
+    target_link_libraries(linux-wallpaperengine pthread
+                                                wayland-cursor
+                                                wayland-client
+                                                wayland-egl
+                                                ${OPENGL_egl_LIBRARY})
+endif()
+
 file(CREATE_LINK linux-wallpaperengine wallengine SYMBOLIC)
 
 set(CMAKE_REQUIRED_LIBRARIES ${X11_LIBRARIES})

+ 285 - 0
protocols/wlr-layer-shell-unstable-v1.xml

@@ -0,0 +1,285 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_layer_shell_v1_unstable_v1">
+  <copyright>
+    Copyright © 2017 Drew DeVault
+
+    Permission to use, copy, modify, distribute, and sell this
+    software and its documentation for any purpose is hereby granted
+    without fee, provided that the above copyright notice appear in
+    all copies and that both that copyright notice and this permission
+    notice appear in supporting documentation, and that the name of
+    the copyright holders not be used in advertising or publicity
+    pertaining to distribution of the software without specific,
+    written prior permission.  The copyright holders make no
+    representations about the suitability of this software for any
+    purpose.  It is provided "as is" without express or implied
+    warranty.
+
+    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+    THIS SOFTWARE.
+  </copyright>
+
+  <interface name="zwlr_layer_shell_v1" version="1">
+    <description summary="create surfaces that are layers of the desktop">
+      Clients can use this interface to assign the surface_layer role to
+      wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+      rendered with a defined z-depth respective to each other. They may also be
+      anchored to the edges and corners of a screen and specify input handling
+      semantics. This interface should be suitable for the implementation of
+      many desktop shell components, and a broad number of other applications
+      that interact with the desktop.
+    </description>
+
+    <request name="get_layer_surface">
+      <description summary="create a layer_surface from a surface">
+        Create a layer surface for an existing surface. This assigns the role of
+        layer_surface, or raises a protocol error if another role is already
+        assigned.
+
+        Creating a layer surface from a wl_surface which has a buffer attached
+        or committed is a client error, and any attempts by a client to attach
+        or manipulate a buffer prior to the first layer_surface.configure call
+        must also be treated as errors.
+
+        You may pass NULL for output to allow the compositor to decide which
+        output to use. Generally this will be the one that the user most
+        recently interacted with.
+
+        Clients can specify a namespace that defines the purpose of the layer
+        surface.
+      </description>
+      <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
+      <arg name="surface" type="object" interface="wl_surface"/>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+      <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
+      <arg name="namespace" type="string" summary="namespace for the layer surface"/>
+    </request>
+
+    <enum name="error">
+      <entry name="role" value="0" summary="wl_surface has another role"/>
+      <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
+      <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
+    </enum>
+
+    <enum name="layer">
+      <description summary="available layers for surfaces">
+        These values indicate which layers a surface can be rendered in. They
+        are ordered by z depth, bottom-most first. Traditional shell surfaces
+        will typically be rendered between the bottom and top layers.
+        Fullscreen shell surfaces are typically rendered at the top layer.
+        Multiple surfaces can share a single layer, and ordering within a
+        single layer is undefined.
+      </description>
+
+      <entry name="background" value="0"/>
+      <entry name="bottom" value="1"/>
+      <entry name="top" value="2"/>
+      <entry name="overlay" value="3"/>
+    </enum>
+  </interface>
+
+  <interface name="zwlr_layer_surface_v1" version="1">
+    <description summary="layer metadata interface">
+      An interface that may be implemented by a wl_surface, for surfaces that
+      are designed to be rendered as a layer of a stacked desktop-like
+      environment.
+
+      Layer surface state (size, anchor, exclusive zone, margin, interactivity)
+      is double-buffered, and will be applied at the time wl_surface.commit of
+      the corresponding wl_surface is called.
+    </description>
+
+    <request name="set_size">
+      <description summary="sets the size of the surface">
+        Sets the size of the surface in surface-local coordinates. The
+        compositor will display the surface centered with respect to its
+        anchors.
+
+        If you pass 0 for either value, the compositor will assign it and
+        inform you of the assignment in the configure event. You must set your
+        anchor to opposite edges in the dimensions you omit; not doing so is a
+        protocol error. Both values are 0 by default.
+
+        Size is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="width" type="uint"/>
+      <arg name="height" type="uint"/>
+    </request>
+
+    <request name="set_anchor">
+      <description summary="configures the anchor point of the surface">
+        Requests that the compositor anchor the surface to the specified edges
+        and corners. If two orthoginal edges are specified (e.g. 'top' and
+        'left'), then the anchor point will be the intersection of the edges
+        (e.g. the top left corner of the output); otherwise the anchor point
+        will be centered on that edge, or in the center if none is specified.
+
+        Anchor is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="anchor" type="uint" enum="anchor"/>
+    </request>
+
+    <request name="set_exclusive_zone">
+      <description summary="configures the exclusive geometry of this surface">
+        Requests that the compositor avoids occluding an area of the surface
+        with other surfaces. The compositor's use of this information is
+        implementation-dependent - do not assume that this region will not
+        actually be occluded.
+
+        A positive value is only meaningful if the surface is anchored to an
+        edge, rather than a corner. The zone is the number of surface-local
+        coordinates from the edge that are considered exclusive.
+
+        Surfaces that do not wish to have an exclusive zone may instead specify
+        how they should interact with surfaces that do. If set to zero, the
+        surface indicates that it would like to be moved to avoid occluding
+        surfaces with a positive excluzive zone. If set to -1, the surface
+        indicates that it would not like to be moved to accommodate for other
+        surfaces, and the compositor should extend it all the way to the edges
+        it is anchored to.
+
+        For example, a panel might set its exclusive zone to 10, so that
+        maximized shell surfaces are not shown on top of it. A notification
+        might set its exclusive zone to 0, so that it is moved to avoid
+        occluding the panel, but shell surfaces are shown underneath it. A
+        wallpaper or lock screen might set their exclusive zone to -1, so that
+        they stretch below or over the panel.
+
+        The default value is 0.
+
+        Exclusive zone is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="zone" type="int"/>
+    </request>
+
+    <request name="set_margin">
+      <description summary="sets a margin from the anchor point">
+        Requests that the surface be placed some distance away from the anchor
+        point on the output, in surface-local coordinates. Setting this value
+        for edges you are not anchored to has no effect.
+
+        The exclusive zone includes the margin.
+
+        Margin is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="top" type="int"/>
+      <arg name="right" type="int"/>
+      <arg name="bottom" type="int"/>
+      <arg name="left" type="int"/>
+    </request>
+
+    <request name="set_keyboard_interactivity">
+      <description summary="requests keyboard events">
+        Set to 1 to request that the seat send keyboard events to this layer
+        surface. For layers below the shell surface layer, the seat will use
+        normal focus semantics. For layers above the shell surface layers, the
+        seat will always give exclusive keyboard focus to the top-most layer
+        which has keyboard interactivity set to true.
+
+        Layer surfaces receive pointer, touch, and tablet events normally. If
+        you do not want to receive them, set the input region on your surface
+        to an empty region.
+
+        Events is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="keyboard_interactivity" type="uint"/>
+    </request>
+
+    <request name="get_popup">
+      <description summary="assign this layer_surface as an xdg_popup parent">
+        This assigns an xdg_popup's parent to this layer_surface.  This popup
+        should have been created via xdg_surface::get_popup with the parent set
+        to NULL, and this request must be invoked before committing the popup's
+        initial state.
+
+        See the documentation of xdg_popup for more details about what an
+        xdg_popup is and how it is used.
+      </description>
+      <arg name="popup" type="object" interface="xdg_popup"/>
+    </request>
+
+    <request name="ack_configure">
+      <description summary="ack a configure event">
+        When a configure event is received, if a client commits the
+        surface in response to the configure event, then the client
+        must make an ack_configure request sometime before the commit
+        request, passing along the serial of the configure event.
+
+        If the client receives multiple configure events before it
+        can respond to one, it only has to ack the last configure event.
+
+        A client is not required to commit immediately after sending
+        an ack_configure request - it may even ack_configure several times
+        before its next surface commit.
+
+        A client may send multiple ack_configure requests before committing, but
+        only the last request sent before a commit indicates which configure
+        event the client really is responding to.
+      </description>
+      <arg name="serial" type="uint" summary="the serial from the configure event"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the layer_surface">
+        This request destroys the layer surface.
+      </description>
+    </request>
+
+    <event name="configure">
+      <description summary="suggest a surface change">
+        The configure event asks the client to resize its surface.
+
+        Clients should arrange their surface for the new states, and then send
+        an ack_configure request with the serial sent in this configure event at
+        some point before committing the new surface.
+
+        The client is free to dismiss all but the last configure event it
+        received.
+
+        The width and height arguments specify the size of the window in
+        surface-local coordinates.
+
+        The size is a hint, in the sense that the client is free to ignore it if
+        it doesn't resize, pick a smaller size (to satisfy aspect ratio or
+        resize in steps of NxM pixels). If the client picks a smaller size and
+        is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
+        surface will be centered on this axis.
+
+        If the width or height arguments are zero, it means the client should
+        decide its own window dimension.
+      </description>
+      <arg name="serial" type="uint"/>
+      <arg name="width" type="uint"/>
+      <arg name="height" type="uint"/>
+    </event>
+
+    <event name="closed">
+      <description summary="surface should be closed">
+        The closed event is sent by the compositor when the surface will no
+        longer be shown. The output may have been destroyed or the user may
+        have asked for it to be removed. Further changes to the surface will be
+        ignored. The client should destroy the resource after receiving this
+        event, and create a new surface if they so choose.
+      </description>
+    </event>
+
+    <enum name="error">
+      <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
+      <entry name="invalid_size" value="1" summary="size is invalid"/>
+      <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
+    </enum>
+
+    <enum name="anchor" bitfield="true">
+      <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
+      <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
+      <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
+      <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
+    </enum>
+  </interface>
+</protocol>

+ 2 - 2
src/External/Android/fft.cpp

@@ -26,8 +26,8 @@ namespace External::Android
  * half of the twiddle factors are stored. Although there are still ways to make
  * it even faster or smaller, it costs too much on one of the aspects.
  */
-#include <stdio.h>
-#include <stdint.h>
+#include <cstdio>
+#include <cstdint>
 #ifdef __arm__
 #include <machine/cpu-features.h>
 #endif

+ 18 - 18
src/WallpaperEngine/Application/CApplicationContext.cpp

@@ -12,22 +12,22 @@
 using namespace WallpaperEngine::Application;
 
 struct option long_options[] = {
-    { "screen-root", required_argument,  nullptr, 'r' },
-    { "bg", required_argument,           nullptr, 'b' },
-    { "window", required_argument,       nullptr, 'w' },
-    { "pkg", required_argument,          nullptr, 'p' },
-    { "dir", required_argument,          nullptr, 'd' },
-    { "silent", no_argument,             nullptr, 's' },
-    { "volume", required_argument,       nullptr, 'v' },
-    { "help", no_argument,               nullptr, 'h' },
-    { "fps", required_argument,          nullptr, 'f' },
-    { "assets-dir", required_argument,   nullptr, 'a' },
-    { "screenshot", required_argument,   nullptr, 'c' },
-    { "list-properties", no_argument,    nullptr, 'l' },
-    { "set-property", required_argument, nullptr, 'o' },
-    { "noautomute", no_argument,         nullptr, 'm' },
-    { "no-fullscreen-pause", no_argument,nullptr, 'n' },
-    { nullptr, 0,                        nullptr, 0 }
+    { "screen-root", required_argument,     nullptr, 'r' },
+    { "bg", required_argument,              nullptr, 'b' },
+    { "window", required_argument,          nullptr, 'w' },
+    { "pkg", required_argument,             nullptr, 'p' },
+    { "dir", required_argument,             nullptr, 'd' },
+    { "silent", no_argument,                nullptr, 's' },
+    { "volume", required_argument,          nullptr, 'v' },
+    { "help", no_argument,                  nullptr, 'h' },
+    { "fps", required_argument,             nullptr, 'f' },
+    { "assets-dir", required_argument,      nullptr, 'a' },
+    { "screenshot", required_argument,      nullptr, 'c' },
+    { "list-properties", no_argument,       nullptr, 'l' },
+    { "set-property", required_argument,    nullptr, 'o' },
+    { "noautomute", no_argument,            nullptr, 'm' },
+    { "no-fullscreen-pause", no_argument,   nullptr, 'n' },
+    { nullptr, 0,                           nullptr, 0 }
 };
 
 std::string stringPathFixes (const std::string& s)
@@ -124,13 +124,13 @@ CApplicationContext::CApplicationContext (int argc, char* argv[])
                 if (this->settings.render.mode == EXPLICIT_WINDOW)
                     sLog.exception ("Cannot run in both background and window mode");
 
-                this->settings.render.mode = X11_BACKGROUND;
+                this->settings.render.mode = DESKTOP_BACKGROUND;
                 lastScreen = optarg;
                 this->settings.general.screenBackgrounds[lastScreen] = "";
                 break;
 
             case 'w':
-                if (this->settings.render.mode == X11_BACKGROUND)
+                if (this->settings.render.mode == DESKTOP_BACKGROUND)
                     sLog.exception ("Cannot run in both background and window mode");
 
                 if (optarg != nullptr)

+ 2 - 2
src/WallpaperEngine/Application/CApplicationContext.h

@@ -25,8 +25,8 @@ namespace WallpaperEngine::Application
 		{
 			/** Default window mode */
 			NORMAL_WINDOW = 0,
-			/** Draw to X11 background */
-			X11_BACKGROUND = 1,
+			/** Draw to the window server desktop */
+			DESKTOP_BACKGROUND = 1,
 			/** Explicit window mode with specified geometry */
 			EXPLICIT_WINDOW = 2,
 		};

+ 96 - 81
src/WallpaperEngine/Application/CWallpaperApplication.cpp

@@ -3,17 +3,15 @@
 #include "Steam/FileSystem/FileSystem.h"
 #include "WallpaperEngine/Assets/CDirectory.h"
 #include "WallpaperEngine/Assets/CVirtualContainer.h"
-#include "WallpaperEngine/Audio/Drivers/CSDLAudioDriver.h"
 #include "WallpaperEngine/Core/CVideo.h"
 #include "WallpaperEngine/Logging/CLog.h"
 #include "WallpaperEngine/Render/CRenderContext.h"
-#include "WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.h"
-#include "WallpaperEngine/Render/Drivers/Output/CX11Output.h"
 #include "WallpaperEngine/Application/CApplicationState.h"
-#include "WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.h"
 #include "WallpaperEngine/Audio/Drivers/Detectors/CPulseAudioPlayingDetector.h"
+#include "WallpaperEngine/Input/Drivers/CGLFWMouseInput.h"
 
-#include <unistd.h>
+#include "WallpaperEngine/Input/Drivers/CWaylandMouseInput.h"
+#include "WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.h"
 
 float g_Time;
 float g_TimeLast;
@@ -29,6 +27,15 @@ namespace WallpaperEngine::Application
         this->setupProperties ();
     }
 
+    CWallpaperApplication::~CWallpaperApplication ()
+    {
+        delete context;
+        delete videoDriver;
+        delete audioContext;
+        delete audioDriver;
+        delete inputContext;
+    }
+
     void CWallpaperApplication::setupContainer (CCombinedContainer& container, const std::string& bg) const
     {
         std::filesystem::path basepath = bg;
@@ -227,126 +234,128 @@ namespace WallpaperEngine::Application
     )
     {
         // this should be getting called at the end of the frame, so the right thing should be bound already
-
-        int width = context.getOutput ()->getFullWidth ();
-        int height = context.getOutput ()->getFullHeight ();
-
-        // make room for storing the pixel data
-        uint8_t* buffer = new uint8_t[width * height * sizeof (uint8_t) * 3];
-        uint8_t* pixel = buffer;
-
-        // read the image into the buffer
-        glReadPixels (0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer);
+        int width = context.getOutput ().getFullWidth ();
+        int height = context.getOutput ().getFullHeight ();
 
         // build the output file with FreeImage
-        FIBITMAP* bitmap = FreeImage_Allocate (width, height, 24);
+        static FIBITMAP* bitmap = FreeImage_Allocate (width, height, 24);
         RGBQUAD color;
+        int xoffset = 0;
 
-        // now get access to the pixels
-        for (int y = height; y > 0; y--)
+        for (const auto& viewport : context.getOutput ().getViewports ())
         {
-            for (int x = 0; x < width; x++)
-            {
-                color.rgbRed = *pixel++;
-                color.rgbGreen = *pixel++;
-                color.rgbBlue = *pixel++;
+            // activate opengl context so we can read from the framebuffer
+            viewport.second->makeCurrent ();
+            // make room for storing the pixel of this viewport
+            uint8_t* buffer = new uint8_t[viewport.second->viewport.z * viewport.second->viewport.w * sizeof (uint8_t) * 3];
+            uint8_t* pixel = buffer;
+
+            // read the viewport data into the pixel buffer
+            glReadPixels (
+                viewport.second->viewport.x, viewport.second->viewport.y,
+                viewport.second->viewport.z, viewport.second->viewport.w,
+                GL_RGB, GL_UNSIGNED_BYTE, buffer
+            );
 
-                // set the pixel in the destination
-                FreeImage_SetPixelColor (bitmap, x, y, &color);
+            // now get access to the pixels
+            for (int y = viewport.second->viewport.w; y > 0; y--)
+            {
+                for (int x = 0; x < viewport.second->viewport.z; x++)
+                {
+                    color.rgbRed = *pixel++;
+                    color.rgbGreen = *pixel++;
+                    color.rgbBlue = *pixel++;
+
+                    // set the pixel in the destination
+                    FreeImage_SetPixelColor (bitmap, x + xoffset, context.getOutput ().renderVFlip() ? (viewport.second->viewport.w - y) : y, &color);
+                }
             }
+
+            if (viewport.second->single)
+                xoffset += viewport.second->viewport.z;
+
+            // free the buffer allocated for the viewport
+            delete[] buffer;
         }
 
         // finally save the file
         FreeImage_Save (format, bitmap, filename.c_str (), 0);
 
-        // free all the used memory
-        delete[] buffer;
-
         FreeImage_Unload (bitmap);
     }
 
     void CWallpaperApplication::show ()
     {
-        // initialize OpenGL driver
-        WallpaperEngine::Render::Drivers::CX11OpenGLDriver videoDriver ("wallpaperengine", this->m_context);
-        // initialize the input subsystem
-        WallpaperEngine::Input::CInputContext inputContext (videoDriver);
-        // output requested
-        WallpaperEngine::Render::Drivers::Output::COutput* output;
-        // fullscreen detector is common for the different render modes
-        WallpaperEngine::Render::Drivers::Detectors::CX11FullScreenDetector fullscreenDetector (this->m_context, videoDriver);
+#ifdef ENABLE_WAYLAND
+        const bool WAYLAND_DISPLAY = getenv ("WAYLAND_DISPLAY");
+
+        // setup the right video driver based on the environment and the startup mode requested
+        if (WAYLAND_DISPLAY && this->m_context.settings.render.mode == CApplicationContext::DESKTOP_BACKGROUND)
+        {
+            auto waylandDriver = new WallpaperEngine::Render::Drivers::CWaylandOpenGLDriver (this->m_context, *this);
+            inputContext = new WallpaperEngine::Input::CInputContext (new WallpaperEngine::Input::Drivers::CWaylandMouseInput (waylandDriver));
+
+            videoDriver = waylandDriver;
+        }
+        else
+#endif
+        {
+            auto x11Driver = new WallpaperEngine::Render::Drivers::CX11OpenGLDriver ("wallpaperengine", this->m_context, *this);
+            // no wayland detected, try the old X11 method
+            inputContext = new WallpaperEngine::Input::CInputContext (new WallpaperEngine::Input::Drivers::CGLFWMouseInput (x11Driver));
+
+            videoDriver = x11Driver;
+        }
+
         // stereo mix recorder for audio processing
         WallpaperEngine::Audio::Drivers::Recorders::CPulseAudioPlaybackRecorder audioRecorder;
         // audio playing detector
-        WallpaperEngine::Audio::Drivers::Detectors::CPulseAudioPlayingDetector audioDetector (this->m_context, fullscreenDetector);
+        WallpaperEngine::Audio::Drivers::Detectors::CPulseAudioPlayingDetector audioDetector (this->m_context, videoDriver->getFullscreenDetector ());
         // initialize sdl audio driver
-        WallpaperEngine::Audio::Drivers::CSDLAudioDriver audioDriver (this->m_context, audioDetector, audioRecorder);
+        audioDriver = new WallpaperEngine::Audio::Drivers::CSDLAudioDriver (this->m_context, audioDetector, audioRecorder);
         // initialize audio context
-        WallpaperEngine::Audio::CAudioContext audioContext (audioDriver);
-
-        // initialize the requested output
-        switch (this->m_context.settings.render.mode)
-        {
-            case CApplicationContext::EXPLICIT_WINDOW:
-            case CApplicationContext::NORMAL_WINDOW:
-                output = new WallpaperEngine::Render::Drivers::Output::CGLFWWindowOutput (this->m_context, videoDriver, fullscreenDetector);
-                break;
-
-            case CApplicationContext::X11_BACKGROUND:
-                output = new WallpaperEngine::Render::Drivers::Output::CX11Output (this->m_context, videoDriver, fullscreenDetector);
-                break;
-        }
-
+        audioContext = new WallpaperEngine::Audio::CAudioContext (*audioDriver);
         // initialize render context
-        WallpaperEngine::Render::CRenderContext context (output, videoDriver, inputContext, *this);
+        context = new WallpaperEngine::Render::CRenderContext (*videoDriver, *inputContext, *this);
 
         // set all the specific wallpapers required
         for (const auto& it : this->m_backgrounds)
-            context.setWallpaper (
+            context->setWallpaper (
                 it.first,
-                WallpaperEngine::Render::CWallpaper::fromWallpaper (it.second->getWallpaper (), context, audioContext)
+                WallpaperEngine::Render::CWallpaper::fromWallpaper (it.second->getWallpaper (), *context, *audioContext)
             );
 
         // set the default rendering wallpaper if available
         if (this->m_defaultBackground != nullptr)
-            context.setDefaultWallpaper (WallpaperEngine::Render::CWallpaper::fromWallpaper (
-                this->m_defaultBackground->getWallpaper (), context, audioContext
+            context->setDefaultWallpaper (WallpaperEngine::Render::CWallpaper::fromWallpaper (
+                this->m_defaultBackground->getWallpaper (), *context, *audioContext
             ));
 
-        float startTime, endTime, minimumTime = 1.0f / this->m_context.settings.render.maximumFPS;
-        time_t seconds;
-        struct tm* timeinfo;
+        static time_t seconds;
+        static struct tm* timeinfo;
 
-        while (!videoDriver.closeRequested () && this->m_context.state.general.keepRunning)
+        while (this->m_context.state.general.keepRunning && !videoDriver->closeRequested ())
         {
             // update g_Daytime
             time (&seconds);
             timeinfo = localtime(&seconds);
             g_Daytime = ((timeinfo->tm_hour * 60) + timeinfo->tm_min) / (24.0 * 60.0);
-            // update audio recorder
-            audioDriver.update ();
-            // update input information
-            inputContext.update ();
+
             // keep track of the previous frame's time
             g_TimeLast = g_Time;
             // calculate the current time value
-            g_Time = videoDriver.getRenderTime ();
-            // get the start time of the frame
-            startTime = g_Time;
-            // render the scene
-            context.render ();
-            // get the end time of the frame
-            endTime = videoDriver.getRenderTime ();
-
-            // ensure the frame time is correct to not overrun FPS
-            if ((endTime - startTime) < minimumTime)
-                usleep ((minimumTime - (endTime - startTime)) * CLOCKS_PER_SEC);
-
-            if (!this->m_context.settings.screenshot.take || videoDriver.getFrameCounter () != 5)
+            g_Time = videoDriver->getRenderTime ();
+            // update audio recorder
+            audioDriver->update ();
+            // update input information
+            inputContext->update ();
+            // process driver events
+            videoDriver->dispatchEventQueue ();
+
+            if (!this->m_context.settings.screenshot.take || videoDriver->getFrameCounter () < 5)
                 continue;
 
-            this->takeScreenshot (context, this->m_context.settings.screenshot.path, this->m_context.settings.screenshot.format);
-            // disable screenshot just in case the counter overflows
+            this->takeScreenshot (*context, this->m_context.settings.screenshot.path, this->m_context.settings.screenshot.format);
             this->m_context.settings.screenshot.take = false;
         }
 
@@ -358,6 +367,12 @@ namespace WallpaperEngine::Application
         SDL_Quit ();
     }
 
+    void CWallpaperApplication::update(Render::Drivers::Output::COutputViewport* viewport)
+    {
+        // render the scene
+        context->render (viewport);
+    }
+
     void CWallpaperApplication::signal (int signal)
     {
         this->m_context.state.general.keepRunning = false;

+ 33 - 0
src/WallpaperEngine/Application/CWallpaperApplication.h

@@ -9,6 +9,24 @@
 #include "WallpaperEngine/Render/CWallpaper.h"
 #include "WallpaperEngine/Render/CRenderContext.h"
 #include "WallpaperEngine/Render/Drivers/CX11OpenGLDriver.h"
+#ifdef ENABLE_WAYLAND
+#include "WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.h"
+#endif
+
+#include "WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.h"
+#ifdef ENABLE_WAYLAND
+#include "WallpaperEngine/Render/Drivers/Detectors/CWaylandFullScreenDetector.h"
+#endif
+
+#include "WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.h"
+#include "WallpaperEngine/Render/Drivers/Output/CX11Output.h"
+#ifdef ENABLE_WAYLAND
+#include "WallpaperEngine/Render/Drivers/Output/CWaylandOutput.h"
+#endif
+
+#include "WallpaperEngine/Audio/Drivers/CSDLAudioDriver.h"
+
+#include "WallpaperEngine/Input/CInputContext.h"
 
 namespace WallpaperEngine::Application
 {
@@ -21,6 +39,7 @@ namespace WallpaperEngine::Application
     {
     public:
         explicit CWallpaperApplication (CApplicationContext& context);
+        ~CWallpaperApplication ();
 
         /**
          * Shows the application until it's closed
@@ -44,6 +63,14 @@ namespace WallpaperEngine::Application
          * @return The current application context
          */
         [[nodiscard]] CApplicationContext& getContext () const;
+        /**
+         * Renders a frame
+        */
+        void update(Render::Drivers::Output::COutputViewport* viewport);
+        /**
+         * Gets the output
+        */
+        WallpaperEngine::Render::Drivers::Output::COutput* getOutput() const;
 
     private:
         /**
@@ -89,5 +116,11 @@ namespace WallpaperEngine::Application
         CApplicationContext& m_context;
         /** Maps screens to backgrounds */
         std::map <std::string, Core::CProject*> m_backgrounds;
+
+        WallpaperEngine::Render::Drivers::CVideoDriver* videoDriver;
+        WallpaperEngine::Input::CInputContext* inputContext;
+        WallpaperEngine::Audio::Drivers::CSDLAudioDriver* audioDriver;
+        WallpaperEngine::Render::CRenderContext* context;
+        WallpaperEngine::Audio::CAudioContext* audioContext;
     };
 }

+ 2 - 0
src/WallpaperEngine/Assets/CFileEntry.h

@@ -1,5 +1,7 @@
 #pragma once
 
+#include <cstdint>
+
 namespace WallpaperEngine::Assets
 {
     /**

+ 2 - 2
src/WallpaperEngine/Audio/Drivers/Detectors/CAudioPlayingDetector.cpp

@@ -4,7 +4,7 @@ namespace WallpaperEngine::Audio::Drivers::Detectors
 {
     CAudioPlayingDetector::CAudioPlayingDetector (
         Application::CApplicationContext& appContext,
-        Render::Drivers::Detectors::CFullScreenDetector& fullscreenDetector) :
+        const Render::Drivers::Detectors::CFullScreenDetector& fullscreenDetector) :
         m_applicationContext (appContext),
         m_fullscreenDetector (fullscreenDetector),
         m_isPlaying (false)
@@ -21,7 +21,7 @@ namespace WallpaperEngine::Audio::Drivers::Detectors
         return this->m_applicationContext;
     }
 
-    Render::Drivers::Detectors::CFullScreenDetector& CAudioPlayingDetector::getFullscreenDetector ()
+    const Render::Drivers::Detectors::CFullScreenDetector& CAudioPlayingDetector::getFullscreenDetector () const
     {
         return this->m_fullscreenDetector;
     }

+ 3 - 3
src/WallpaperEngine/Audio/Drivers/Detectors/CAudioPlayingDetector.h

@@ -23,7 +23,7 @@ namespace WallpaperEngine
         class CAudioPlayingDetector
         {
         public:
-            CAudioPlayingDetector (Application::CApplicationContext& appContext, Render::Drivers::Detectors::CFullScreenDetector& fullscreenDetector);
+            CAudioPlayingDetector (Application::CApplicationContext& appContext, const Render::Drivers::Detectors::CFullScreenDetector& fullscreenDetector);
 
             /**
              * @return If any kind of sound is currently playing on the default audio device
@@ -48,13 +48,13 @@ namespace WallpaperEngine
             /**
              * @return The fullscreen detector used
              */
-            [[nodiscard]] Render::Drivers::Detectors::CFullScreenDetector& getFullscreenDetector ();
+            [[nodiscard]] const Render::Drivers::Detectors::CFullScreenDetector& getFullscreenDetector () const;
 
         private:
             bool m_isPlaying;
 
             Application::CApplicationContext& m_applicationContext;
-            Render::Drivers::Detectors::CFullScreenDetector& m_fullscreenDetector;
+            const Render::Drivers::Detectors::CFullScreenDetector& m_fullscreenDetector;
         };
     }
 }

+ 2 - 2
src/WallpaperEngine/Audio/Drivers/Detectors/CPulseAudioPlayingDetector.cpp

@@ -18,7 +18,7 @@ namespace WallpaperEngine::Audio::Drivers::Detectors
         // get processid
         const char* value = pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_PROCESS_ID);
 
-        if (atoi (value) != getpid () && pa_cvolume_avg (&info->volume) != PA_VOLUME_MUTED)
+        if (value && atoi (value) != getpid () && pa_cvolume_avg (&info->volume) != PA_VOLUME_MUTED)
             detector->setIsPlaying (true);
     }
 
@@ -34,7 +34,7 @@ namespace WallpaperEngine::Audio::Drivers::Detectors
 
     CPulseAudioPlayingDetector::CPulseAudioPlayingDetector (
         Application::CApplicationContext& appContext,
-        Render::Drivers::Detectors::CFullScreenDetector& fullscreenDetector) :
+        const Render::Drivers::Detectors::CFullScreenDetector& fullscreenDetector) :
         CAudioPlayingDetector (appContext, fullscreenDetector),
         m_mainloop (nullptr),
         m_mainloopApi (nullptr),

+ 1 - 1
src/WallpaperEngine/Audio/Drivers/Detectors/CPulseAudioPlayingDetector.h

@@ -10,7 +10,7 @@ namespace WallpaperEngine::Audio::Drivers::Detectors
     class CPulseAudioPlayingDetector : public CAudioPlayingDetector
     {
     public:
-        explicit CPulseAudioPlayingDetector (Application::CApplicationContext& appContext, Render::Drivers::Detectors::CFullScreenDetector&);
+        explicit CPulseAudioPlayingDetector (Application::CApplicationContext& appContext, const Render::Drivers::Detectors::CFullScreenDetector&);
         ~CPulseAudioPlayingDetector ();
 
         void update () override;

+ 4 - 5
src/WallpaperEngine/Input/CInputContext.cpp

@@ -1,20 +1,19 @@
 #include "CInputContext.h"
-#include "WallpaperEngine/Render/Drivers/CX11OpenGLDriver.h"
 
 using namespace WallpaperEngine::Input;
 using namespace WallpaperEngine::Render::Drivers;
 
-CInputContext::CInputContext (CX11OpenGLDriver& videoDriver) :
-    m_mouse (videoDriver.getWindow ())
+CInputContext::CInputContext (CMouseInput* mouseInput) :
+    m_mouse (mouseInput)
 {
 }
 
 void CInputContext::update ()
 {
-    this->m_mouse.update ();
+    this->m_mouse->update ();
 }
 
 const CMouseInput& CInputContext::getMouseInput () const
 {
-    return this->m_mouse;
+    return *this->m_mouse;
 }

+ 7 - 4
src/WallpaperEngine/Input/CInputContext.h

@@ -1,11 +1,10 @@
 #pragma once
 
-#include "WallpaperEngine/Render/Drivers/CX11OpenGLDriver.h"
 #include "CMouseInput.h"
 
 namespace WallpaperEngine::Render::Drivers
 {
-    class CX11OpenGLDriver;
+    class CVideoDriver;
 }
 
 namespace WallpaperEngine::Input
@@ -13,12 +12,16 @@ namespace WallpaperEngine::Input
     class CInputContext
     {
     public:
-        explicit CInputContext (Render::Drivers::CX11OpenGLDriver& videoDriver);
+        explicit CInputContext (CMouseInput* mouseInput);
+
+        /**
+         * Updates input information
+         */
         void update ();
 
         [[nodiscard]] const CMouseInput& getMouseInput () const;
 
     private:
-        CMouseInput m_mouse;
+        CMouseInput* m_mouse;
     };
 }

+ 0 - 14
src/WallpaperEngine/Input/CMouseInput.cpp

@@ -1,14 +0,0 @@
-#include <glm/common.hpp>
-#include "CMouseInput.h"
-
-using namespace WallpaperEngine::Input;
-
-CMouseInput::CMouseInput (GLFWwindow* window) : position (), m_mousePosition (), m_window (window) {}
-
-void CMouseInput::update ()
-{
-    // update current mouse position
-    glfwGetCursorPos (this->m_window, &this->m_mousePosition.x, &this->m_mousePosition.y);
-    // interpolate to the new position
-    this->position = glm::mix (this->position, this->m_mousePosition, 1.0);
-}

+ 2 - 16
src/WallpaperEngine/Input/CMouseInput.h

@@ -1,7 +1,6 @@
 #pragma once
 
 #include <glm/vec2.hpp>
-#include "GLFW/glfw3.h"
 
 namespace WallpaperEngine::Input
 {
@@ -11,28 +10,15 @@ namespace WallpaperEngine::Input
     class CMouseInput
     {
     public:
-        explicit CMouseInput(GLFWwindow* window);
-
         /**
          * Takes current mouse position and updates it
          */
-        void update ();
+        virtual void update () = 0;
 
         /**
          * The virtual pointer's position
          */
-        glm::dvec2 position;
-
-    private:
-        /**
-         * The GLFW window to get mouse position from
-         */
-        GLFWwindow* m_window;
-
-        /**
-         * The current mouse position
-         */
-        glm::dvec2 m_mousePosition;
+        virtual glm::dvec2 position () const = 0;
     };
 }
 

+ 24 - 0
src/WallpaperEngine/Input/Drivers/CGLFWMouseInput.cpp

@@ -0,0 +1,24 @@
+#include <glm/common.hpp>
+#include "CGLFWMouseInput.h"
+
+#include "WallpaperEngine/Render/Drivers/CX11OpenGLDriver.h"
+
+using namespace WallpaperEngine::Input::Drivers;
+
+CGLFWMouseInput::CGLFWMouseInput (Render::Drivers::CX11OpenGLDriver* driver) :
+    m_reportedPosition (),
+    m_mousePosition (),
+    m_driver (driver) {}
+
+void CGLFWMouseInput::update ()
+{
+    // update current mouse position
+    glfwGetCursorPos (this->m_driver->getWindow (), &this->m_mousePosition.x, &this->m_mousePosition.y);
+    // interpolate to the new position
+    this->m_reportedPosition = glm::mix (this->m_reportedPosition, this->m_mousePosition, 1.0);
+}
+
+glm::dvec2 CGLFWMouseInput::position() const
+{
+    return this->m_reportedPosition;
+}

+ 42 - 0
src/WallpaperEngine/Input/Drivers/CGLFWMouseInput.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include "WallpaperEngine/Input/CMouseInput.h"
+
+#include <glm/vec2.hpp>
+
+namespace WallpaperEngine::Render::Drivers
+{
+    class CX11OpenGLDriver;
+}
+
+namespace WallpaperEngine::Input::Drivers
+{
+    /**
+     * Handles mouse input for the background
+     */
+    class CGLFWMouseInput : public CMouseInput
+    {
+    public:
+        explicit CGLFWMouseInput(Render::Drivers::CX11OpenGLDriver* driver);
+
+        /**
+         * Takes current mouse position and updates it
+         */
+        void update () override;
+
+        /**
+         * The virtual pointer's position
+         */
+        glm::dvec2 position () const override;
+
+    private:
+        Render::Drivers::CX11OpenGLDriver* m_driver;
+
+        /**
+         * The current mouse position
+         */
+        glm::dvec2 m_mousePosition;
+        glm::dvec2 m_reportedPosition;
+    };
+}
+

+ 21 - 0
src/WallpaperEngine/Input/Drivers/CWaylandMouseInput.cpp

@@ -0,0 +1,21 @@
+#include <glm/common.hpp>
+#include "CWaylandMouseInput.h"
+
+using namespace WallpaperEngine::Input::Drivers;
+
+CWaylandMouseInput::CWaylandMouseInput(WallpaperEngine::Render::Drivers::CWaylandOpenGLDriver* driver) :
+    waylandDriver (driver)
+{
+}
+
+void CWaylandMouseInput::update ()
+{
+}
+
+glm::dvec2 CWaylandMouseInput::position() const
+{
+    if (waylandDriver->viewportInFocus && waylandDriver->viewportInFocus->rendering)
+        return waylandDriver->viewportInFocus->mousePos;
+
+    return {0, 0};
+}

+ 41 - 0
src/WallpaperEngine/Input/Drivers/CWaylandMouseInput.h

@@ -0,0 +1,41 @@
+#ifdef ENABLE_WAYLAND
+#pragma once
+
+#include "WallpaperEngine/Input/CMouseInput.h"
+#include "WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.h"
+
+#include <glm/vec2.hpp>
+#include "GLFW/glfw3.h"
+
+
+namespace WallpaperEngine::Input::Drivers
+{
+    /**
+     * Handles mouse input for the background
+     */
+    class CWaylandMouseInput : public CMouseInput
+    {
+    public:
+        explicit CWaylandMouseInput(WallpaperEngine::Render::Drivers::CWaylandOpenGLDriver* driver);
+
+        /**
+         * Takes current mouse position and updates it
+         */
+        void update () override;
+
+        /**
+         * The virtual pointer's position
+         */
+        glm::dvec2 position () const override;
+
+    private:
+        /**
+         * Wayland: Driver
+        */
+        WallpaperEngine::Render::Drivers::CWaylandOpenGLDriver* waylandDriver = nullptr;
+
+        glm::dvec2 pos;
+    };
+}
+
+#endif /* ENABLE_WAYLAND */

+ 19 - 38
src/WallpaperEngine/Render/CRenderContext.cpp

@@ -10,11 +10,10 @@
 namespace WallpaperEngine::Render
 {
     CRenderContext::CRenderContext (
-        const Drivers::Output::COutput* output, Drivers::CVideoDriver& driver, Input::CInputContext& input,
+        Drivers::CVideoDriver& driver, Input::CInputContext& input,
         CWallpaperApplication& app
     ) :
         m_defaultWallpaper (nullptr),
-        m_output (output),
         m_driver (driver),
         m_app (app),
         m_input (input),
@@ -22,48 +21,30 @@ namespace WallpaperEngine::Render
     {
     }
 
-    void CRenderContext::render ()
+    void CRenderContext::render (Drivers::Output::COutputViewport* viewport)
     {
-        bool firstFrame = true;
-        bool renderFrame = true;
+        viewport->makeCurrent ();
 
-        for (const auto& cur : this->m_output->getViewports ())
-        {
 #if !NDEBUG
-            std::string str = "Rendering to output " + cur.first;
+        std::string str = "Rendering to output " + viewport->name;
 
-            glPushDebugGroup (GL_DEBUG_SOURCE_APPLICATION, 0, -1, str.c_str ());
+        glPushDebugGroup (GL_DEBUG_SOURCE_APPLICATION, 0, -1, str.c_str ());
 #endif /* DEBUG */
 
-            // search the background in the viewport selection
-            auto ref = this->m_wallpapers.find (cur.first);
-
-            // render the background
-            if (ref != this->m_wallpapers.end ())
-                ref->second->render (cur.second.viewport, this->m_output->renderVFlip (), renderFrame, firstFrame);
-            else
-                this->m_defaultWallpaper->render (
-                    cur.second.viewport, this->m_output->renderVFlip (), renderFrame, firstFrame
-                );
-            // scenes need to render a new frame for each viewport as they produce different results
-            // but videos should only be rendered once per group of viewports
-            firstFrame = false;
+        // search the background in the viewport selection
+        auto ref = this->m_wallpapers.find (viewport->name);
+
+        // render the background
+        if (ref != this->m_wallpapers.end ())
+            ref->second->render (viewport->viewport, this->getOutput ().renderVFlip ());
+        else
+            this->m_defaultWallpaper->render (viewport->viewport, this->getOutput ().renderVFlip ());
+
 #if !NDEBUG
-            glPopDebugGroup ();
+        glPopDebugGroup ();
 #endif /* DEBUG */
-        }
-
-        // read the full texture into the image
-        if (this->m_output->haveImageBuffer ())
-            glReadPixels (
-                0, 0, this->m_output->getFullWidth (), this->m_output->getFullHeight (), GL_BGRA, GL_UNSIGNED_BYTE,
-                this->m_output->getImageBuffer ()
-            );
-
-        // update the output with the given image
-        this->m_output->updateRender ();
-        // finally swap buffers
-        this->m_driver.swapBuffers ();
+
+        viewport->swapOutput ();
     }
 
     void CRenderContext::setDefaultWallpaper (CWallpaper* wallpaper)
@@ -91,9 +72,9 @@ namespace WallpaperEngine::Render
         return this->m_driver;
     }
 
-    const Drivers::Output::COutput* CRenderContext::getOutput () const
+    const Drivers::Output::COutput& CRenderContext::getOutput () const
     {
-        return this->m_output;
+        return this->m_driver.getOutput ();
     }
 
     const ITexture* CRenderContext::resolveTexture (const std::string& name)

+ 5 - 5
src/WallpaperEngine/Render/CRenderContext.h

@@ -9,6 +9,7 @@
 #include "WallpaperEngine/Input/CMouseInput.h"
 #include "WallpaperEngine/Render/Drivers/CVideoDriver.h"
 #include "WallpaperEngine/Render/Drivers/Output/COutput.h"
+#include "WallpaperEngine/Render/Drivers/Output/COutputViewport.h"
 
 namespace WallpaperEngine
 {
@@ -26,6 +27,7 @@ namespace WallpaperEngine
             namespace Output
             {
                 class COutput;
+                class COutputViewport;
             }
         }
 
@@ -35,15 +37,15 @@ namespace WallpaperEngine
         class CRenderContext
         {
         public:
-            CRenderContext (const Drivers::Output::COutput* output, Drivers::CVideoDriver& driver, Input::CInputContext& input, CWallpaperApplication& app);
+            CRenderContext (Drivers::CVideoDriver& driver, Input::CInputContext& input, CWallpaperApplication& app);
 
-            void render ();
+            void render (Drivers::Output::COutputViewport* viewport);
             void setDefaultWallpaper (CWallpaper* wallpaper);
             void setWallpaper (const std::string& display, CWallpaper* wallpaper);
             [[nodiscard]] Input::CInputContext& getInputContext () const;
             [[nodiscard]] const CWallpaperApplication& getApp () const;
             [[nodiscard]] const Drivers::CVideoDriver& getDriver () const;
-            [[nodiscard]] const Drivers::Output::COutput* getOutput () const;
+            [[nodiscard]] const Drivers::Output::COutput& getOutput () const;
             const ITexture* resolveTexture (const std::string& name);
 
         private:
@@ -59,8 +61,6 @@ namespace WallpaperEngine
             CWallpaperApplication& m_app;
             /** Texture cache for the render */
             CTextureCache* m_textureCache;
-            /** Output driver that describes how the wallpapers are rendered */
-            const Drivers::Output::COutput* m_output;
         };
     }
 }

+ 1 - 1
src/WallpaperEngine/Render/CScene.cpp

@@ -248,7 +248,7 @@ void CScene::renderFrame (glm::ivec4 viewport)
 void CScene::updateMouse (glm::ivec4 viewport)
 {
     // update virtual mouse position first
-    glm::dvec2 position = this->getContext ().getInputContext ().getMouseInput ().position;
+    glm::dvec2 position = this->getContext ().getInputContext ().getMouseInput ().position();
     // TODO: PROPERLY TRANSLATE THESE TO WHAT'S VISIBLE ON SCREEN (FOR BACKGROUNDS THAT DO NOT EXACTLY FIT ON SCREEN)
 
     // rollover the position to the last

+ 2 - 2
src/WallpaperEngine/Render/CVideo.cpp

@@ -8,7 +8,7 @@ using namespace WallpaperEngine::Render;
 
 void* get_proc_address (void* ctx, const char* name)
 {
-    return reinterpret_cast <void*> (glfwGetProcAddress (name));
+    return static_cast<CVideo*> (ctx)->getContext ().getDriver ().getProcAddress (name);
 }
 
 CVideo::CVideo (Core::CVideo* video, CRenderContext& context, CAudioContext& audioContext) :
@@ -40,7 +40,7 @@ CVideo::CVideo (Core::CVideo* video, CRenderContext& context, CAudioContext& aud
     mpv_set_option (this->m_mpv, "volume", MPV_FORMAT_DOUBLE, &volume);
 
     // initialize gl context for mpv
-    mpv_opengl_init_params gl_init_params {get_proc_address, nullptr};
+    mpv_opengl_init_params gl_init_params {get_proc_address, this};
     mpv_render_param params[] {
         {MPV_RENDER_PARAM_API_TYPE, const_cast <char*> (MPV_RENDER_API_TYPE_OPENGL)},
         {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},

+ 2 - 6
src/WallpaperEngine/Render/CWallpaper.cpp

@@ -211,10 +211,9 @@ void CWallpaper::setDestinationFramebuffer (GLuint framebuffer)
     this->m_destFramebuffer = framebuffer;
 }
 
-void CWallpaper::render (glm::ivec4 viewport, bool vflip, bool renderFrame, bool newFrame)
+void CWallpaper::render (glm::ivec4 viewport, bool vflip)
 {
-    if (renderFrame)
-        this->renderFrame (viewport);
+    this->renderFrame (viewport);
 
     uint32_t projectionWidth = this->getWidth ();
     uint32_t projectionHeight = this->getHeight ();
@@ -293,9 +292,6 @@ void CWallpaper::render (glm::ivec4 viewport, bool vflip, bool renderFrame, bool
 
     glBindVertexArray (this->m_vaoBuffer);
 
-    if (newFrame)
-        glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
     glDisable (GL_BLEND);
     glDisable (GL_DEPTH_TEST);
     // do not use any shader

+ 1 - 1
src/WallpaperEngine/Render/CWallpaper.h

@@ -37,7 +37,7 @@ namespace WallpaperEngine::Render
         /**
          * Performs a render pass of the wallpaper
          */
-        void render (glm::ivec4 viewport, bool vflip, bool renderFrame = true, bool newFrame = true);
+        void render (glm::ivec4 viewport, bool vflip);
 
         /**
          * @return The container to resolve files for this wallpaper

+ 14 - 0
src/WallpaperEngine/Render/Drivers/CVideoDriver.cpp

@@ -1,3 +1,17 @@
 #include "CVideoDriver.h"
 
 using namespace WallpaperEngine::Render::Drivers;
+
+CVideoDriver::CVideoDriver (CWallpaperApplication& app) :
+    m_app (app)
+{
+}
+
+CVideoDriver::~CVideoDriver ()
+{
+}
+
+CWallpaperApplication& CVideoDriver::getApp () const
+{
+    return this->m_app;
+}

+ 40 - 8
src/WallpaperEngine/Render/Drivers/CVideoDriver.h

@@ -2,20 +2,39 @@
 
 #include <glm/vec4.hpp>
 #include <glm/vec2.hpp>
+#include <string>
+#include "WallpaperEngine/Render/Drivers/Output/COutput.h"
+
+namespace WallpaperEngine::Application
+{
+    class CWallpaperApplication;
+}
 
 namespace WallpaperEngine::Render::Drivers
 {
+    namespace Detectors
+    {
+        class CFullScreenDetector;
+    }
+
     class CVideoDriver
     {
     public:
+        CVideoDriver (CWallpaperApplication& app);
+        virtual ~CVideoDriver ();
+
+        /**
+         * @return The fullscreen detector this video driver uses
+         */
+        [[nodiscard]] virtual Detectors::CFullScreenDetector& getFullscreenDetector () = 0;
         /**
-         * @return The window handle used by this video driver
+         * @return The current output in use
          */
-        virtual void* getWindowHandle () const = 0;
+        [[nodiscard]] virtual Output::COutput& getOutput () = 0;
         /**
          * @return The time that has passed since the driver started
          */
-        virtual float getRenderTime () const = 0;
+        [[nodiscard]] virtual float getRenderTime () const = 0;
         /**
          * @return If a close was requested by the OS
          */
@@ -39,14 +58,27 @@ namespace WallpaperEngine::Render::Drivers
         /**
          * @return The size of the framebuffer available for the driver
          */
-        virtual glm::ivec2 getFramebufferSize () const = 0;
+        [[nodiscard]] virtual glm::ivec2 getFramebufferSize () const = 0;
         /**
-         * Performs buffer swapping
+         * @return The number of rendered frames since the start of the driver
          */
-        virtual void swapBuffers () = 0;
+        [[nodiscard]] virtual uint32_t getFrameCounter () const = 0;
         /**
-         * @return The number of rendered frames since the start of the driver
+         * @param name
+         * @return GetProcAddress for this video driver
+         */
+        [[nodiscard]] virtual void* getProcAddress (const char* name) const = 0;
+        /**
+         * Process events on the driver and renders a frame
+        */
+        virtual void dispatchEventQueue() = 0;
+        /**
+         * @return The app that owns this driver
          */
-        virtual uint32_t getFrameCounter () const = 0;
+        [[nodiscard]] CWallpaperApplication& getApp () const;
+
+    private:
+        /** App that owns this driver */
+        CWallpaperApplication& m_app;
     };
 }

+ 401 - 0
src/WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.cpp

@@ -0,0 +1,401 @@
+#include "CWaylandOpenGLDriver.h"
+#include "WallpaperEngine/Application/CWallpaperApplication.h"
+
+#include <FreeImage.h>
+
+#include "common.h"
+
+#define class _class
+#define namespace _namespace
+#define static
+extern "C" {
+#include "xdg-shell-protocol.h"
+#include "wlr-layer-shell-unstable-v1-protocol.h"
+}
+#undef class
+#undef namespace
+#undef static
+
+#include <string.h>
+#include <unistd.h>
+
+using namespace WallpaperEngine::Render::Drivers;
+
+static void handlePointerEnter (
+    void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface,
+    wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+    const auto driver = static_cast <CWaylandOpenGLDriver*> (data);
+    const auto viewport = driver->surfaceToViewport (surface);
+    driver->viewportInFocus = viewport;
+    wl_surface_set_buffer_scale(viewport->cursorSurface, viewport->scale);
+    wl_surface_attach(viewport->cursorSurface, wl_cursor_image_get_buffer(viewport->pointer->images[0]), 0, 0);
+    wl_pointer_set_cursor(wl_pointer, serial, viewport->cursorSurface, viewport->pointer->images[0]->hotspot_x, viewport->pointer->images[0]->hotspot_y);
+    wl_surface_commit(viewport->cursorSurface);
+}
+
+static void handlePointerLeave(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface)
+{
+}
+
+static void handlePointerAxis(void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value)
+{
+}
+
+static void handlePointerMotion(
+    void* data, struct wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+    const auto driver = static_cast <CWaylandOpenGLDriver*> (data);
+
+    auto x = wl_fixed_to_double(surface_x);
+    auto y = wl_fixed_to_double(surface_y);
+
+    if (!driver->viewportInFocus)
+        return;
+
+    driver->viewportInFocus->mousePos = {x * driver->viewportInFocus->scale, y * driver->viewportInFocus->scale};
+}
+
+static void handlePointerButton(
+    void* data, struct wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state)
+{
+}
+
+const struct wl_pointer_listener pointerListener =
+{
+    .enter = handlePointerEnter,
+    .leave = handlePointerLeave,
+    .motion = handlePointerMotion,
+    .button = handlePointerButton,
+    .axis = handlePointerAxis
+};
+
+static void handleCapabilities(void* data, wl_seat* wl_seat, uint32_t capabilities)
+{
+    if (capabilities & WL_SEAT_CAPABILITY_POINTER)
+        wl_pointer_add_listener(wl_seat_get_pointer(wl_seat), &pointerListener, data);
+}
+
+const struct wl_seat_listener seatListener =
+{
+    .capabilities = handleCapabilities
+};
+
+static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
+{
+    const auto driver = static_cast <CWaylandOpenGLDriver*> (data);
+
+    if (strcmp(interface, wl_compositor_interface.name) == 0) {
+        driver->getWaylandContext()->compositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, 4);
+    } else if (strcmp(interface, wl_shm_interface.name) == 0) {
+        driver->getWaylandContext()->shm = (wl_shm*)wl_registry_bind(registry, name, &wl_shm_interface, 1);
+    } else if (strcmp(interface, wl_output_interface.name) == 0) {
+        driver->m_screens.emplace_back (new WallpaperEngine::Render::Drivers::Output::CWaylandOutputViewport (driver, name, registry));
+    } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
+        driver->getWaylandContext()->layerShell = (zwlr_layer_shell_v1*)wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1);
+    } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+        driver->getWaylandContext()->seat = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, 1);
+        wl_seat_add_listener(driver->getWaylandContext()->seat, &seatListener, driver);
+    }
+}
+
+static void handleGlobalRemoved(void *data, struct wl_registry *registry, uint32_t id)
+{
+    // todo: outputs
+}
+
+const struct wl_registry_listener registryListener =
+{
+    .global = handleGlobal,
+    .global_remove = handleGlobalRemoved,
+};
+
+void CWaylandOpenGLDriver::initEGL()
+{
+    const char* CLIENT_EXTENSIONS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+    if (!CLIENT_EXTENSIONS)
+        sLog.exception("Failed to query EGL Extensions");
+
+    const std::string CLIENTEXTENSIONS = std::string(CLIENT_EXTENSIONS);
+    
+    if (CLIENTEXTENSIONS.find("EGL_EXT_platform_base") == std::string::npos)
+        sLog.exception("EGL_EXT_platform_base not supported by EGL!");
+
+    if (CLIENTEXTENSIONS.find("EGL_EXT_platform_wayland") == std::string::npos)
+        sLog.exception("EGL_EXT_platform_wayland not supported by EGL!");
+
+    PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
+    m_eglContext.eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
+
+    if (!eglGetPlatformDisplayEXT || !m_eglContext.eglCreatePlatformWindowSurfaceEXT)
+        sLog.exception("EGL did not return EXT proc pointers!");
+
+    auto deinitEGL = [&] () -> void {
+        eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+        if (m_eglContext.display)
+            eglTerminate(m_eglContext.display);
+        eglReleaseThread();
+    };
+
+    m_eglContext.display = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, m_waylandContext.display, nullptr);
+
+    if (m_eglContext.display == EGL_NO_DISPLAY)
+    {
+        deinitEGL ();
+        sLog.exception ("eglGetPlatformDisplayEXT failed!");
+    }
+
+    if (!eglInitialize(m_eglContext.display, nullptr, nullptr))
+    {
+        deinitEGL ();
+        sLog.exception ("eglInitialize failed!");
+    }
+
+    const std::string CLIENTEXTENSIONSPOSTINIT = std::string(eglQueryString(m_eglContext.display, EGL_EXTENSIONS));
+
+    if (CLIENTEXTENSIONSPOSTINIT.find("EGL_KHR_create_context") == std::string::npos)
+    {
+        deinitEGL ();
+        sLog.exception ("EGL_KHR_create_context not supported!");
+    }
+
+    EGLint matchedConfigs = 0;
+    const EGLint CONFIG_ATTRIBUTES[] = {
+        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+        EGL_RED_SIZE, 1,
+        EGL_GREEN_SIZE, 1,
+        EGL_BLUE_SIZE, 1,
+        EGL_SAMPLES, 4,
+        EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+        EGL_NONE,
+    };
+
+    if (!eglChooseConfig(m_eglContext.display, CONFIG_ATTRIBUTES, &m_eglContext.config, 1, &matchedConfigs))
+    {
+        deinitEGL ();
+        sLog.exception ("eglChooseConfig failed!");
+    }
+
+    if (matchedConfigs == 0)
+    {
+        deinitEGL ();
+        sLog.exception ("eglChooseConfig failed! (matched 0 configs)");
+    }
+
+    if (!eglBindAPI(EGL_OPENGL_API))
+    {
+        deinitEGL ();
+        sLog.exception ("eglBindAPI failed!");
+    }
+
+    const EGLint CONTEXT_ATTRIBUTES[] = {
+        EGL_CONTEXT_MAJOR_VERSION_KHR, 3,
+        EGL_CONTEXT_MINOR_VERSION_KHR, 3,
+        EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
+        EGL_NONE,
+    };
+
+    m_eglContext.context = eglCreateContext(m_eglContext.display, m_eglContext.config, EGL_NO_CONTEXT, CONTEXT_ATTRIBUTES);
+
+    if (m_eglContext.context == EGL_NO_CONTEXT)
+    {
+        deinitEGL ();
+        sLog.error("eglCreateContext error " + std::to_string(eglGetError()));
+        sLog.exception("eglCreateContext failed!");
+    }
+}
+
+void CWaylandOpenGLDriver::onLayerClose(Output::CWaylandOutputViewport* viewport)
+{
+    sLog.error ("Compositor closed our LS, freeing data...");
+
+    if (viewport->eglSurface)
+        eglDestroySurface (m_eglContext.display, viewport->eglSurface);
+
+    if (viewport->eglWindow)
+        wl_egl_window_destroy(viewport->eglWindow);
+
+    if (viewport->layerSurface)
+        zwlr_layer_surface_v1_destroy(viewport->layerSurface);
+
+    if (viewport->surface)
+        wl_surface_destroy(viewport->surface);
+
+    // remove the output from the list
+    std::remove (this->m_screens.begin (), this->m_screens.end (), viewport);
+
+    // reset the viewports
+    this->getOutput ().reset ();
+
+    // finally free memory used by the viewport
+    delete viewport;
+}
+
+CWaylandOpenGLDriver::CWaylandOpenGLDriver(CApplicationContext& context, CWallpaperApplication& app) :
+    m_frameCounter(0),
+    m_fullscreenDetector (context, *this),
+    m_output (context, *this),
+    m_requestedExit (false),
+    m_context (context),
+    CVideoDriver (app)
+{
+    m_waylandContext.display = wl_display_connect (nullptr);
+
+    if (!m_waylandContext.display)
+        sLog.exception ("Failed to query wayland display");
+
+    m_waylandContext.registry = wl_display_get_registry(m_waylandContext.display);
+    wl_registry_add_listener(m_waylandContext.registry, &registryListener, this);
+
+    wl_display_dispatch(m_waylandContext.display);
+    wl_display_roundtrip(m_waylandContext.display);
+
+    if (!m_waylandContext.compositor || !m_waylandContext.shm || !m_waylandContext.layerShell || this->m_screens.empty())
+        sLog.exception ("Failed to bind to required interfaces");
+
+    initEGL();
+
+    bool any = false;
+
+    for (auto& o : this->m_screens)
+    {
+        const auto cur = context.settings.general.screenBackgrounds.find (o->name);
+
+        if (cur == context.settings.general.screenBackgrounds.end ())
+            continue;
+
+        o->setupLS ();
+        any = true;
+    }
+
+    if (!any)
+        sLog.exception("No outputs could be initialized, please check the parameters and try again");
+
+    GLenum result = glewInit ();
+
+    if (result != GLEW_OK)
+        sLog.error("Failed to initialize GLEW: ", glewGetErrorString (result));
+
+    FreeImage_Initialise (TRUE);
+}
+
+CWaylandOpenGLDriver::~CWaylandOpenGLDriver ()
+{
+    // stop EGL
+    eglMakeCurrent (EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+    if (m_eglContext.context != EGL_NO_CONTEXT)
+        eglDestroyContext (m_eglContext.display, m_eglContext.context);
+
+    eglTerminate (m_eglContext.display);
+    eglReleaseThread ();
+
+    // disconnect from wayland display
+    if (this->m_waylandContext.display)
+        wl_display_disconnect (this->m_waylandContext.display);
+}
+
+void CWaylandOpenGLDriver::dispatchEventQueue()
+{
+    static bool initialized = false;
+
+    if (!initialized)
+    {
+        initialized = true;
+
+        for (const auto& viewport : this->getOutput ().getViewports ())
+            this->getApp ().update (viewport.second);
+    }
+
+    // TODO: FRAMETIME CONTROL SHOULD GO BACK TO THE CWALLPAPAERAPPLICATION ONCE ACTUAL PARTICLES ARE IMPLEMENTED
+    // TODO: AS THOSE, MORE THAN LIKELY, WILL REQUIRE OF A DIFFERENT PROCESSING RATE
+
+    // TODO: WRITE A NON-BLOCKING VERSION OF THIS ONCE PARTICLE SIMULATION STARTS WORKING
+    // TODO: OTHERWISE wl_display_dispatch WILL BLOCK IF NO SURFACES ARE BEING DRAWN
+    static float startTime, endTime, minimumTime = 1.0f / this->m_context.settings.render.maximumFPS;
+    // get the start time of the frame
+    startTime = this->getRenderTime ();
+
+    if (wl_display_dispatch(m_waylandContext.display) == -1)
+        m_requestedExit = true;
+
+    m_frameCounter ++;
+
+    endTime = this->getRenderTime ();
+
+    // ensure the frame time is correct to not overrun FPS
+    if ((endTime - startTime) < minimumTime)
+        usleep ((minimumTime - (endTime - startTime)) * CLOCKS_PER_SEC);
+}
+
+Detectors::CFullScreenDetector& CWaylandOpenGLDriver::getFullscreenDetector ()
+{
+    return this->m_fullscreenDetector;
+}
+
+Output::COutput& CWaylandOpenGLDriver::getOutput ()
+{
+    return this->m_output;
+}
+
+float CWaylandOpenGLDriver::getRenderTime () const
+{
+    return (float)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - renderStart).count() / 1000000.0;
+}
+
+bool CWaylandOpenGLDriver::closeRequested ()
+{
+    return this->m_requestedExit;
+}
+
+void CWaylandOpenGLDriver::resizeWindow (glm::ivec2 size)
+{
+}
+
+void CWaylandOpenGLDriver::resizeWindow (glm::ivec4 sizeandpos)
+{
+}
+
+void CWaylandOpenGLDriver::showWindow ()
+{
+}
+
+void CWaylandOpenGLDriver::hideWindow ()
+{
+}
+
+glm::ivec2 CWaylandOpenGLDriver::getFramebufferSize () const
+{
+    return glm::ivec2{0, 0};
+}
+
+uint32_t CWaylandOpenGLDriver::getFrameCounter () const
+{
+    return m_frameCounter;
+}
+
+CWaylandOpenGLDriver::SEGLContext* CWaylandOpenGLDriver::getEGLContext ()
+{
+    return &this->m_eglContext;
+}
+
+void* CWaylandOpenGLDriver::getProcAddress (const char* name) const
+{
+    return reinterpret_cast <void*> (eglGetProcAddress (name));
+}
+
+CWaylandOpenGLDriver::SWaylandContext* CWaylandOpenGLDriver::getWaylandContext ()
+{
+    return &this->m_waylandContext;
+}
+
+Output::CWaylandOutputViewport* CWaylandOpenGLDriver::surfaceToViewport(wl_surface* surface)
+{
+    for (auto& o : m_screens)
+    {
+        if (o->surface == surface)
+            return o;
+    }
+
+    return nullptr;
+}

+ 114 - 0
src/WallpaperEngine/Render/Drivers/CWaylandOpenGLDriver.h

@@ -0,0 +1,114 @@
+#ifdef ENABLE_WAYLAND
+#pragma once
+
+#include <wayland-client.h>
+#include <wayland-cursor.h>
+#include <wayland-egl.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GL/glew.h>
+
+#include "WallpaperEngine/Render/Drivers/CVideoDriver.h"
+#include "WallpaperEngine/Application/CApplicationContext.h"
+#include "WallpaperEngine/Render/Drivers/Detectors/CWaylandFullScreenDetector.h"
+#include "WallpaperEngine/Render/Drivers/Output/CWaylandOutputViewport.h"
+#include "WallpaperEngine/Render/Drivers/Output/CWaylandOutput.h"
+
+namespace WallpaperEngine::Application
+{
+    class CApplicationContext;
+    class CWallpaperApplication;
+}
+
+namespace WallpaperEngine::Input::Drivers
+{
+    class CWaylandMouseInput;
+}
+
+struct zwlr_layer_shell_v1;
+struct zwlr_layer_surface_v1;
+
+namespace WallpaperEngine::Render::Drivers
+{
+    using namespace WallpaperEngine::Application;
+    using namespace WallpaperEngine::Input::Drivers;
+    class CWaylandOpenGLDriver;
+
+    namespace Output
+    {
+        class CWaylandOutputViewport;
+        class CWaylandOutput;
+    }
+
+    class CWaylandOpenGLDriver : public CVideoDriver
+    {
+        friend class Output::CWaylandOutput;
+        friend class CWaylandMouseInput;
+    public:
+        struct SEGLContext
+        {
+            EGLDisplay display = nullptr;
+            EGLConfig config = nullptr;
+            EGLContext context = nullptr;
+            PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT = nullptr;
+        };
+
+        struct SWaylandContext
+        {
+            wl_display* display = nullptr;
+            wl_registry* registry = nullptr;
+            wl_compositor* compositor = nullptr;
+            wl_shm* shm = nullptr;
+            zwlr_layer_shell_v1* layerShell = nullptr;
+            wl_seat* seat = nullptr;
+        };
+
+        explicit CWaylandOpenGLDriver (CApplicationContext& context, CWallpaperApplication& app);
+        ~CWaylandOpenGLDriver();
+
+        [[nodiscard]] Detectors::CFullScreenDetector& getFullscreenDetector () override;
+        [[nodiscard]] Output::COutput& getOutput () override;
+        float getRenderTime () const override;
+        bool closeRequested () override;
+        void resizeWindow (glm::ivec2 size) override;
+        void resizeWindow (glm::ivec4 sizeandpos) override;
+        void showWindow () override;
+        void hideWindow () override;
+        glm::ivec2 getFramebufferSize () const override;
+        uint32_t getFrameCounter () const override;
+        void dispatchEventQueue() override;
+        [[nodiscard]] void* getProcAddress (const char* name) const override;
+
+        void onLayerClose(Output::CWaylandOutputViewport*);
+        Output::CWaylandOutputViewport* surfaceToViewport(wl_surface*);
+
+        Output::CWaylandOutputViewport* viewportInFocus = nullptr;
+
+        [[nodiscard]] SEGLContext* getEGLContext ();
+        [[nodiscard]] SWaylandContext* getWaylandContext ();
+
+
+        /** List of available screens */
+        std::vector <Output::CWaylandOutputViewport*> m_screens;
+
+    private:
+        /** Fullscreen detection used by this driver */
+        Detectors::CWaylandFullScreenDetector m_fullscreenDetector;
+        /** The output used by the driver */
+        Output::CWaylandOutput m_output;
+        /** The EGL context in use */
+        SEGLContext m_eglContext;
+        /** The Wayland context in use */
+        SWaylandContext m_waylandContext;
+        mutable bool m_requestedExit;
+
+        void initEGL();
+        void finishEGL();
+
+        uint32_t m_frameCounter;
+        CApplicationContext& m_context;
+
+        std::chrono::high_resolution_clock::time_point renderStart = std::chrono::high_resolution_clock::now();
+    };
+}
+#endif /* ENABLE_WAYLAND */

+ 60 - 7
src/WallpaperEngine/Render/Drivers/CX11OpenGLDriver.cpp

@@ -1,10 +1,13 @@
 #include "CX11OpenGLDriver.h"
 #include "common.h"
+#include "WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.h"
 #include <FreeImage.h>
 
 #define GLFW_EXPOSE_NATIVE_X11
 #include <GLFW/glfw3native.h>
 
+#include <unistd.h>
+
 using namespace WallpaperEngine::Render::Drivers;
 
 void CustomGLFWErrorHandler (int errorCode, const char* reason)
@@ -12,8 +15,11 @@ void CustomGLFWErrorHandler (int errorCode, const char* reason)
     sLog.error ("GLFW error ", errorCode, ": ", reason);
 }
 
-CX11OpenGLDriver::CX11OpenGLDriver (const char* windowTitle, CApplicationContext& context) :
-    m_frameCounter (0)
+CX11OpenGLDriver::CX11OpenGLDriver (const char* windowTitle, CApplicationContext& context, CWallpaperApplication& app) :
+    m_frameCounter (0),
+    m_fullscreenDetector (context, *this),
+    m_context (context),
+    CVideoDriver (app)
 {
     glfwSetErrorCallback (CustomGLFWErrorHandler);
 
@@ -60,6 +66,17 @@ CX11OpenGLDriver::CX11OpenGLDriver (const char* windowTitle, CApplicationContext
 
     // initialize free image
     FreeImage_Initialise (TRUE);
+
+    // setup output
+    if (context.settings.render.mode == CApplicationContext::EXPLICIT_WINDOW ||
+        context.settings.render.mode == CApplicationContext::NORMAL_WINDOW)
+    {
+        m_output = new WallpaperEngine::Render::Drivers::Output::CGLFWWindowOutput (context, *this);
+    }
+    else
+    {
+        m_output = new WallpaperEngine::Render::Drivers::Output::CX11Output (context, *this);
+    }
 }
 
 CX11OpenGLDriver::~CX11OpenGLDriver ()
@@ -68,9 +85,14 @@ CX11OpenGLDriver::~CX11OpenGLDriver ()
     FreeImage_DeInitialise();
 }
 
-void* CX11OpenGLDriver::getWindowHandle () const
+Detectors::CFullScreenDetector& CX11OpenGLDriver::getFullscreenDetector ()
+{
+    return this->m_fullscreenDetector;
+}
+
+Output::COutput& CX11OpenGLDriver::getOutput ()
 {
-    return reinterpret_cast <void*> (glfwGetX11Window (this->m_window));
+    return *this->m_output;
 }
 
 float CX11OpenGLDriver::getRenderTime () const
@@ -113,19 +135,50 @@ glm::ivec2 CX11OpenGLDriver::getFramebufferSize () const
     return size;
 }
 
-void CX11OpenGLDriver::swapBuffers ()
+uint32_t CX11OpenGLDriver::getFrameCounter () const
+{
+    return this->m_frameCounter;
+}
+
+void CX11OpenGLDriver::dispatchEventQueue()
 {
+    static float startTime, endTime, minimumTime = 1.0f / this->m_context.settings.render.maximumFPS;
+    // get the start time of the frame
+    startTime = this->getRenderTime ();
+    // clear the screen
+    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    for (const auto& viewport : this->m_output->getViewports ())
+        this->getApp ().update (viewport.second);
+
+    // read the full texture into the image
+    if (this->m_output->haveImageBuffer ())
+        glReadPixels (
+            0, 0, this->m_output->getFullWidth (), this->m_output->getFullHeight (), GL_BGRA, GL_UNSIGNED_BYTE,
+            this->m_output->getImageBuffer ()
+        );
+
+    // TODO: FRAMETIME CONTROL SHOULD GO BACK TO THE CWALLPAPAERAPPLICATION ONCE ACTUAL PARTICLES ARE IMPLEMENTED
+    // TODO: AS THOSE, MORE THAN LIKELY, WILL REQUIRE OF A DIFFERENT PROCESSING RATE
+    // update the output with the given image
+    this->m_output->updateRender ();
     // do buffer swapping first
     glfwSwapBuffers (this->m_window);
     // poll for events
     glfwPollEvents ();
     // increase frame counter
     this->m_frameCounter ++;
+    // get the end time of the frame
+    endTime = this->getRenderTime ();
+
+    // ensure the frame time is correct to not overrun FPS
+    if ((endTime - startTime) < minimumTime)
+        usleep ((minimumTime - (endTime - startTime)) * CLOCKS_PER_SEC);
 }
 
-uint32_t CX11OpenGLDriver::getFrameCounter () const
+void* CX11OpenGLDriver::getProcAddress (const char* name) const
 {
-    return this->m_frameCounter;
+    return reinterpret_cast <void*> (glfwGetProcAddress (name));
 }
 
 GLFWwindow* CX11OpenGLDriver::getWindow ()

+ 16 - 6
src/WallpaperEngine/Render/Drivers/CX11OpenGLDriver.h

@@ -4,10 +4,15 @@
 #include <GLFW/glfw3.h>
 #include "WallpaperEngine/Render/Drivers/CVideoDriver.h"
 #include "WallpaperEngine/Application/CApplicationContext.h"
+#include "WallpaperEngine/Render/Drivers/Detectors/CFullScreenDetector.h"
+#include "WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.h"
+#include "WallpaperEngine/Render/Drivers/Output/CX11Output.h"
+#include "WallpaperEngine/Application/CWallpaperApplication.h"
 
 namespace WallpaperEngine::Application
 {
     class CApplicationContext;
+    class CWallpaperApplication;
 }
 
 namespace WallpaperEngine::Render::Drivers
@@ -17,23 +22,28 @@ namespace WallpaperEngine::Render::Drivers
     class CX11OpenGLDriver : public CVideoDriver
     {
     public:
-        explicit CX11OpenGLDriver (const char* windowTitle, CApplicationContext& context);
+        explicit CX11OpenGLDriver (const char* windowTitle, CApplicationContext& context, CWallpaperApplication& app);
         ~CX11OpenGLDriver();
 
-        void* getWindowHandle () const;
-        float getRenderTime () const override;
+        [[nodiscard]] Detectors::CFullScreenDetector& getFullscreenDetector () override;
+        [[nodiscard]] Output::COutput& getOutput () override;
+        [[nodiscard]] float getRenderTime () const override;
         bool closeRequested () override;
         void resizeWindow (glm::ivec2 size) override;
         void resizeWindow (glm::ivec4 sizeandpos) override;
         void showWindow () override;
         void hideWindow () override;
-        glm::ivec2 getFramebufferSize () const override;
-        void swapBuffers () override;
-        uint32_t getFrameCounter () const override;
+        [[nodiscard]] glm::ivec2 getFramebufferSize () const override;
+        [[nodiscard]] uint32_t getFrameCounter () const override;
+        void dispatchEventQueue() override;
+        [[nodiscard]] void* getProcAddress (const char* name) const override;
 
         GLFWwindow* getWindow ();
 
     private:
+        Detectors::CX11FullScreenDetector m_fullscreenDetector;
+        CApplicationContext& m_context;
+        Output::COutput* m_output;
         GLFWwindow* m_window;
         uint32_t m_frameCounter;
     };

+ 20 - 0
src/WallpaperEngine/Render/Drivers/Detectors/CWaylandFullScreenDetector.cpp

@@ -0,0 +1,20 @@
+#include "CWaylandFullScreenDetector.h"
+
+using namespace WallpaperEngine::Render::Drivers::Detectors;
+
+CWaylandFullScreenDetector::CWaylandFullScreenDetector (Application::CApplicationContext& appContext, CWaylandOpenGLDriver& driver) :
+    CFullScreenDetector (appContext)
+{
+}
+
+CWaylandFullScreenDetector::~CWaylandFullScreenDetector () {
+    ;
+}
+
+bool CWaylandFullScreenDetector::anythingFullscreen () const {
+    return false; // todo
+}
+
+void CWaylandFullScreenDetector::reset () {
+    ;
+}

+ 28 - 0
src/WallpaperEngine/Render/Drivers/Detectors/CWaylandFullScreenDetector.h

@@ -0,0 +1,28 @@
+#ifdef ENABLE_WAYLAND
+#pragma once
+
+#include <string>
+#include <vector>
+#include <glm/vec4.hpp>
+
+#include "CFullScreenDetector.h"
+#include "WallpaperEngine/Render/Drivers/CVideoDriver.h"
+
+namespace WallpaperEngine::Render::Drivers
+{
+    class CWaylandOpenGLDriver;
+
+    namespace Detectors
+    {
+        class CWaylandFullScreenDetector : public CFullScreenDetector
+        {
+        public:
+            CWaylandFullScreenDetector (Application::CApplicationContext& appContext, CWaylandOpenGLDriver& driver);
+            ~CWaylandFullScreenDetector ();
+
+            [[nodiscard]] bool anythingFullscreen () const override;
+            void reset () override;
+        };
+    }
+}
+#endif /* ENABLE_WAYLAND */

+ 3 - 2
src/WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.cpp

@@ -4,6 +4,7 @@
 #include <X11/Xlib.h>
 #include <X11/extensions/Xrandr.h>
 
+#include "WallpaperEngine/Render/Drivers/CX11OpenGLDriver.h"
 
 namespace WallpaperEngine::Render::Drivers::Detectors
 {
@@ -31,7 +32,7 @@ namespace WallpaperEngine::Render::Drivers::Detectors
         return 0;
     }
 
-    CX11FullScreenDetector::CX11FullScreenDetector (Application::CApplicationContext& appContext, CVideoDriver& driver) :
+    CX11FullScreenDetector::CX11FullScreenDetector (Application::CApplicationContext& appContext, CX11OpenGLDriver& driver) :
         CFullScreenDetector (appContext),
         m_driver (driver)
     {
@@ -67,7 +68,7 @@ namespace WallpaperEngine::Render::Drivers::Detectors
         if (!XQueryTree (this->m_display, this->m_root, &_, &_, &children, &nchildren))
             return false;
 
-        auto ourWindow = reinterpret_cast <Window> (this->m_driver.getWindowHandle ());
+        auto ourWindow = reinterpret_cast <Window> (this->m_driver.getWindow ());
         Window parentWindow;
 
         {

+ 27 - 24
src/WallpaperEngine/Render/Drivers/Detectors/CX11FullScreenDetector.h

@@ -5,34 +5,37 @@
 #include <glm/vec4.hpp>
 
 #include "CFullScreenDetector.h"
-#include "WallpaperEngine/Render/Drivers/CVideoDriver.h"
-
 #include <X11/Xlib.h>
 
-namespace WallpaperEngine::Render::Drivers::Detectors
+namespace WallpaperEngine::Render::Drivers
 {
-    class CX11FullScreenDetector : public CFullScreenDetector
-    {
-    public:
-        CX11FullScreenDetector (Application::CApplicationContext& appContext, CVideoDriver& driver);
-        ~CX11FullScreenDetector ();
-
-        [[nodiscard]] bool anythingFullscreen () const override;
-        void reset () override;
+    class CX11OpenGLDriver;
 
-    private:
-        void initialize ();
-        void stop ();
-
-        struct ScreenInfo
+    namespace Detectors
+    {
+        class CX11FullScreenDetector : public CFullScreenDetector
         {
-            glm::ivec4 viewport;
-            std::string name;
+        public:
+            CX11FullScreenDetector (Application::CApplicationContext& appContext, CX11OpenGLDriver& driver);
+            ~CX11FullScreenDetector ();
+
+            [[nodiscard]] bool anythingFullscreen () const override;
+            void reset () override;
+
+        private:
+            void initialize ();
+            void stop ();
+
+            struct ScreenInfo
+            {
+                glm::ivec4 viewport;
+                std::string name;
+            };
+
+            Display* m_display;
+            Window m_root;
+            std::vector <ScreenInfo> m_screens;
+            CX11OpenGLDriver& m_driver;
         };
-
-        Display* m_display;
-        Window m_root;
-        std::vector <ScreenInfo> m_screens;
-        CVideoDriver& m_driver;
-    };
+    }
 }

+ 6 - 6
src/WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.cpp

@@ -1,6 +1,7 @@
 #include <GLFW/glfw3.h>
 #include "CGLFWWindowOutput.h"
 #include "WallpaperEngine/Logging/CLog.h"
+#include "CX11OutputViewport.h"
 
 #include <unistd.h>
 
@@ -8,9 +9,8 @@
 
 using namespace WallpaperEngine::Render::Drivers::Output;
 
-CGLFWWindowOutput::CGLFWWindowOutput (CApplicationContext& context, CVideoDriver& driver, Detectors::CFullScreenDetector& detector) :
-    COutput (context, detector),
-    m_driver (driver)
+CGLFWWindowOutput::CGLFWWindowOutput (CApplicationContext& context, CVideoDriver& driver) :
+    COutput (context, driver)
 {
     if (
         this->m_context.settings.render.mode != Application::CApplicationContext::NORMAL_WINDOW &&
@@ -34,7 +34,7 @@ CGLFWWindowOutput::CGLFWWindowOutput (CApplicationContext& context, CVideoDriver
     }
 
     // register the default viewport
-    this->m_viewports ["default"] = {{0, 0, this->m_fullWidth, this->m_fullHeight}, "default"};
+    this->m_viewports ["default"] = new CX11OutputViewport {{0, 0, this->m_fullWidth, this->m_fullHeight}, "default"};
 }
 
 void CGLFWWindowOutput::repositionWindow ()
@@ -78,9 +78,9 @@ void CGLFWWindowOutput::updateRender () const
     this->m_fullHeight = this->m_driver.getFramebufferSize ().y;
 
     // update the default viewport
-    this->m_viewports ["default"] = {{0, 0, this->m_fullWidth, this->m_fullHeight}, "default"};
+    this->m_viewports ["default"]->viewport = {0, 0, this->m_fullWidth, this->m_fullHeight};
 
     // check for fullscreen windows and wait until there's none fullscreen
-    while (this->m_detector.anythingFullscreen () && this->m_context.state.general.keepRunning)
+    while (this->m_driver.getFullscreenDetector ().anythingFullscreen () && this->m_context.state.general.keepRunning)
         usleep (FULLSCREEN_CHECK_WAIT_TIME);
 }

+ 1 - 3
src/WallpaperEngine/Render/Drivers/Output/CGLFWWindowOutput.h

@@ -8,7 +8,7 @@ namespace WallpaperEngine::Render::Drivers::Output
     class CGLFWWindowOutput : public COutput
     {
     public:
-        CGLFWWindowOutput (CApplicationContext& context, CVideoDriver& driver, Detectors::CFullScreenDetector& detector);
+        CGLFWWindowOutput (CApplicationContext& context, CVideoDriver& driver);
 
         void reset () override;
         bool renderVFlip () const override;
@@ -19,7 +19,5 @@ namespace WallpaperEngine::Render::Drivers::Output
 
     private:
         void repositionWindow ();
-
-        CVideoDriver& m_driver;
     };
 }

+ 3 - 3
src/WallpaperEngine/Render/Drivers/Output/COutput.cpp

@@ -2,13 +2,13 @@
 
 using namespace WallpaperEngine::Render::Drivers::Output;
 
-COutput::COutput (CApplicationContext& context, Detectors::CFullScreenDetector& detector) :
+COutput::COutput (CApplicationContext& context, CVideoDriver& driver) :
     m_context (context),
-    m_detector (detector)
+    m_driver (driver)
 {
 }
 
-const std::map <std::string, COutput::ScreenInfo>& COutput::getViewports () const
+const std::map <std::string, COutputViewport*>& COutput::getViewports () const
 {
     return this->m_viewports;
 }

+ 31 - 30
src/WallpaperEngine/Render/Drivers/Output/COutput.h

@@ -14,41 +14,42 @@ namespace WallpaperEngine::Application
     class CApplicationContext;
 }
 
-namespace WallpaperEngine::Render::Drivers::Detectors
+namespace WallpaperEngine::Render::Drivers
 {
-    class CFullScreenDetector;
-}
+    class CVideoDriver;
 
-namespace WallpaperEngine::Render::Drivers::Output
-{
-    class COutput
+    namespace Detectors
     {
-    public:
-        COutput (CApplicationContext& context, Detectors::CFullScreenDetector& detector);
-
-        virtual void reset () = 0;
+        class CFullScreenDetector;
+    }
 
-        int getFullWidth () const;
-        int getFullHeight () const;
+    namespace Output
+    {
+        class COutputViewport;
 
-        struct ScreenInfo
+        class COutput
         {
-            glm::ivec4 viewport;
-            std::string name;
+        public:
+            COutput (CApplicationContext& context, CVideoDriver& driver);
+
+            virtual void reset () = 0;
+
+            int getFullWidth () const;
+            int getFullHeight () const;
+
+            virtual bool renderVFlip () const = 0;
+            virtual bool renderMultiple () const = 0;
+            virtual bool haveImageBuffer () const = 0;
+            const std::map <std::string, COutputViewport*>& getViewports () const;
+            virtual void* getImageBuffer () const = 0;
+            virtual void updateRender () const = 0;
+
+        protected:
+            mutable int m_fullWidth;
+            mutable int m_fullHeight;
+            mutable std::map <std::string, COutputViewport*> m_viewports;
+            CApplicationContext& m_context;
+            CVideoDriver& m_driver;
         };
-
-        virtual bool renderVFlip () const = 0;
-        virtual bool renderMultiple () const = 0;
-        virtual bool haveImageBuffer () const = 0;
-        const std::map <std::string, ScreenInfo>& getViewports () const;
-        virtual void* getImageBuffer () const = 0;
-        virtual void updateRender () const = 0;
-
-    protected:
-        mutable int m_fullWidth;
-        mutable int m_fullHeight;
-        mutable std::map <std::string, ScreenInfo> m_viewports;
-        CApplicationContext& m_context;
-        Detectors::CFullScreenDetector& m_detector;
-    };
+    }
 }

+ 16 - 0
src/WallpaperEngine/Render/Drivers/Output/COutputViewport.cpp

@@ -0,0 +1,16 @@
+#include "COutputViewport.h"
+
+#include <utility>
+
+using namespace WallpaperEngine::Render::Drivers::Output;
+
+COutputViewport::COutputViewport (glm::ivec4 viewport, std::string name, bool single) :
+    viewport (viewport),
+    name (std::move(name)),
+    single (single)
+{
+}
+
+COutputViewport::~COutputViewport ()
+{
+}

+ 30 - 0
src/WallpaperEngine/Render/Drivers/Output/COutputViewport.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <string>
+#include <glm/vec4.hpp>
+
+namespace WallpaperEngine::Render::Drivers::Output
+{
+    class COutputViewport
+    {
+    public:
+        COutputViewport (glm::ivec4 viewport, std::string name, bool single = false);
+        virtual ~COutputViewport ();
+
+        glm::ivec4 viewport;
+        std::string name;
+
+        /** Whether this viewport is single in the framebuffer or shares space with more viewports */
+        bool single;
+
+        /**
+         * Activates output's context for drawing
+         */
+        virtual void makeCurrent () = 0;
+
+        /**
+         * Swaps buffers to present data on the viewport
+         */
+        virtual void swapOutput () = 0;
+    };
+}

+ 66 - 0
src/WallpaperEngine/Render/Drivers/Output/CWaylandOutput.cpp

@@ -0,0 +1,66 @@
+#include "common.h"
+#include "CWaylandOutput.h"
+#include "../CWaylandOpenGLDriver.h"
+#include "WallpaperEngine/Application/CWallpaperApplication.h"
+
+using namespace WallpaperEngine::Render::Drivers::Output;
+
+CWaylandOutput::CWaylandOutput (CApplicationContext& context, CWaylandOpenGLDriver& driver) :
+    COutput (context, driver)
+{
+    updateViewports();
+}
+
+CWaylandOutput::~CWaylandOutput ()
+{
+}
+
+void CWaylandOutput::updateViewports()
+{
+    m_viewports.clear();
+    const auto PDRIVER = (CWaylandOpenGLDriver*)&m_driver;
+    glm::ivec2 fullw = {0,0};
+    for (auto& o : PDRIVER->m_screens)
+    {
+        if (!o->layerSurface)
+            continue;
+
+        m_viewports[o->name] = o;
+
+        fullw = fullw + glm::ivec2{o->size.x * o->scale, 0};
+        if (o->size.y > fullw.y)
+            fullw.y = o->size.y;
+    }
+
+    m_fullWidth = fullw.x;
+    m_fullHeight = fullw.y;
+}
+
+void CWaylandOutput::reset ()
+{
+    updateViewports();
+}
+
+bool CWaylandOutput::renderVFlip () const
+{
+    return true;
+}
+
+bool CWaylandOutput::renderMultiple () const
+{
+    return false; // todo
+}
+
+bool CWaylandOutput::haveImageBuffer () const
+{
+    return false;
+}
+
+void* CWaylandOutput::getImageBuffer () const
+{
+    return nullptr;
+}
+
+void CWaylandOutput::updateRender () const
+{
+}

+ 36 - 0
src/WallpaperEngine/Render/Drivers/Output/CWaylandOutput.h

@@ -0,0 +1,36 @@
+#ifdef ENABLE_WAYLAND
+#pragma once
+
+#include <map>
+#include <string>
+#include <glm/vec4.hpp>
+
+#include "WallpaperEngine/Render/Drivers/CVideoDriver.h"
+#include "COutput.h"
+
+namespace WallpaperEngine::Render::Drivers
+{
+    class CWaylandOpenGLDriver;
+
+    namespace Output
+    {
+        class CWaylandOutput : public COutput
+        {
+        public:
+            CWaylandOutput (CApplicationContext& context, CWaylandOpenGLDriver& driver);
+            ~CWaylandOutput ();
+
+            void reset () override;
+
+            bool renderVFlip () const override;
+            bool renderMultiple () const override;
+            bool haveImageBuffer () const override;
+            void* getImageBuffer () const override;
+            void updateRender () const override;
+
+        private:
+            void updateViewports();
+        };
+    }
+}
+#endif /* ENABLE_WAYLAND */

+ 224 - 0
src/WallpaperEngine/Render/Drivers/Output/CWaylandOutputViewport.cpp

@@ -0,0 +1,224 @@
+#include "WallpaperEngine/Application/CWallpaperApplication.h"
+#include "CWaylandOutputViewport.h"
+#include "WallpaperEngine/Logging/CLog.h"
+
+#define class _class
+#define namespace _namespace
+#define static
+extern "C" {
+#include "xdg-shell-protocol.h"
+#include "wlr-layer-shell-unstable-v1-protocol.h"
+}
+#undef class
+#undef namespace
+#undef static
+
+#include <unistd.h>
+
+using namespace WallpaperEngine::Render::Drivers;
+using namespace WallpaperEngine::Render::Drivers::Output;
+
+static void handleLSConfigure(void *data, zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t w, uint32_t h)
+{
+    const auto viewport = static_cast <CWaylandOutputViewport*> (data);
+    viewport->size = {w, h};
+    viewport->viewport = {0, 0, viewport->size.x * viewport->scale, viewport->size.y * viewport->scale};
+    viewport->resize ();
+
+    zwlr_layer_surface_v1_ack_configure(surface, serial);
+}
+
+static void handleLSClosed(void *data, zwlr_layer_surface_v1 *surface)
+{
+    const auto viewport = static_cast <CWaylandOutputViewport*> (data);
+
+    viewport->getDriver ()->onLayerClose(viewport);
+}
+
+static void geometry(
+    void* data, wl_output* output, int32_t x, int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel,
+    const char* make, const char* model, int32_t transform
+)
+{
+    // ignored
+}
+
+static void mode(void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh)
+{
+    const auto viewport = static_cast <CWaylandOutputViewport*> (data);
+
+    // update viewport size too
+    viewport->size = {width, height};
+    viewport->viewport = {0, 0, viewport->size.x * viewport->scale, viewport->size.y * viewport->scale};
+
+    if (viewport->layerSurface)
+        viewport->resize ();
+
+    if (viewport->initialized)
+        viewport->getDriver ()->getOutput ().reset ();
+}
+
+static void done(void* data, wl_output* wl_output)
+{
+    static_cast <CWaylandOutputViewport*> (data)->initialized = true;
+}
+
+static void scale(void* data, wl_output* wl_output, int32_t scale)
+{
+    const auto viewport = static_cast <CWaylandOutputViewport*> (data);
+
+    viewport->scale = scale;
+
+    if (viewport->layerSurface)
+        viewport->resize ();
+
+    if (viewport->initialized)
+        viewport->getDriver ()->getOutput ().reset ();
+}
+
+static void name(void* data, wl_output* wl_output, const char* name)
+{
+    const auto viewport = static_cast <CWaylandOutputViewport*> (data);
+
+    if (name)
+        viewport->name = name;
+
+    // ensure the output is updated with the new name too
+    viewport->getDriver ()->getOutput ().reset ();
+}
+
+static void description(void* data, wl_output* wl_output, const char* description)
+{
+    // ignored
+}
+
+static void surfaceFrameCallback(void *data, struct wl_callback *cb, uint32_t time)
+{
+    const auto viewport = static_cast <CWaylandOutputViewport*> (data);
+
+    wl_callback_destroy(cb);
+
+    viewport->frameCallback = nullptr;
+    viewport->rendering = true;
+    viewport->getDriver ()->getApp ().update (viewport);
+    viewport->rendering = false;
+
+    float renderTime = viewport->getDriver ()->getRenderTime();
+}
+
+const struct wl_callback_listener frameListener =
+{
+    .done = surfaceFrameCallback
+};
+
+const wl_output_listener outputListener =
+{
+    .geometry = geometry,
+    .mode = mode,
+    .done = done,
+    .scale = scale,
+    .name = name,
+    .description = description
+};
+
+const struct zwlr_layer_surface_v1_listener layerSurfaceListener = {
+    .configure = handleLSConfigure,
+    .closed = handleLSClosed,
+};
+
+CWaylandOutputViewport::CWaylandOutputViewport (CWaylandOpenGLDriver* driver, uint32_t waylandName, struct wl_registry *registry) :
+    m_driver (driver),
+    waylandName (waylandName),
+    COutputViewport ({0,0,0,0}, "", true)
+{
+    // setup output listener
+    this->output = (wl_output*) wl_registry_bind(registry, waylandName, &wl_output_interface, 4);
+    this->name = "";
+    this->size = {0, 0};
+    wl_output_add_listener(output, &outputListener, this);
+}
+
+void CWaylandOutputViewport::setupLS ()
+{
+    surface = wl_compositor_create_surface(m_driver->getWaylandContext()->compositor);
+    layerSurface = zwlr_layer_shell_v1_get_layer_surface(m_driver->getWaylandContext()->layerShell, surface, output, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "linux-wallpaperengine");
+
+    if (!layerSurface)
+        sLog.exception("Failed to get a layer surface");
+
+    wl_region* region = wl_compositor_create_region(m_driver->getWaylandContext()->compositor);
+    wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
+
+    zwlr_layer_surface_v1_set_size(layerSurface, 0, 0);
+    zwlr_layer_surface_v1_set_anchor(layerSurface, ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM);
+    zwlr_layer_surface_v1_set_keyboard_interactivity(layerSurface, false);
+    zwlr_layer_surface_v1_add_listener(layerSurface, &layerSurfaceListener, this);
+    zwlr_layer_surface_v1_set_exclusive_zone(layerSurface, -1);
+    wl_surface_set_input_region(surface, region);
+    wl_surface_commit(surface);
+    wl_display_roundtrip(m_driver->getWaylandContext()->display);
+
+    eglWindow = wl_egl_window_create(surface, size.x * scale, size.y * scale);
+    eglSurface = m_driver->getEGLContext ()->eglCreatePlatformWindowSurfaceEXT(m_driver->getEGLContext ()->display, m_driver->getEGLContext ()->config, eglWindow, nullptr);
+    wl_surface_commit(surface);
+    wl_display_roundtrip(m_driver->getWaylandContext()->display);
+    wl_display_flush(m_driver->getWaylandContext()->display);
+
+    static const auto XCURSORSIZE = getenv("XCURSOR_SIZE") ? std::stoi(getenv("XCURSOR_SIZE")) : 24;
+    const auto PRCURSORTHEME = wl_cursor_theme_load(getenv("XCURSOR_THEME"), XCURSORSIZE * scale, m_driver->getWaylandContext()->shm);
+
+    if (!PRCURSORTHEME)
+        sLog.exception("Failed to get a cursor theme");
+
+    pointer = wl_cursor_theme_get_cursor(PRCURSORTHEME, "left_ptr");
+    cursorSurface = wl_compositor_create_surface(m_driver->getWaylandContext()->compositor);
+
+    if (!cursorSurface)
+        sLog.exception("Failed to get a cursor surface");
+
+    if (eglMakeCurrent(m_driver->getEGLContext ()->display, eglSurface, eglSurface, m_driver->getEGLContext ()->context) == EGL_FALSE)
+        sLog.exception("Failed to make egl current");
+
+    this->m_driver->getOutput ().reset ();
+}
+
+CWaylandOpenGLDriver* CWaylandOutputViewport::getDriver ()
+{
+    return this->m_driver;
+}
+
+void CWaylandOutputViewport::makeCurrent ()
+{
+    EGLBoolean result = eglMakeCurrent (
+        m_driver->getEGLContext ()->display,
+        eglSurface,
+        eglSurface,
+        m_driver->getEGLContext ()->context
+    );
+
+    if (result == EGL_FALSE)
+        sLog.error ("Couldn't make egl current");
+}
+
+void CWaylandOutputViewport::swapOutput ()
+{
+    this->callbackInitialized = true;
+
+    this->makeCurrent ();
+    frameCallback = wl_surface_frame (surface);
+    wl_callback_add_listener (frameCallback, &frameListener, this);
+    eglSwapBuffers (m_driver->getEGLContext ()->display, this->eglSurface);
+    wl_surface_set_buffer_scale(surface, scale);
+    wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
+    wl_surface_commit(surface);
+}
+
+void CWaylandOutputViewport::resize ()
+{
+    if (!this->eglWindow)
+        return;
+
+    wl_egl_window_resize(this->eglWindow, this->size.x * this->scale, this->size.y * this->scale, 0, 0);
+
+    this->getDriver ()->getOutput().reset();
+}

+ 75 - 0
src/WallpaperEngine/Render/Drivers/Output/CWaylandOutputViewport.h

@@ -0,0 +1,75 @@
+#ifdef ENABLE_WAYLAND
+#pragma once
+
+#include <wayland-client.h>
+#include <wayland-cursor.h>
+#include <wayland-egl.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GL/glew.h>
+
+#include <glm/vec2.hpp>
+#include "COutputViewport.h"
+#include "../CWaylandOpenGLDriver.h"
+
+struct zwlr_layer_shell_v1;
+struct zwlr_layer_surface_v1;
+
+namespace WallpaperEngine::Render::Drivers
+{
+    class CWaylandOpenGLDriver;
+
+    namespace Output
+    {
+        class COutputViewport;
+
+        class CWaylandOutputViewport : public COutputViewport
+        {
+        public:
+            CWaylandOutputViewport (CWaylandOpenGLDriver* driver, uint32_t waylandName, struct wl_registry *registry);
+
+            /**
+             * @return The wayland driver
+             */
+            CWaylandOpenGLDriver* getDriver ();
+
+            wl_output* output;
+            glm::ivec2 size;
+            uint32_t waylandName;
+            int scale = 1;
+            bool initialized = false;
+            bool rendering = false;
+
+            wl_egl_window* eglWindow = nullptr;
+            EGLSurface eglSurface = nullptr;
+            wl_surface* surface = nullptr;
+            zwlr_layer_surface_v1* layerSurface = nullptr;
+            wl_callback* frameCallback = nullptr;
+            glm::dvec2 mousePos = {0, 0};
+            wl_cursor* pointer = nullptr;
+            wl_surface* cursorSurface = nullptr;
+            bool callbackInitialized = false;
+
+            void setupLS ();
+
+            /**
+             * Activates output's context for drawing
+             */
+            void makeCurrent () override;
+
+            /**
+             * Swaps buffers to present data on the viewport
+             */
+            void swapOutput () override;
+
+            /**
+             * Updates the viewport size
+             */
+            void resize ();
+
+        private:
+            CWaylandOpenGLDriver* m_driver;
+        };
+    }
+}
+#endif /* ENABLE_WAYLAND */

+ 13 - 5
src/WallpaperEngine/Render/Drivers/Output/CX11Output.cpp

@@ -1,5 +1,6 @@
 #include "common.h"
 #include "CX11Output.h"
+#include "CX11OutputViewport.h"
 
 #include <unistd.h>
 #include <X11/Xlib.h>
@@ -34,9 +35,8 @@ int CustomXIOErrorHandler (Display* dsp)
     return 0;
 }
 
-CX11Output::CX11Output (CApplicationContext& context, CVideoDriver& driver, Detectors::CFullScreenDetector& detector) :
-    COutput (context, detector),
-    m_driver (driver)
+CX11Output::CX11Output (CApplicationContext& context, CVideoDriver& driver) :
+    COutput (context, driver)
 {
     // do not use previous handler, it might stop the app under weird circumstances
     XSetErrorHandler (CustomXErrorHandler);
@@ -57,11 +57,17 @@ void CX11Output::reset ()
     // re-load screen info
     this->loadScreenInfo ();
     // do the same for the detector
-    this->m_detector.reset ();
+    this->m_driver.getFullscreenDetector ().reset ();
 }
 
 void CX11Output::free ()
 {
+    // go through all the viewports and free them
+    for(const auto& cur : this->m_viewports)
+        delete cur.second;
+
+    this->m_viewports.clear ();
+
     // free all the resources we've got
     XDestroyImage (this->m_image);
     XFreeGC (this->m_display, this->m_gc);
@@ -132,6 +138,7 @@ void CX11Output::loadScreenInfo ()
 
         // add the screen to the list of screens
         this->m_screens.push_back (
+            new CX11OutputViewport
             {
                 {crtc->x, crtc->y, crtc->width, crtc->height},
                 info->name
@@ -144,6 +151,7 @@ void CX11Output::loadScreenInfo ()
             sLog.out ("Found requested screen: ", info->name, " -> ", crtc->x, "x", crtc->y, ":", crtc->width, "x", crtc->height);
 
             this->m_viewports[info->name] =
+                new CX11OutputViewport
                 {
                     {crtc->x, crtc->y, crtc->width, crtc->height},
                     info->name
@@ -187,6 +195,6 @@ void CX11Output::updateRender () const
     XFlush(this->m_display);
 
     // check for fullscreen windows and wait until there's none fullscreen
-    while (this->m_detector.anythingFullscreen () && this->m_context.state.general.keepRunning)
+    while (this->m_driver.getFullscreenDetector ().anythingFullscreen () && this->m_context.state.general.keepRunning)
         usleep (FULLSCREEN_CHECK_WAIT_TIME);
 }

+ 2 - 3
src/WallpaperEngine/Render/Drivers/Output/CX11Output.h

@@ -14,7 +14,7 @@ namespace WallpaperEngine::Render::Drivers::Output
     class CX11Output : public COutput
     {
     public:
-        CX11Output (CApplicationContext& context, CVideoDriver& driver, Detectors::CFullScreenDetector& detector);
+        CX11Output (CApplicationContext& context, CVideoDriver& driver);
         ~CX11Output ();
 
         void reset () override;
@@ -35,7 +35,6 @@ namespace WallpaperEngine::Render::Drivers::Output
         GC m_gc;
         char* m_imageData;
         XImage* m_image;
-        CVideoDriver& m_driver;
-        std::vector <ScreenInfo> m_screens;
+        std::vector <COutputViewport*> m_screens;
     };
 }

+ 18 - 0
src/WallpaperEngine/Render/Drivers/Output/CX11OutputViewport.cpp

@@ -0,0 +1,18 @@
+#include "CX11OutputViewport.h"
+
+#include <utility>
+
+using namespace WallpaperEngine::Render::Drivers::Output;
+
+CX11OutputViewport::CX11OutputViewport (glm::ivec4 viewport, std::string name) :
+    COutputViewport (viewport, std::move(name))
+{
+}
+
+void CX11OutputViewport::makeCurrent ()
+{
+}
+
+void CX11OutputViewport::swapOutput ()
+{
+}

+ 15 - 0
src/WallpaperEngine/Render/Drivers/Output/CX11OutputViewport.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include "COutputViewport.h"
+
+namespace WallpaperEngine::Render::Drivers::Output
+{
+    class CX11OutputViewport : public COutputViewport
+    {
+    public:
+        CX11OutputViewport (glm::ivec4 viewport, std::string name);
+
+        void makeCurrent () override;
+        void swapOutput () override;
+    };
+}