diff --git a/Dockerfile b/Dockerfile
index 6776c8ac..4646e375 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,7 +6,7 @@ RUN apt-get update -y && apt-get install -y software-properties-common
RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test
RUN add-apt-repository -y ppa:openjdk-r/ppa
RUN add-apt-repository -y ppa:git-core/ppa
-RUN apt-get update -y && apt-get install -y wget zip python git build-essential g++-9 cmake ninja-build libxcomposite-dev libxrandr-dev libgl1-mesa-dev libxi-dev libxcursor-dev openjdk-11-jdk-headless
+RUN apt-get update -y && apt-get install -y wget zip python git build-essential g++-9 cmake ninja-build libxcomposite-dev libxrandr-dev libgl1-mesa-dev libxi-dev libxcursor-dev openjdk-11-jdk-headless libegl1-mesa libegl1-mesa-dev extra-cmake-modules wayland-protocols wayland-utils libwayland-dev
RUN wget --no-verbose https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz --output-document - | tar -xz
RUN echo 'export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64' > /etc/profile.d/02-jdk.sh
RUN echo 'export PATH=$JAVA_HOME/bin:/root/apache-maven-3.6.3/bin:$PATH' >> /etc/profile.d/02-jdk.sh
diff --git a/examples/dashboard/java/Example.java b/examples/dashboard/java/Example.java
index f8a2ee20..27613297 100644
--- a/examples/dashboard/java/Example.java
+++ b/examples/dashboard/java/Example.java
@@ -53,10 +53,11 @@ public Example() {
panelTheme = new PanelTheme(window);
panelTouch = new PanelTouch(window);
- var scale = window.getScreen().getScale();
+ var scale = window.getScale();
int count = App._windows.size() - 1;
Screen screen = App.getScreens()[(count / 5) % App.getScreens().length];
IRect bounds = screen.getWorkArea();
+ // IRect bounds = new IRect(0, 0, 100, 100);
window.setTitle("JWM Window #" + count);
if (window instanceof WindowMac windowMac) {
@@ -199,6 +200,18 @@ public void accept(Event e) {
window.minimize();
case B ->
setProgressBar(progressBars.next());
+ case S -> {
+ var timer = new Timer();
+ // delay to allow workspace/focus switching
+ timer.schedule(new TimerTask() {
+ public void run() {
+ App.runOnUIThread(() -> {
+ window.focus();
+ timer.cancel();
+ });
+ }
+ }, 2000);
+ }
}
}
} else if (e instanceof EventFrame) {
diff --git a/examples/dashboard/java/PanelMouseCursors.java b/examples/dashboard/java/PanelMouseCursors.java
index ee104e0a..fc32b105 100644
--- a/examples/dashboard/java/PanelMouseCursors.java
+++ b/examples/dashboard/java/PanelMouseCursors.java
@@ -56,7 +56,7 @@ public void accept(Event e) {
keepCursor = false;
} else if (!keepCursor) {
if (window._lastCursor != MouseCursor.ARROW)
- window.requestFrame();
+ window.requestFrame();
window.setMouseCursor(MouseCursor.ARROW);
}
lastInside = inside;
diff --git a/examples/dashboard/java/PanelRendering.java b/examples/dashboard/java/PanelRendering.java
index 5ab882b4..4d215ccf 100644
--- a/examples/dashboard/java/PanelRendering.java
+++ b/examples/dashboard/java/PanelRendering.java
@@ -34,6 +34,8 @@ else if (Platform.CURRENT == Platform.WINDOWS)
layers = new String[] { "LayerD3D12Skija", "LayerGLSkija", "SkijaLayerRaster" };
else if (Platform.CURRENT == Platform.X11)
layers = new String[] { "LayerGLSkija", "LayerRasterSkija" };
+ else if (Platform.CURRENT == Platform.WAYLAND)
+ layers = new String[] { "LayerGLSkija", "LayerRasterSkija" };
for (var layerName: layers)
layersStatus.put(layerName, UNKNOWN);
diff --git a/examples/dashboard/java/PanelScreens.java b/examples/dashboard/java/PanelScreens.java
index 378f8ef3..dd287ab7 100644
--- a/examples/dashboard/java/PanelScreens.java
+++ b/examples/dashboard/java/PanelScreens.java
@@ -18,7 +18,7 @@ public PanelScreens(Window window) {
super(window);
if (Platform.MACOS == Platform.CURRENT) {
titleStyles = new Options("Default", "Hidden", "Transparent", "Unified", "Unified Compact", "Unified Transparent", "Unified Compact Transparent");
- } else if (Platform.X11 == Platform.CURRENT) {
+ } else if (Platform.X11 == Platform.CURRENT || Platform.WAYLAND == Platform.CURRENT) {
titleStyles = new Options("Default", "Hidden");
}
}
@@ -66,6 +66,14 @@ public void setTitleStyle(String style) {
case "Hidden" ->
w.setTitlebarVisible(false);
}
+ } else if (Platform.WAYLAND == Platform.CURRENT) {
+ WindowWayland w = (WindowWayland) window;
+ switch (style) {
+ case "Default" ->
+ w.setTitlebarVisible(true);
+ case "Hidden" ->
+ w.setTitlebarVisible(false);
+ }
}
}
@@ -171,4 +179,4 @@ public void paintImpl(Canvas canvas, int width, int height, float scale) {
canvas.translate(0, lineHeight);
canvas.restore();
}
-}
\ No newline at end of file
+}
diff --git a/examples/dashboard/java/PanelTextInput.java b/examples/dashboard/java/PanelTextInput.java
index 9d55a522..04eff63b 100644
--- a/examples/dashboard/java/PanelTextInput.java
+++ b/examples/dashboard/java/PanelTextInput.java
@@ -232,4 +232,4 @@ public String getSubstring(int start, int end) {
int end2 = Math.min(end, start2);
return text.substring(start2, end2);
}
-}
\ No newline at end of file
+}
diff --git a/linux/cc/ILayer.cc b/linux/cc/ILayer.cc
index 4f7d2d1e..6d4d4264 100644
--- a/linux/cc/ILayer.cc
+++ b/linux/cc/ILayer.cc
@@ -10,4 +10,4 @@ void jwm::ILayer::makeCurrent() {
}
void jwm::ILayer::makeCurrentForced() {
_ourCurrentLayer = this;
-}
\ No newline at end of file
+}
diff --git a/linux/cc/ILayer.hh b/linux/cc/ILayer.hh
index 5a22d209..81a32c2b 100644
--- a/linux/cc/ILayer.hh
+++ b/linux/cc/ILayer.hh
@@ -20,4 +20,4 @@ public:
static ILayer* _ourCurrentLayer;
};
-} // namespace jwm
\ No newline at end of file
+} // namespace jwm
diff --git a/macos/java/WindowMac.java b/macos/java/WindowMac.java
index 645be065..297736fa 100644
--- a/macos/java/WindowMac.java
+++ b/macos/java/WindowMac.java
@@ -228,6 +228,11 @@ public Window restore() {
return this;
}
+ @Override
+ public float getScale() {
+ return this.getScreen().getScale();
+ }
+
@Override
public Window setFullScreen(boolean value) {
assert _onUIThread() : "Should be run on UI thread";
diff --git a/script/build.py b/script/build.py
index 56e4cc3b..9954d826 100755
--- a/script/build.py
+++ b/script/build.py
@@ -1,8 +1,8 @@
#! /usr/bin/env python3
import argparse, build_utils, common, glob, os, platform, subprocess, sys
-def build_native():
- os.chdir(common.basedir + "/" + build_utils.system)
+def build_native_system(system):
+ os.chdir(common.basedir + "/" + system)
subprocess.check_call(["cmake",
"-DCMAKE_BUILD_TYPE=Release",
"-B", "build",
@@ -19,16 +19,30 @@ def build_native():
if os.path.exists('build/libjwm_x64.so'):
build_utils.copy_newer('build/libjwm_x64.so', '../target/classes/libjwm_x64.so')
+
+ if os.path.exists('build/libjwm_x64_wayland.so'):
+ build_utils.copy_newer('build/libjwm_x64_wayland.so', '../target/classes/libjwm_x64_wayland.so')
if os.path.exists('build/jwm_x64.dll'):
build_utils.copy_newer('build/jwm_x64.dll', '../target/classes/jwm_x64.dll')
return 0
-
+def build_native():
+ cur_system = build_utils.system;
+ if cur_system == "linux":
+ build_native_system("x11")
+ build_native_system("wayland")
+ else:
+ build_native_system(cur_system)
+ return 0
def build_java():
os.chdir(common.basedir)
- sources = build_utils.files("linux/java/**/*.java", "macos/java/**/*.java", "shared/java/**/*.java", "windows/java/**/*.java",)
- build_utils.javac(sources, "target/classes", classpath=common.deps_compile())
+ sources = build_utils.files("x11/java/**/*.java",
+ "macos/java/**/*.java",
+ "shared/java/**/*.java",
+ "windows/java/**/*.java",
+ "wayland/java/**/*.java")
+ build_utils.javac(sources, "target/classes", classpath=common.deps_compile(), release="16")
return 0
def main():
diff --git a/script/build_utils.py b/script/build_utils.py
index a3f7986b..721ff5ea 100644
--- a/script/build_utils.py
+++ b/script/build_utils.py
@@ -250,4 +250,4 @@ def fetch(path, data = None):
"stagedRepositoryIds":[repo_id]
}})
print('Success! Just released', repo_id)
- return 0
\ No newline at end of file
+ return 0
diff --git a/script/clean.py b/script/clean.py
index 77a3870b..db328a9b 100755
--- a/script/clean.py
+++ b/script/clean.py
@@ -4,9 +4,13 @@
def main():
os.chdir(common.basedir)
build_utils.rmdir("target")
- build_utils.rmdir(build_utils.system + "/build")
+ if build_utils.system == "linux":
+ build_utils.rmdir("wayland/build")
+ build_utils.rmdir("x11/build")
+ else:
+ build_utils.rmdir(build_utils.system + "/build")
build_utils.rmdir("examples/dashboard/target")
return 0
if __name__ == '__main__':
- sys.exit(main())
\ No newline at end of file
+ sys.exit(main())
diff --git a/script/common.py b/script/common.py
index 3f3b0f7d..5e8bd9fd 100644
--- a/script/common.py
+++ b/script/common.py
@@ -8,7 +8,7 @@ def deps_compile():
parser = argparse.ArgumentParser()
parser.add_argument('--skija-dir', default=None)
parser.add_argument('--skija-shared-jar', default=None)
- parser.add_argument('--skija-version', default='0.116.1')
+ parser.add_argument('--skija-version', default='0.116.2')
(args, _) = parser.parse_known_args()
deps = [
@@ -38,4 +38,4 @@ def deps_compile():
return deps
-version = build_utils.get_arg("version") or build_utils.parse_ref() or build_utils.parse_sha() or "0.0.0-SNAPSHOT"
\ No newline at end of file
+version = build_utils.get_arg("version") or build_utils.parse_ref() or build_utils.parse_sha() or "0.0.0-SNAPSHOT"
diff --git a/script/package.py b/script/package.py
index 5bd1fa1f..adde3153 100755
--- a/script/package.py
+++ b/script/package.py
@@ -22,7 +22,8 @@ def main() -> Tuple[str, str, str]:
jar = build_utils.jar(f"target/jwm-{common.version}.jar", ("target/classes", "."), ("target/maven", "META-INF"))
build_utils.makedirs("target/src/io/github/humbleui/jwm")
- shutil.copytree("linux/java", "target/src/io/github/humbleui/jwm", dirs_exist_ok=True)
+ shutil.copytree("x11/java", "target/src/io/github/humbleui/jwm", dirs_exist_ok=True)
+ shutil.copytree("wayland/java", "target/src/io/github/humbleui/jwm", dirs_exist_ok=True)
shutil.copytree("macos/java", "target/src/io/github/humbleui/jwm", dirs_exist_ok=True)
shutil.copytree("shared/java", "target/src/io/github/humbleui/jwm", dirs_exist_ok=True)
shutil.copytree("windows/java", "target/src/io/github/humbleui/jwm", dirs_exist_ok=True)
diff --git a/script/run.py b/script/run.py
index d4a282ef..64c201c2 100755
--- a/script/run.py
+++ b/script/run.py
@@ -5,14 +5,15 @@ def main():
parser = argparse.ArgumentParser()
parser.add_argument('--example', default='dashboard')
parser.add_argument('--jwm-version', default=None)
- parser.add_argument('--skija-version', default='0.116.1')
+ parser.add_argument('--skija-version', default='0.116.2')
parser.add_argument('--skija-dir', default=None)
parser.add_argument('--skija-shared-jar', default=None)
parser.add_argument('--skija-platform-jar', default=None)
parser.add_argument('--types-dir', default=None)
+ parser.add_argument('--just-run', action='store_true')
args = parser.parse_args()
- if not args.jwm_version:
+ if not args.jwm_version and not args.just_run:
build.main()
if args.skija_dir:
@@ -32,14 +33,21 @@ def main():
]
else:
classpath += [
- 'target/classes',
- build_utils.system + '/build'
- ]
+ 'target/classes'
+ ]
+ if build_utils.system == "linux":
+ classpath += [
+ "wayland/build",
+ "x11/build"
+ ]
+ else:
+ classpath += [
+ build_utils.system + '/build'
+ ]
if args.skija_dir:
classpath += [
- skija_dir + '/platform/build',
- skija_dir + '/platform/target/classes',
+ skija_dir + '/platform/target/' + build_utils.system + '-' + build_utils.arch + '/classes',
]
elif args.skija_platform_jar:
classpath += [
diff --git a/shared/cc/MouseCursor.hh b/shared/cc/MouseCursor.hh
index f7d798ce..8a3f0ab0 100644
--- a/shared/cc/MouseCursor.hh
+++ b/shared/cc/MouseCursor.hh
@@ -51,4 +51,4 @@ namespace jwm {
}
}
-}
\ No newline at end of file
+}
diff --git a/shared/cc/Window.cc b/shared/cc/Window.cc
index ce9c1f29..3567e77f 100644
--- a/shared/cc/Window.cc
+++ b/shared/cc/Window.cc
@@ -6,6 +6,7 @@
jwm::Window::~Window() {
fEnv->DeleteGlobalRef(fWindow);
+ fWindow = nullptr;
}
void jwm::Window::dispatch(jobject event) {
diff --git a/shared/java/App.java b/shared/java/App.java
index 268ddc60..3275eafd 100644
--- a/shared/java/App.java
+++ b/shared/java/App.java
@@ -52,6 +52,8 @@ else if (Platform.CURRENT == Platform.MACOS)
window = new WindowMac();
else if (Platform.CURRENT == Platform.X11)
window = new WindowX11();
+ else if (Platform.CURRENT == Platform.WAYLAND)
+ window = new WindowWayland();
else
throw new RuntimeException("Unsupported platform: " + Platform.CURRENT);
_windows.add(window);
@@ -98,12 +100,15 @@ public static Screen[] getScreens() {
*
* @return primary desktop screen
*/
+ @Nullable
public static Screen getPrimaryScreen() {
assert _onUIThread() : "Should be run on UI thread";
+ if (Platform.CURRENT == Platform.WAYLAND)
+ return null;
for (Screen s: getScreens())
if (s.isPrimary())
return s;
- throw new IllegalStateException("Can't find primary screen");
+ return null;
}
public static void openSymbolsPalette() {
diff --git a/shared/java/ClipboardEntry.java b/shared/java/ClipboardEntry.java
index 6b1b0ddb..97b7f2ed 100644
--- a/shared/java/ClipboardEntry.java
+++ b/shared/java/ClipboardEntry.java
@@ -62,7 +62,7 @@ public static ClipboardEntry makeRTF(String text) {
*/
@NotNull @SneakyThrows
public static ClipboardEntry makeString(ClipboardFormat format, String text) {
- if (Platform.CURRENT == Platform.X11 || Platform.CURRENT == Platform.MACOS) {
+ if (Platform.CURRENT == Platform.X11 || Platform.CURRENT == Platform.MACOS || Platform.CURRENT == Platform.WAYLAND) {
return make(format, text.getBytes("UTF-8"));
}
return make(format, text.getBytes("UTF-16LE"));
@@ -76,9 +76,9 @@ public static ClipboardEntry makeString(ClipboardFormat format, String text) {
*/
@NotNull @SneakyThrows
public String getString() {
- if (Platform.CURRENT == Platform.X11 || Platform.CURRENT == Platform.MACOS) {
+ if (Platform.CURRENT == Platform.X11 || Platform.CURRENT == Platform.MACOS || Platform.CURRENT == Platform.WAYLAND) {
return new String(_data, "UTF-8");
}
return new String(_data, "UTF-16LE");
}
-}
\ No newline at end of file
+}
diff --git a/shared/java/Layer.java b/shared/java/Layer.java
index c3931abd..7099f9ba 100644
--- a/shared/java/Layer.java
+++ b/shared/java/Layer.java
@@ -47,4 +47,4 @@ default void swapBuffers() {}
@Override
default void close() {}
-}
\ No newline at end of file
+}
diff --git a/shared/java/LayerGL.java b/shared/java/LayerGL.java
index 75aabb57..b31be871 100644
--- a/shared/java/LayerGL.java
+++ b/shared/java/LayerGL.java
@@ -15,8 +15,8 @@ public LayerGL() {
@Override
public void attach(Window window) {
assert _onUIThread() : "Should be run on UI thread";
- _nAttach(window);
_window = window;
+ _nAttach(window);
}
@Override
@@ -75,4 +75,4 @@ public void close() {
@ApiStatus.Internal public native void _nResize(int width, int height);
@ApiStatus.Internal public native void _nSwapBuffers();
@ApiStatus.Internal public native void _nClose();
-}
\ No newline at end of file
+}
diff --git a/shared/java/LayerRaster.java b/shared/java/LayerRaster.java
index e097bfe0..4f5bd379 100644
--- a/shared/java/LayerRaster.java
+++ b/shared/java/LayerRaster.java
@@ -15,8 +15,8 @@ public LayerRaster() {
@Override
public void attach(Window window) {
assert _onUIThread() : "Should be run on UI thread";
- _nAttach(window);
_window = window;
+ _nAttach(window);
}
@Override
@@ -81,4 +81,4 @@ public int getRowBytes() {
@ApiStatus.Internal public native long _nGetPixelsPtr();
@ApiStatus.Internal public native int _nGetRowBytes();
@ApiStatus.Internal public native void _nClose();
-}
\ No newline at end of file
+}
diff --git a/shared/java/Platform.java b/shared/java/Platform.java
index 737fcaa8..cf923704 100644
--- a/shared/java/Platform.java
+++ b/shared/java/Platform.java
@@ -3,7 +3,8 @@
public enum Platform {
WINDOWS,
X11,
- MACOS;
+ MACOS,
+ WAYLAND;
public static final Platform CURRENT;
static {
@@ -13,8 +14,11 @@ public enum Platform {
else if (os.contains("windows"))
CURRENT = WINDOWS;
else if (os.contains("nux") || os.contains("nix"))
- CURRENT = X11;
+ if (System.getenv("WAYLAND_DISPLAY") != null)
+ CURRENT = WAYLAND;
+ else
+ CURRENT = X11;
else
throw new RuntimeException("Unsupported platform: " + os);
}
-}
\ No newline at end of file
+}
diff --git a/shared/java/Window.java b/shared/java/Window.java
index dc78cb15..a77f5060 100644
--- a/shared/java/Window.java
+++ b/shared/java/Window.java
@@ -61,13 +61,16 @@ public Window setLayer(@Nullable Layer layer) {
_layer = null;
}
if (layer != null) {
- layer.attach(this);
_layer = layer;
- accept(EventWindowScreenChange.INSTANCE);
+ layer.attach(this);
+ // accepting this immediately causes crashes on wayland
+ if (Platform.CURRENT != Platform.WAYLAND)
+ accept(EventWindowScreenChange.INSTANCE);
}
return this;
}
+ public abstract float getScale();
/**
*
Enables complex text input on this window.
* Passed value `true` or `false` enables or disables complex text input and IME on this window respectively.
diff --git a/shared/java/impl/Library.java b/shared/java/impl/Library.java
index 7104fc59..29a5da5d 100644
--- a/shared/java/impl/Library.java
+++ b/shared/java/impl/Library.java
@@ -46,6 +46,9 @@ public static synchronized void load() {
} else if (Platform.CURRENT == Platform.X11) {
File library = _extract("/", "libjwm_x64.so", tempDir);
System.load(library.getAbsolutePath());
+ } else if (Platform.CURRENT == Platform.WAYLAND) {
+ File library = _extract("/", "libjwm_x64_wayland.so", tempDir);
+ System.load(library.getAbsolutePath());
}
if (tempDir.exists() && version == null) {
diff --git a/shared/java/skija/LayerGLSkija.java b/shared/java/skija/LayerGLSkija.java
index 9862a675..b543cd2f 100644
--- a/shared/java/skija/LayerGLSkija.java
+++ b/shared/java/skija/LayerGLSkija.java
@@ -82,4 +82,4 @@ public void close() {
super.close();
}
-}
\ No newline at end of file
+}
diff --git a/wayland/CMakeLists.txt b/wayland/CMakeLists.txt
new file mode 100644
index 00000000..eef3c646
--- /dev/null
+++ b/wayland/CMakeLists.txt
@@ -0,0 +1,88 @@
+cmake_minimum_required(VERSION 3.16)
+# prefer the newer GL library (GLVND)
+cmake_policy(SET CMP0072 NEW)
+
+find_package(ECM REQUIRED NO_MODULE)
+set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
+
+include(FindWaylandProtocols)
+include(FindWaylandScanner)
+find_package(WaylandProtocols 1.25)
+set_package_properties(WaylandProtocols PROPERTIES
+ TYPE REQUIRED
+)
+if (NOT WaylandProtocols_FOUND)
+ message(FATAL_ERROR "No protocols installed")
+endif()
+if (NOT WaylandScanner_FOUND)
+ message(FATAL_ERROR "No wayland-scanner")
+endif()
+project(jwm LANGUAGES CXX)
+project(protocols LANGUAGES C)
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+if(NOT JWM_ARCH)
+ if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64")
+ set(JWM_ARCH "arm64")
+ else()
+ set(JWM_ARCH "x64")
+ endif()
+endif()
+
+file(GLOB SOURCES_CXX ${CMAKE_CURRENT_LIST_DIR}/../shared/cc/*.cc
+ ${CMAKE_CURRENT_LIST_DIR}/../linux/cc/*.cc
+ ${CMAKE_CURRENT_LIST_DIR}/cc/*.cc )
+file(GLOB SOURCES_CXX_IMPL ${CMAKE_CURRENT_LIST_DIR}/../shared/cc/impl/*.cc)
+add_library(jwm SHARED ${SOURCES_OBJC} ${SOURCES_CXX} ${SOURCES_CXX_IMPL})
+ecm_add_wayland_client_protocol(PROTOCOLS_SOURCE
+ PROTOCOL "${WaylandProtocols_DATADIR}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
+ BASENAME pointer-constraints-unstable-v1
+)
+ecm_add_wayland_client_protocol(PROTOCOLS_SOURCE
+ PROTOCOL "${WaylandProtocols_DATADIR}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
+ BASENAME relative-pointer-unstable-v1
+)
+ecm_add_wayland_client_protocol(PROTOCOLS_SOURCE
+ PROTOCOL "${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml"
+ BASENAME xdg-shell
+)
+ecm_add_wayland_client_protocol(PROTOCOLS_SOURCE
+ PROTOCOL "${WaylandProtocols_DATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
+ BASENAME xdg-decoration-unstable-v1
+)
+ecm_add_wayland_client_protocol(PROTOCOLS_SOURCE
+ PROTOCOL "${WaylandProtocols_DATADIR}/stable/viewporter/viewporter.xml"
+ BASENAME viewporter
+)
+ecm_add_wayland_client_protocol(PROTOCOLS_SOURCE
+ PROTOCOL "${WaylandProtocols_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml"
+ BASENAME xdg-activation-v1
+ )
+add_library(protocols STATIC ${PROTOCOLS_SOURCE})
+find_library(WAYLAND_CLIENT_LIB wayland-client)
+find_library(WAYLAND_CURSOR wayland-cursor)
+find_library(XKBCOMMON xkbcommon)
+find_library(EGL EGL)
+find_library(WAYLAND_EGL wayland-egl)
+find_package(OpenGL REQUIRED)
+set(JAVA_HOME $ENV{JAVA_HOME})
+if (NOT JAVA_HOME)
+ file(GLOB JAVA_HOMES "/usr/lib/jvm/java-*")
+ if (JAVA_HOMES)
+ list(GET JAVA_HOMES 0 JAVA_HOME)
+ message(STATUS "Java home found automatically at ${JAVA_HOME}. Set JAVA_HOME environment variable to override.")
+ else()
+ message(FATAL_ERROR "Java home not found! Please set JAVA_HOME environment variable.")
+ endif()
+endif()
+
+target_include_directories(jwm PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../shared/cc ${CMAKE_CURRENT_LIST_DIR}/../linux/cc ${JAVA_HOME}/include ${JAVA_HOME}/include/linux
+ ${CMAKE_CURRENT_LIST_DIR}/build)
+set_target_properties(jwm PROPERTIES OUTPUT_NAME "jwm_${JWM_ARCH}_wayland")
+
+target_link_libraries(jwm PRIVATE ${WAYLAND_CLIENT_LIB}
+ ${WAYLAND_CURSOR} ${XKBCOMMON})
+target_link_libraries(jwm PRIVATE ${EGL} ${WAYLAND_EGL})
+target_link_libraries(jwm PRIVATE OpenGL::GL)
+target_link_libraries(jwm PRIVATE protocols)
diff --git a/wayland/cc/AppWayland.cc b/wayland/cc/AppWayland.cc
new file mode 100644
index 00000000..e426fcf7
--- /dev/null
+++ b/wayland/cc/AppWayland.cc
@@ -0,0 +1,71 @@
+#include
+#include "AppWayland.hh"
+#include
+#include
+#include
+jwm::AppWayland jwm::app;
+
+
+const char* jwm::AppWayland::proxyTag = "JWM";
+
+void jwm::AppWayland::init(JNIEnv* jniEnv) {
+ _jniEnv = jniEnv;
+}
+
+void jwm::AppWayland::start() {
+ wm.runLoop();
+}
+
+void jwm::AppWayland::terminate() {
+ wm.terminate();
+}
+
+JNIEnv* jwm::AppWayland::getJniEnv() {
+ return _jniEnv;
+}
+
+bool jwm::AppWayland::ownProxy(wl_proxy* proxy) {
+ if (!proxy)
+ return false;
+ return wl_proxy_get_tag(proxy) == &proxyTag;
+}
+// JNI
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_App__1nStart(JNIEnv* env, jclass jclass, jobject launcher) {
+ jwm::app.init(env);
+ jwm::classes::Runnable::run(env, launcher);
+ jwm::app.start();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_App__1nTerminate(JNIEnv* env, jclass jclass) {
+ jwm::app.terminate();
+}
+
+extern"C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
+ return JNI_VERSION_1_2;
+}
+
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_App__1nRunOnUIThread
+ (JNIEnv* env, jclass cls, jobject callback) {
+ jobject callbackRef = env->NewGlobalRef(callback);
+ jwm::app.getWindowManager().enqueueTask([callbackRef] {
+ jwm::classes::Runnable::run(jwm::app.getJniEnv(), callbackRef);
+ jwm::app.getJniEnv()->DeleteGlobalRef(callbackRef);
+ });
+}
+
+// how awful
+extern "C" JNIEXPORT jobjectArray JNICALL Java_io_github_humbleui_jwm_App__1nGetScreens(JNIEnv* env, jobject cls) noexcept {
+
+
+ jobjectArray array = env->NewObjectArray(jwm::app.wm.outputs.size(), jwm::classes::Screen::kCls, 0);
+ size_t index = 0;
+
+ for (auto& i : jwm::app.wm.outputs) {
+ env->SetObjectArrayElement(array, index++, i->getScreenInfo().asJavaObject(env));
+ }
+
+
+ return array;
+}
diff --git a/wayland/cc/AppWayland.hh b/wayland/cc/AppWayland.hh
new file mode 100644
index 00000000..defcc38c
--- /dev/null
+++ b/wayland/cc/AppWayland.hh
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "WindowManagerWayland.hh"
+#include
+#include "Types.hh"
+#include
+#include "impl/Library.hh"
+#include "ScreenInfo.hh"
+#include
+
+namespace jwm {
+ extern class AppWayland {
+ public:
+
+ void init(JNIEnv* jniEnv);
+ void start();
+ void terminate();
+
+ WindowManagerWayland& getWindowManager() {
+ return wm;
+ }
+
+ JNIEnv* getJniEnv();
+
+ JNIEnv* _jniEnv;
+ WindowManagerWayland wm;
+
+ static const char* proxyTag;
+
+ static bool ownProxy(wl_proxy* proxy);
+ } app;
+}
diff --git a/wayland/cc/Buffer.cc b/wayland/cc/Buffer.cc
new file mode 100644
index 00000000..19ecf611
--- /dev/null
+++ b/wayland/cc/Buffer.cc
@@ -0,0 +1,102 @@
+#include "Buffer.hh"
+#include
+#include
+#include
+#include
+#include
+using namespace jwm;
+
+static void bufRelease(void* data, wl_buffer* wlbuffer) {
+ auto buffer = reinterpret_cast(data);
+ delete buffer;
+}
+wl_buffer_listener Buffer::_bufferListener = {
+ .release = bufRelease
+};
+static void randname(char *buf)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ long r = ts.tv_nsec;
+ for (int i = 0; i < 6; ++i) {
+ buf[i] = 'A'+(r&15)+(r&16)*2;
+ r >>= 5;
+ }
+}
+static int _createShmFile() {
+ int retries = 100;
+ do {
+ char name[] = "/wl_shm-XXXXXX";
+ randname(name + sizeof(name) - 7);
+ --retries;
+ int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd >= 0) {
+ shm_unlink(name);
+ return fd;
+ }
+ } while (retries > 0 && errno == EEXIST);
+ return -1;
+}
+
+static int _allocateShmFile(size_t size) {
+ int fd = _createShmFile();
+ if (fd < 0)
+ return -1;
+ int ret;
+ do {
+ ret = ftruncate(fd, size);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ ::close(fd);
+ return -1;
+ }
+ return fd;
+}
+Buffer::Buffer(wl_buffer* buffer,
+ int width,
+ int height,
+ void *data,
+ size_t dataSize) :
+ _buffer(buffer),
+ _width(width),
+ _height(height),
+ _data(data),
+ _dataSize(dataSize)
+{
+ wl_buffer_add_listener(buffer, &_bufferListener, this);
+}
+Buffer::~Buffer()
+{
+ wl_buffer_destroy(_buffer);
+ munmap(_data, _dataSize);
+}
+
+Buffer* Buffer::createShmBuffer(wl_shm* shm, int width, int height, uint32_t format)
+{
+ wl_shm_pool* pool;
+ int fd, size, stride;
+ void* data;
+ wl_buffer* buffer;
+
+ stride = width * 4;
+ size = stride * height;
+
+ fd = _allocateShmFile(size);
+ if (fd < 0)
+ return nullptr;
+
+ data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ close(fd);
+ return nullptr;
+ }
+
+ pool = wl_shm_create_pool(shm, fd, size);
+
+ buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format);
+
+ wl_shm_pool_destroy(pool);
+ close(fd);
+
+ return new Buffer(buffer, width, height, data, size);
+}
diff --git a/wayland/cc/Buffer.hh b/wayland/cc/Buffer.hh
new file mode 100644
index 00000000..2cdfc90c
--- /dev/null
+++ b/wayland/cc/Buffer.hh
@@ -0,0 +1,41 @@
+#pragma once
+
+#include
+#include
+#include
+
+namespace jwm {
+ class Buffer {
+ public:
+ Buffer(wl_buffer* buffer,
+ int width,
+ int height,
+ void *data,
+ size_t dataSize);
+ ~Buffer();
+ wl_buffer* _buffer = nullptr;
+ wl_buffer* getBuffer() const {
+ return _buffer;
+ }
+ int _width;
+ int _height;
+ void* _data;
+ void* getData() const {
+ return _data;
+ }
+ size_t _dataSize;
+ size_t getSize() {
+ return _dataSize;
+ }
+
+ static wl_buffer_listener _bufferListener;
+
+ static Buffer* createShmBuffer(wl_shm* shm, int width, int height, uint32_t format);
+ private:
+ Buffer(const Buffer&) = delete;
+ Buffer(Buffer&&) = delete;
+ Buffer& operator=(const Buffer&) = delete;
+ Buffer& operator=(Buffer&&) = delete;
+
+ };
+}
diff --git a/wayland/cc/ClipboardWayland.cc b/wayland/cc/ClipboardWayland.cc
new file mode 100644
index 00000000..25ff41a1
--- /dev/null
+++ b/wayland/cc/ClipboardWayland.cc
@@ -0,0 +1,123 @@
+#include
+#include
+#include
+#include
+#include "AppWayland.hh"
+#include
+
+namespace jwm {
+ class ClipboardWayland {
+ public:
+ static ClipboardWayland& inst() {
+ static ClipboardWayland s;
+ return s;
+ }
+
+ jobjectArray getFormats(JNIEnv* env) const {
+ auto formats = jwm::app.getWindowManager().getClipboardFormats();
+ if (formats.empty()) {
+ return nullptr;
+ }
+
+ std::vector formatObjs;
+
+ for (auto& format : formats) {
+ auto js = StringUTF16(format.c_str()).toJString(env);
+ formatObjs.push_back(classes::Clipboard::registerFormat(env, js.get()));
+ }
+ jobjectArray jniFormats = env->NewObjectArray(static_cast(formats.size()), classes::ClipboardFormat::kCls, nullptr);
+
+ // fill java array
+ for (jsize i = 0; i < static_cast(formatObjs.size()); ++i) {
+ env->SetObjectArrayElement(jniFormats, i, formatObjs[i]);
+ }
+ return jniFormats;
+ }
+
+ jobject get(JNIEnv* env, jobjectArray formats) {
+ jsize formatsSize = env->GetArrayLength(formats);
+ for (jsize i = 0; i < formatsSize; ++i) {
+ jobject format = env->GetObjectArrayElement(formats, i);
+ if (format) {
+ jwm::StringUTF16 formatId = jwm::StringUTF16::makeFromJString(env, classes::ClipboardFormat::getFormatId(env, format));
+
+
+ ByteBuf contents;
+ // text will ALWAYS be utf8 if we are getting plain text.
+ // If you are outputting utf16 into something that other apps read from,
+ // you are going to hell.
+ contents = app.getWindowManager().getClipboardContents(formatId.toAscii());
+ if (contents.empty()) {
+ return nullptr;
+ }
+ JNILocal data(env, env->NewByteArray(static_cast(contents.size())));
+ jbyte* bytes = env->GetByteArrayElements(data.get(), nullptr);
+ std::memcpy(bytes, contents.data(), contents.size());
+
+ env->ReleaseByteArrayElements(data.get(), bytes, 0);
+
+
+ JNILocal entry(env, classes::ClipboardEntry::make(env, format, data.get()));
+ return env->NewGlobalRef(entry.get());
+ }
+ }
+ classes::Throwable::exceptionThrown(env);
+ return nullptr;
+ }
+
+
+ void set(JNIEnv* env, jobjectArray entries) {
+ jsize size = env->GetArrayLength(entries);
+ std::map contents;
+ for (jsize i = 0; i < size; ++i) {
+ jobject entry = env->GetObjectArrayElement(entries, i);
+
+ if (entry) {
+ jobject format = classes::ClipboardEntry::getFormat(env, entry);
+ jbyteArray data = classes::ClipboardEntry::getData(env, entry);
+ jsize dataSize = env->GetArrayLength(data);
+
+ StringUTF16 formatId = StringUTF16::makeFromJString(env, classes::ClipboardFormat::getFormatId(env, format));
+
+ ByteBuf resultBuffer;
+ {
+ jbyte* dataBytes = env->GetByteArrayElements(data, nullptr);
+ resultBuffer.insert(resultBuffer.end(), dataBytes, dataBytes + dataSize);
+ env->ReleaseByteArrayElements(data, dataBytes, JNI_ABORT);
+ }
+
+ contents[formatId.toAscii()] = std::move(resultBuffer);
+ }
+ }
+ jwm::app.getWindowManager().setClipboardContents(std::move(contents));
+ }
+ };
+}
+
+
+// JNI
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_Clipboard__1nSet
+ (JNIEnv* env, jclass jclass, jobjectArray entries) {
+ return jwm::ClipboardWayland::inst().set(env, entries);
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_Clipboard__1nGet
+ (JNIEnv* env, jclass jclass, jobjectArray formats) {
+ return jwm::ClipboardWayland::inst().get(env, formats);
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_io_github_humbleui_jwm_Clipboard__1nGetFormats
+ (JNIEnv* env, jclass jclass) {
+ return jwm::ClipboardWayland::inst().getFormats(env);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_Clipboard__1nClear
+ (JNIEnv* env, jclass jclass) {
+
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_io_github_humbleui_jwm_Clipboard__1nRegisterFormat
+ (JNIEnv* env, jclass jclass, jstring formatId) {
+ return true;
+}
diff --git a/wayland/cc/Decoration.cc b/wayland/cc/Decoration.cc
new file mode 100644
index 00000000..9dd3affa
--- /dev/null
+++ b/wayland/cc/Decoration.cc
@@ -0,0 +1,414 @@
+#include "Decoration.hh"
+#include "WindowWayland.hh"
+#include "WindowManagerWayland.hh"
+#include
+
+using namespace jwm;
+
+static unsigned int grey_data[] = {0xFF333333};
+static unsigned int zero_data[] = {0x00000000};
+// no image editor
+// pure unadulterated programming
+static unsigned int close_data[] = {
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000,
+ 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000,
+ 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000,
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+};
+static unsigned int min_data[] = {
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+};
+static unsigned int max_data[] = {
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+};
+
+static void _decorationConfigure(void* data, zxdg_toplevel_decoration_v1* decoration, uint32_t mode) {
+ auto self = reinterpret_cast(data);
+ if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) {
+ self->_serverSide = true;
+ } else {
+ self->_serverSide = false;
+ }
+}
+static zxdg_toplevel_decoration_v1_listener _decorationListener = {
+ .configure = _decorationConfigure
+};
+
+static void _xdgSurfaceConfigure(void* data, xdg_surface* surface, uint32_t serial) {
+ auto self = reinterpret_cast(data);
+ auto& window = self->_window;
+ int width = 0, height = 0;
+ int pendingWidth = self->_pendingWidth;
+ int pendingHeight = self->_pendingHeight;
+ // do it here bc we don't know configure order
+ if (!self->_serverSide) {
+ pendingHeight -= self->getTopSize();
+ }
+ if (pendingWidth > 0)
+ width = pendingWidth;
+ else
+ width = window._floatingWidth;
+
+ if (pendingHeight > 0)
+ height = pendingHeight;
+ else
+ height = window._floatingHeight;
+ if (self->_floating)
+ self->constrainSize(width, height);
+ self->_pendingWidth = 0;
+ self->_pendingHeight = 0;
+ if (self->_oldActive != self->_active) {
+ if (self->_active)
+ window.dispatch(classes::EventWindowFocusIn::kInstance);
+ else
+ window.dispatch(classes::EventWindowFocusOut::kInstance);
+ }
+ self->_oldActive = self->_active;
+ if (self->_oldMaximized != self->_maximized) {
+ if (self->_maximized)
+ window.dispatch(classes::EventWindowMaximize::kInstance);
+ }
+ self->_oldMaximized = self->_maximized;
+ if (self->_oldFullscreen != self->_fullscreen) {
+ if (self->_fullscreen)
+ window.dispatch(classes::EventWindowFullScreenEnter::kInstance);
+ else
+ window.dispatch(classes::EventWindowFullScreenExit::kInstance);
+ }
+ self->_oldFullscreen = self->_fullscreen;
+ if (!self->_configured) {
+ if (window._layer)
+ window._layer->attachBuffer();
+ }
+ if (self->_serverSide) {
+ if (self->_border.surface)
+ self->_destroyDecorations();
+ } else {
+ if (!self->_border.surface)
+ self->_showDecorations(!self->_isVisible);
+ }
+ // resize on first configure
+ self->_configured = true;
+ // ask to configure _before_ commit. jank.
+ if (self->_floating) {
+ xdg_surface_ack_configure(self->_xdgSurface, serial);
+ }
+ if (window.getUnscaledWidth() != width || window.getUnscaledHeight() != height) {
+ if (self->_floating) {
+ if (width > 0) {
+ window._floatingWidth = width;
+ }
+ if (height > 0) {
+ window._floatingHeight = height;
+ }
+ }
+ window._adaptSize(width, height);
+ self->_adaptSize();
+ }
+
+ wl_surface_commit(window._waylandWindow);
+ if (!self->_floating) {
+
+ xdg_surface_ack_configure(self->_xdgSurface, serial);
+ }
+}
+
+static xdg_surface_listener _xdgSurfaceListener = {
+ .configure = _xdgSurfaceConfigure
+};
+
+static void _xdgToplevelConfigure(void* data, xdg_toplevel* toplevel, int width, int height, wl_array* states) {
+ auto self = reinterpret_cast(data);
+
+ self->_pendingWidth = width;
+ self->_pendingHeight = height;
+
+ bool active = false;
+ bool maximized = false;
+ bool fullscreen = false;
+ bool floating = true;
+ for (uint32_t* pos = (uint32_t*)states->data;
+ (const char*)pos < ((const char*) states->data + states->size);
+ pos++) {
+ switch (*pos) {
+ case XDG_TOPLEVEL_STATE_MAXIMIZED:
+ maximized = true;
+ floating = false;
+ break;
+ case XDG_TOPLEVEL_STATE_ACTIVATED:
+ active = true;
+ break;
+ case XDG_TOPLEVEL_STATE_FULLSCREEN:
+ fullscreen = true;
+ floating = false;
+ break;
+ case XDG_TOPLEVEL_STATE_TILED_LEFT:
+ case XDG_TOPLEVEL_STATE_TILED_RIGHT:
+ case XDG_TOPLEVEL_STATE_TILED_TOP:
+ case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
+ floating = false;
+ break;
+ }
+ }
+ self->_active = active;
+ self->_maximized = maximized;
+ self->_fullscreen = fullscreen;
+ self->_floating = floating;
+}
+static void _xdgToplevelClose(void* data, xdg_toplevel* toplevel) {
+ auto self = reinterpret_cast(data);
+ auto& window = self->_window;
+
+ window.dispatch(classes::EventWindowCloseRequest::kInstance);
+}
+
+static xdg_toplevel_listener _xdgToplevelListener = {
+ .configure = _xdgToplevelConfigure,
+ .close = _xdgToplevelClose,
+};
+
+const char* Decoration::proxyTag = "DecorationJWM";
+Decoration::Decoration(WindowWayland& window):
+ _window(window),
+ _wm(window._windowManager)
+{
+ _xdgSurface = xdg_wm_base_get_xdg_surface(_wm.xdgWm, window._waylandWindow);
+ xdg_surface_add_listener(_xdgSurface, &_xdgSurfaceListener, this);
+ _xdgToplevel = xdg_surface_get_toplevel(_xdgSurface);
+ xdg_toplevel_add_listener(_xdgToplevel, &_xdgToplevelListener, this);
+ if (_wm.decorationManager) {
+ _decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(_wm.decorationManager, _xdgToplevel);
+ zxdg_toplevel_decoration_v1_add_listener(_decoration, &_decorationListener, this);
+ // for the love of GOD do it for me
+ zxdg_toplevel_decoration_v1_set_mode(_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+ }
+ // delay making parts until configure : )
+}
+
+void Decoration::close() {
+ if (_border.surface)
+ _destroyDecorations();
+ if (_decoration) {
+ // ???
+ // zxdg_toplevel_decoration_v1_destroy(_decoration);
+ _decoration = nullptr;
+ }
+ xdg_toplevel_destroy(_xdgToplevel);
+ _xdgToplevel = nullptr;
+ xdg_surface_destroy(_xdgSurface);
+ _xdgSurface = nullptr;
+
+}
+
+void Decoration::_makePart(DecorationPart* decoration, Buffer* buf, bool opaque, int x, int y, int width, int height) {
+ decoration->surface = wl_compositor_create_surface(_wm.compositor);
+ wl_proxy_set_tag((wl_proxy*)decoration->surface, &proxyTag);
+ wl_proxy_set_user_data((wl_proxy*)decoration->surface, this);
+ decoration->subsurface = wl_subcompositor_get_subsurface(_wm.subcompositor, decoration->surface, _window._waylandWindow);
+ wl_subsurface_set_position(decoration->subsurface, x, y);
+ decoration->viewport = wp_viewporter_get_viewport(_wm.viewporter, decoration->surface);
+ wp_viewport_set_destination(decoration->viewport, width, height);
+ if (buf)
+ wl_surface_attach(decoration->surface, buf->getBuffer(), 0, 0);
+
+ if (opaque) {
+ wl_region* region = wl_compositor_create_region(_wm.compositor);
+ wl_region_add(region, 0, 0, width, height);
+ wl_surface_set_opaque_region(decoration->surface, region);
+ wl_surface_commit(decoration->surface);
+ wl_region_destroy(region);
+ } else
+ wl_surface_commit(decoration->surface);
+}
+
+void Decoration::_resizeDecoration(DecorationPart* decoration, int x, int y, int width, int height) {
+ if (decoration->surface) {
+ wl_subsurface_set_position(decoration->subsurface, x, y);
+ wp_viewport_set_destination(decoration->viewport, width, height);
+ wl_surface_commit(decoration->surface);
+ }
+}
+void Decoration::_destroyDecoration(DecorationPart* decoration) {
+ if (decoration->subsurface) {
+ wl_subsurface_destroy(decoration->subsurface);
+ }
+ if (decoration->surface)
+ wl_surface_destroy(decoration->surface);
+ if (decoration->viewport)
+ wp_viewport_destroy(decoration->viewport);
+ decoration->subsurface = nullptr;
+ decoration->surface = nullptr;
+ decoration->viewport = nullptr;
+}
+
+void Decoration::_destroyDecorations() {
+ _destroyDecoration(&_border);
+ _destroyDecoration(&_titleComp);
+ _destroyDecoration(&_close);
+ _destroyDecoration(&_min);
+ _destroyDecoration(&_max);
+}
+
+void Decoration::_showDecorations(bool hidden) {
+ // ???
+ // When destroyed these get released
+ _decBuffer = Buffer::createShmBuffer(_wm.shm, 1, 1, WL_SHM_FORMAT_ARGB8888);
+ memcpy(_decBuffer->getData(), grey_data, 1 * sizeof(uint32_t));
+ _zeroBuffer = Buffer::createShmBuffer(_wm.shm, 1, 1, WL_SHM_FORMAT_ARGB8888);
+ memcpy(_zeroBuffer->getData(), zero_data, 1 * sizeof(uint32_t));
+ _closeBuffer = Buffer::createShmBuffer(_wm.shm, 9, 9, WL_SHM_FORMAT_ARGB8888);
+ memcpy(_closeBuffer->getData(), close_data, 9 * 9 * sizeof(uint32_t));
+ _maxBuffer = Buffer::createShmBuffer(_wm.shm, 9, 9, WL_SHM_FORMAT_ARGB8888);
+ memcpy(_maxBuffer->getData(), max_data, 9 * 9 * sizeof(uint32_t));
+ _minBuffer = Buffer::createShmBuffer(_wm.shm, 9, 9, WL_SHM_FORMAT_ARGB8888);
+ memcpy(_minBuffer->getData(), min_data, 9 * 9 * sizeof(uint32_t));
+ int borderY = hidden ? DECORATION_BORDER_HIDDEN_Y : DECORATION_BORDER_Y;
+ int borderHeight = hidden ? DECORATION_BORDER_HEIGHT(_window) : DECORATION_BORDER_FULL_HEIGHT(_window);
+ _makePart(&_border, _zeroBuffer, false, DECORATION_BORDER_X, borderY,
+ DECORATION_BORDER_WIDTH(_window), borderHeight);
+ wl_subsurface_place_below(_border.subsurface, _window._waylandWindow);
+ if (!hidden) {
+ _makePart(&_titleComp, _decBuffer, true, DECORATION_TITLE_X, DECORATION_TITLE_Y,
+ DECORATION_TITLE_WIDTH(_window), DECORATION_TITLE_HEIGHT);
+ wl_subsurface_place_above(_titleComp.subsurface, _border.surface);
+ _makePart(&_close, _closeBuffer, false, DECORATION_CLOSE_X(_window), DECORATION_CLOSE_Y, DECORATION_CLOSE_WIDTH, DECORATION_CLOSE_HEIGHT);
+ _makePart(&_max, _maxBuffer, false, DECORATION_MAX_X(_window), DECORATION_MAX_Y, DECORATION_MAX_WIDTH, DECORATION_MAX_HEIGHT);
+ _makePart(&_min, _minBuffer, false, DECORATION_MIN_X(_window), DECORATION_MIN_Y, DECORATION_MIN_WIDTH, DECORATION_MIN_HEIGHT);
+ }
+
+}
+
+void Decoration::_adaptSize() {
+ int borderY = _isVisible ? DECORATION_BORDER_Y : DECORATION_BORDER_HIDDEN_Y;
+ int borderHeight = _isVisible ? DECORATION_BORDER_FULL_HEIGHT(_window) : DECORATION_BORDER_HEIGHT(_window);
+ _resizeDecoration(&_border, DECORATION_BORDER_X, borderY,
+ DECORATION_BORDER_WIDTH(_window), borderHeight);
+ if (_isVisible) {
+ _resizeDecoration(&_titleComp, DECORATION_TITLE_X, DECORATION_TITLE_Y,
+ DECORATION_TITLE_WIDTH(_window), DECORATION_TITLE_HEIGHT);
+ _resizeDecoration(&_close, DECORATION_CLOSE_X(_window), DECORATION_CLOSE_Y, DECORATION_CLOSE_WIDTH, DECORATION_CLOSE_HEIGHT);
+ _resizeDecoration(&_min, DECORATION_MIN_X(_window), DECORATION_MIN_Y, DECORATION_MIN_WIDTH, DECORATION_MIN_HEIGHT);
+ _resizeDecoration(&_max, DECORATION_MAX_X(_window), DECORATION_MAX_Y, DECORATION_MAX_WIDTH, DECORATION_MAX_HEIGHT);
+ }
+
+ if (!_serverSide)
+ xdg_surface_set_window_geometry(_xdgSurface, 0, _isVisible ? DECORATION_TITLE_Y : 0,
+ _window.getUnscaledWidth(),
+ (_isVisible ? DECORATION_TITLE_HEIGHT : 0) + _window.getUnscaledHeight() );
+ else
+ xdg_surface_set_window_geometry(_xdgSurface, 0, 0, _window.getUnscaledWidth(), _window.getUnscaledHeight());
+}
+
+bool Decoration::ownDecorationSurface(wl_surface* surface) {
+ if (!surface) return false;
+ return wl_proxy_get_tag((wl_proxy*)surface) == &proxyTag;
+}
+
+Decoration* Decoration::getDecorationForSurface(wl_surface* surface, DecorationFocus* focus) {
+ if (ownDecorationSurface(surface)) {
+ Decoration* decoration = (Decoration*)wl_proxy_get_user_data((wl_proxy*) surface);
+ if (surface == decoration->_border.surface) {
+ *focus = DECORATION_FOCUS_BORDER;
+ } else if (surface == decoration->_titleComp.surface) {
+ *focus = DECORATION_FOCUS_TITLE;
+ } else if (surface == decoration->_close.surface) {
+ *focus = DECORATION_FOCUS_CLOSE_BUTTON;
+ } else if (surface == decoration->_min.surface) {
+ *focus = DECORATION_FOCUS_MIN_BUTTON;
+ } else if (surface == decoration->_max.surface) {
+ *focus = DECORATION_FOCUS_MAX_BUTTON;
+ }
+
+ return decoration;
+ } else {
+ *focus = DECORATION_FOCUS_MAIN;
+ return nullptr;
+ }
+}
+
+void Decoration::setTitlebarVisible(bool isVisible) {
+ if (isVisible != _isVisible) {
+ _isVisible = isVisible;
+ if (isVisible) {
+ _destroyDecorations();
+ if (_decoration) {
+ zxdg_toplevel_decoration_v1_set_mode(_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+ } else {
+ _showDecorations(false);
+ }
+ } else {
+ _destroyDecorations();
+ if (_decoration) {
+ zxdg_toplevel_decoration_v1_set_mode(_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
+ } else {
+ _showDecorations(true);
+ wl_surface_commit(_window._waylandWindow);
+ }
+ }
+ }
+}
+
+void Decoration::setTitle(const std::string& title) {
+ xdg_toplevel_set_title(_xdgToplevel, title.c_str());
+ // grab a copy - unused for now but maybe a text renderer will eventually be pulled in
+ _title = title;
+}
+
+void Decoration::getBorders(int& left, int& top, int& right, int& bottom) {
+ if (_serverSide) {
+ left = 0;
+ top = 0;
+ right = 0;
+ bottom = 0;
+ } else {
+ left = 0;
+ top = getTopSize();
+ right = 0;
+ bottom = 0;
+ }
+}
+
+int Decoration::getTopSize() {
+ if (_serverSide)
+ return 0;
+ if (_isVisible)
+ return DECORATION_TOP_HEIGHT;
+ else
+ return 0;
+}
+
+void Decoration::setMinSize(int width, int height) {
+ _minWidth = width;
+ _minHeight = height;
+}
+
+void Decoration::constrainSize(int& width, int& height) {
+ if (width < _minWidth)
+ width = _minWidth;
+ // hard coded for buttons
+ if (width < 40)
+ width = 40;
+ if (height < _minHeight)
+ height = _minHeight;
+}
diff --git a/wayland/cc/Decoration.hh b/wayland/cc/Decoration.hh
new file mode 100644
index 00000000..f1d7729e
--- /dev/null
+++ b/wayland/cc/Decoration.hh
@@ -0,0 +1,140 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include "Buffer.hh"
+#include
+
+#define DECORATION_WIDTH 4
+#define DECORATION_TOP_HEIGHT 25
+
+#define DECORATION_BORDER_X -(DECORATION_WIDTH)
+#define DECORATION_BORDER_HIDDEN_Y -(DECORATION_WIDTH)
+#define DECORATION_BORDER_Y -(DECORATION_WIDTH + DECORATION_TOP_HEIGHT)
+#define DECORATION_BORDER_WIDTH(window) window.getUnscaledWidth() + DECORATION_WIDTH + DECORATION_WIDTH
+#define DECORATION_BORDER_HEIGHT(window) window.getUnscaledHeight() + DECORATION_WIDTH + DECORATION_WIDTH
+#define DECORATION_BORDER_FULL_HEIGHT(window) window.getUnscaledHeight() + DECORATION_TOP_HEIGHT + DECORATION_WIDTH + DECORATION_WIDTH
+
+#define DECORATION_TITLE_X 0
+#define DECORATION_TITLE_Y -(DECORATION_TOP_HEIGHT)
+#define DECORATION_TITLE_WIDTH(window) window.getUnscaledWidth()
+#define DECORATION_TITLE_HEIGHT DECORATION_TOP_HEIGHT
+
+#define DECORATION_CLOSE_X(window) window.getUnscaledWidth() - 10
+#define DECORATION_CLOSE_Y -20
+#define DECORATION_CLOSE_WIDTH 9
+#define DECORATION_CLOSE_HEIGHT 9
+
+#define DECORATION_MAX_X(window) (DECORATION_CLOSE_X(window)) - 10
+#define DECORATION_MAX_Y DECORATION_CLOSE_Y
+#define DECORATION_MAX_WIDTH 9
+#define DECORATION_MAX_HEIGHT 9
+
+#define DECORATION_MIN_X(window) (DECORATION_MAX_X(window)) - 10
+#define DECORATION_MIN_Y DECORATION_CLOSE_Y
+#define DECORATION_MIN_WIDTH 9
+#define DECORATION_MIN_HEIGHT 9
+
+namespace jwm {
+ class WindowManagerWayland;
+ class WindowWayland;
+ struct DecorationPart {
+ wl_surface* surface = nullptr;
+ wl_subsurface* subsurface = nullptr;
+ wp_viewport* viewport = nullptr;
+ };
+ enum DecorationFocus {
+ DECORATION_FOCUS_MAIN,
+ DECORATION_FOCUS_BORDER,
+ DECORATION_FOCUS_TITLE,
+ DECORATION_FOCUS_CLOSE_BUTTON,
+ DECORATION_FOCUS_MAX_BUTTON,
+ DECORATION_FOCUS_MIN_BUTTON
+ };
+ // Creation is mapping
+ // Closing is unmapping
+ class Decoration {
+ public:
+ Decoration() = delete;
+ Decoration(WindowWayland& window);
+ ~Decoration();
+
+ WindowManagerWayland& _wm;
+ WindowWayland& _window;
+
+ Buffer* _decBuffer;
+ Buffer* _zeroBuffer;
+ Buffer* _closeBuffer;
+ Buffer* _maxBuffer;
+ Buffer* _minBuffer;
+
+ DecorationPart _border;
+ DecorationPart _titleComp;
+
+ DecorationPart _close;
+ DecorationPart _max;
+ DecorationPart _min;
+ // May be null at runtime
+ zxdg_toplevel_decoration_v1* _decoration = nullptr;
+
+ xdg_surface* _xdgSurface = nullptr;
+ xdg_toplevel* _xdgToplevel = nullptr;
+
+ bool _serverSide = false;
+ int _pendingWidth = 0;
+ int _pendingHeight = 0;
+ bool _oldActive = false;
+ // : )
+ bool _active = true;
+ bool _oldMaximized = false;
+ bool _maximized = false;
+ bool _oldFullscreen = false;
+ bool _fullscreen = false;
+ bool _floating = true;
+ bool _configured = false;
+ // unmap and dispose
+ void close();
+
+ void _makePart(DecorationPart* part, Buffer* buf, bool opaque, int x, int y, int width, int height);
+ void _resizeDecoration(DecorationPart* part, int x, int y, int width, int height);
+ void _destroyDecoration(DecorationPart* part);
+
+ void _adaptSize();
+ void _destroyDecorations();
+ void _showDecorations(bool hidden);
+
+ static const char* proxyTag;
+ static bool ownDecorationSurface(wl_surface* surface);
+ static Decoration* getDecorationForSurface(wl_surface* surface, DecorationFocus* focus);
+
+ std::string _title;
+
+ void setTitle(const std::string& title);
+
+ bool _isVisible = true;
+ void setTitlebarVisible(bool isVisible);
+
+ void getBorders(int& left, int& top, int& right, int& bottom);
+
+ int getTopSize();
+
+ int _minWidth = 10;
+ int _minHeight = 10;
+ void setMinSize(int width, int height);
+ void constrainSize(int& width, int& height);
+
+
+ private:
+ Decoration(Decoration&& other) = delete;
+ Decoration& operator=(Decoration&& other) = delete;
+
+ Decoration(Decoration& other) = delete;
+ Decoration& operator=(Decoration& other) = delete;
+
+
+
+ };
+}
diff --git a/wayland/cc/ILayerWayland.cc b/wayland/cc/ILayerWayland.cc
new file mode 100644
index 00000000..d892979c
--- /dev/null
+++ b/wayland/cc/ILayerWayland.cc
@@ -0,0 +1,9 @@
+#include "ILayerWayland.hh"
+#include
+#include "WindowWayland.hh"
+
+void jwm::ILayerWayland::detachBuffer() {
+ if (fWindow && fWindow->_waylandWindow) {
+ wl_surface_set_buffer_scale(fWindow->_waylandWindow, 1);
+ }
+}
diff --git a/wayland/cc/ILayerWayland.hh b/wayland/cc/ILayerWayland.hh
new file mode 100644
index 00000000..054abeb5
--- /dev/null
+++ b/wayland/cc/ILayerWayland.hh
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+
+namespace jwm {
+ class WindowWayland;
+ class ILayerWayland: public ILayer {
+ public:
+ WindowWayland* fWindow = nullptr;
+
+ virtual void attachBuffer() = 0;
+ virtual void swapBuffers() = 0;
+ virtual void detachBuffer();
+ };
+}
diff --git a/wayland/cc/KeyWayland.cc b/wayland/cc/KeyWayland.cc
new file mode 100644
index 00000000..14d3b95f
--- /dev/null
+++ b/wayland/cc/KeyWayland.cc
@@ -0,0 +1,170 @@
+#include "KeyWayland.hh"
+#include "KeyModifier.hh"
+#include
+#include
+#include
+
+int jwm::KeyWayland::getModifiers(xkb_state* state) {
+
+ int m = 0;
+
+ if (!state)
+ return 0;
+ if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE)) m |= (int)jwm::KeyModifier::SHIFT;
+ if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL , XKB_STATE_MODS_EFFECTIVE)) m |= (int)jwm::KeyModifier::CONTROL;
+ if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT , XKB_STATE_MODS_EFFECTIVE)) m |= (int)jwm::KeyModifier::ALT;
+ if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO , XKB_STATE_MODS_EFFECTIVE)) m |= (int)jwm::KeyModifier::LINUX_META;
+ if (xkb_state_mod_name_is_active(state, "Super" , XKB_STATE_MODS_EFFECTIVE)) m |= (int)jwm::KeyModifier::LINUX_SUPER;
+
+ return m;
+}
+/*
+int jwm::KeyWayland::getModifiersFromMask(int mask) {
+ int m = getModifiers();
+ // ???
+ // if (mask & ShiftMask ) m |= (int)jwm::KeyModifier::SHIFT;
+ // if (mask & ControlMask) m |= (int)jwm::KeyModifier::CONTROL;
+ // if (mask & Mod1Mask ) m |= (int)jwm::KeyModifier::ALT;
+
+ return m;
+}*/
+
+jwm::Key jwm::KeyWayland::fromNative(uint32_t v) {
+ switch (v - 8) {
+ // Modifiers
+ case KEY_CAPSLOCK: return Key::CAPS_LOCK;
+ case KEY_RIGHTSHIFT:
+ case KEY_LEFTSHIFT: return Key::SHIFT;
+ case KEY_RIGHTCTRL:
+ case KEY_LEFTCTRL: return Key::CONTROL;
+ case KEY_RIGHTALT:
+ case KEY_LEFTALT: return Key::ALT;
+ // Key::WIN_LOGO
+ case KEY_LEFTMETA:
+ case KEY_RIGHTMETA: return Key::LINUX_SUPER;
+ // prefer super over meta
+ // KEY::LINUX_META
+ // Key::MAC_COMMAND
+ // Key::MAC_OPTION
+ // Key::MAC_FN
+
+ // Rest of the keys
+ case KEY_ENTER: return Key::ENTER;
+ case KEY_BACKSPACE: return Key::BACKSPACE;
+ case KEY_TAB: return Key::TAB;
+ case KEY_CANCEL: return Key::CANCEL;
+ case KEY_CLEAR: return Key::CLEAR;
+ case KEY_PAUSE: return Key::PAUSE;
+ case KEY_ESC: return Key::ESCAPE;
+ case KEY_SPACE: return Key::SPACE;
+ case KEY_PAGEUP: return Key::PAGE_UP;
+ case KEY_PAGEDOWN: return Key::PAGE_DOWN;
+ case KEY_END: return Key::END;
+ case KEY_HOME: return Key::HOME;
+ case KEY_LEFT: return Key::LEFT;
+ case KEY_UP: return Key::UP;
+ case KEY_RIGHT: return Key::RIGHT;
+ case KEY_DOWN: return Key::DOWN;
+ case KEY_COMMA: return Key::COMMA;
+ case KEY_MINUS: return Key::MINUS;
+ case KEY_DOT: return Key::PERIOD;
+ case KEY_SLASH: return Key::SLASH;
+ case KEY_0: return Key::DIGIT0;
+ case KEY_1: return Key::DIGIT1;
+ case KEY_2: return Key::DIGIT2;
+ case KEY_3: return Key::DIGIT3;
+ case KEY_4: return Key::DIGIT4;
+ case KEY_5: return Key::DIGIT5;
+ case KEY_6: return Key::DIGIT6;
+ case KEY_7: return Key::DIGIT7;
+ case KEY_8: return Key::DIGIT8;
+ case KEY_9: return Key::DIGIT9;
+ case KEY_SEMICOLON: return Key::SEMICOLON;
+ case KEY_EQUAL: return Key::EQUALS;
+ case KEY_A: return Key::A;
+ case KEY_B: return Key::B;
+ case KEY_C: return Key::C;
+ case KEY_D: return Key::D;
+ case KEY_E: return Key::E;
+ case KEY_F: return Key::F;
+ case KEY_G: return Key::G;
+ case KEY_H: return Key::H;
+ case KEY_I: return Key::I;
+ case KEY_J: return Key::J;
+ case KEY_K: return Key::K;
+ case KEY_L: return Key::L;
+ case KEY_M: return Key::M;
+ case KEY_N: return Key::N;
+ case KEY_O: return Key::O;
+ case KEY_P: return Key::P;
+ case KEY_Q: return Key::Q;
+ case KEY_R: return Key::R;
+ case KEY_S: return Key::S;
+ case KEY_T: return Key::T;
+ case KEY_U: return Key::U;
+ case KEY_V: return Key::V;
+ case KEY_W: return Key::W;
+ case KEY_X: return Key::X;
+ case KEY_Y: return Key::Y;
+ case KEY_Z: return Key::Z;
+ case KEY_LEFTBRACE: return Key::OPEN_BRACKET;
+ case KEY_BACKSLASH: return Key::BACK_SLASH;
+ case KEY_RIGHTBRACE: return Key::CLOSE_BRACKET;
+ case KEY_KP0: return Key::DIGIT0;
+ case KEY_KP1: return Key::DIGIT1;
+ case KEY_KP2: return Key::DIGIT2;
+ case KEY_KP3: return Key::DIGIT3;
+ case KEY_KP4: return Key::DIGIT4;
+ case KEY_KP5: return Key::DIGIT5;
+ case KEY_KP6: return Key::DIGIT6;
+ case KEY_KP7: return Key::DIGIT7;
+ case KEY_KP8: return Key::DIGIT8;
+ case KEY_KP9: return Key::DIGIT9;
+ case KEY_KPASTERISK: return Key::MULTIPLY;
+ case KEY_KPPLUS: return Key::ADD;
+ case KEY_KPCOMMA: return Key::SEPARATOR;
+ case KEY_KPMINUS: return Key::MINUS;
+ case KEY_KPDOT: return Key::PERIOD;
+ case KEY_KPSLASH: return Key::SLASH;
+ // no kp delete?
+ // case KEY_: return Key::DEL;
+ case KEY_DELETE: return Key::DEL;
+ case KEY_NUMLOCK: return Key::NUM_LOCK;
+ case KEY_SCROLLLOCK: return Key::SCROLL_LOCK;
+ case KEY_F1: return Key::F1;
+ case KEY_F2: return Key::F2;
+ case KEY_F3: return Key::F3;
+ case KEY_F4: return Key::F4;
+ case KEY_F5: return Key::F5;
+ case KEY_F6: return Key::F6;
+ case KEY_F7: return Key::F7;
+ case KEY_F8: return Key::F8;
+ case KEY_F9: return Key::F9;
+ case KEY_F10: return Key::F10;
+ case KEY_F11: return Key::F11;
+ case KEY_F12: return Key::F12;
+ case KEY_F13: return Key::F13;
+ case KEY_F14: return Key::F14;
+ case KEY_F15: return Key::F15;
+ case KEY_F16: return Key::F16;
+ case KEY_F17: return Key::F17;
+ case KEY_F18: return Key::F18;
+ case KEY_F19: return Key::F19;
+ case KEY_F20: return Key::F20;
+ case KEY_F21: return Key::F21;
+ case KEY_F22: return Key::F22;
+ case KEY_F23: return Key::F23;
+ case KEY_F24: return Key::F24;
+ case KEY_PRINT: return Key::PRINTSCREEN;
+ case KEY_INSERT: return Key::INSERT;
+ case KEY_HELP: return Key::HELP;
+ case KEY_GRAVE: return Key::BACK_QUOTE;
+ case KEY_APOSTROPHE: return Key::QUOTE;
+ case KEY_MENU: return Key::MENU;
+ // Key::KANA
+ case KEY_VOLUMEUP: return Key::VOLUME_UP;
+ case KEY_VOLUMEDOWN: return Key::VOLUME_DOWN;
+ case KEY_MUTE: return Key::MUTE;
+ default: return Key::UNDEFINED;
+ }
+}
diff --git a/wayland/cc/KeyWayland.hh b/wayland/cc/KeyWayland.hh
new file mode 100644
index 00000000..03009cc1
--- /dev/null
+++ b/wayland/cc/KeyWayland.hh
@@ -0,0 +1,13 @@
+#pragma once
+
+#include
+#include "Key.hh"
+#include
+
+namespace jwm {
+ namespace KeyWayland {
+ jwm::Key fromNative(uint32_t v);
+ int getModifiers(xkb_state* state);
+ // int getModifiersFromMask(int mask);
+ }
+}
diff --git a/wayland/cc/Keyboard.cc b/wayland/cc/Keyboard.cc
new file mode 100644
index 00000000..57bf50e6
--- /dev/null
+++ b/wayland/cc/Keyboard.cc
@@ -0,0 +1,250 @@
+#include "Keyboard.hh"
+#include "WindowManagerWayland.hh"
+#include "WindowWayland.hh"
+#include "KeyWayland.hh"
+#include
+#include
+#include "StringUTF16.hh"
+#include
+#include
+#include
+#include
+#include
+#include "AppWayland.hh"
+#include
+#include
+#include
+#include
+
+using namespace jwm;
+
+// I've noticed that pointers to lambdas are null for some reason.
+// No idea what's wrong. Going to cry myself to sleep tonight.
+static void kbKeymap(void* data, wl_keyboard* kb, uint32_t format, int32_t fd, uint32_t size) {
+ auto self = reinterpret_cast(data);
+
+
+ if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+ close(fd);
+ fprintf(stderr, "no xkb keymap\n");
+ return;
+ }
+
+ char* map_str = reinterpret_cast(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0));
+ if (map_str == MAP_FAILED) {
+ close(fd);
+ fprintf(stderr, "keymap mmap failed: %s", strerror(errno));
+ return;
+ }
+
+ xkb_keymap* keymap = xkb_keymap_new_from_string(
+ self->_context, map_str,
+ XKB_KEYMAP_FORMAT_TEXT_V1,
+ XKB_KEYMAP_COMPILE_NO_FLAGS
+ );
+ munmap(map_str, size);
+ close(fd);
+
+ if (!keymap) {
+ return;
+ }
+ self->_state = xkb_state_new(keymap);
+
+ self->_keymap = keymap;
+
+ const char* locale = std::setlocale(LC_CTYPE, nullptr);
+
+ self->_composeTable = xkb_compose_table_new_from_locale(self->_context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
+ self->_composeState = xkb_compose_state_new(self->_composeTable, XKB_COMPOSE_STATE_NO_FLAGS);
+
+}
+static void kbEnter(void* data, wl_keyboard* kb, uint32_t serial, wl_surface* surface,
+ wl_array *keys) {
+ auto self = reinterpret_cast(data);
+ auto win = self->_wm.getWindowForNative(surface);
+ if (!win) return;
+ self->_serial = serial;
+ self->_focus = jwm::ref(win);
+ if (self->_state) {
+ uint32_t* key;
+ // C++ jank
+ // Normal macro fails to compile bc `void*` can't implicitly convert into `uint32_t*`
+ for (key = (uint32_t*)keys->data;
+ (const char*) key < (const char*)keys->data + keys->size;
+ key++
+ ) {
+ auto jwmKey = jwm::KeyWayland::fromNative(*key + 8);
+ self->submitKey(jwmKey, WL_KEYBOARD_KEY_STATE_PRESSED);
+ }
+ }
+}
+static void kbLeave(void* data, wl_keyboard* kb, uint32_t serial, wl_surface* surface) {
+ auto self = reinterpret_cast(data);
+ std::list liftedKeys(self->_depressedKeys);
+ for (auto key : liftedKeys) {
+ self->submitKey(key, WL_KEYBOARD_KEY_STATE_RELEASED);
+ }
+ self->_repeating = false;
+ self->_repeatingText = false;
+ self->_serial = 0;
+ if (self->_focus)
+ jwm::unref(&self->_focus);
+}
+static void kbKey(void* data, wl_keyboard* kb, uint32_t serial, uint32_t time,
+ uint32_t key, uint32_t state) {
+ auto self = reinterpret_cast(data);
+ if (!self->_state || !self->_focus) return;
+
+ const xkb_keysym_t *syms;
+ uint32_t keyCode = key + 8;
+ if (xkb_state_key_get_syms(self->_state, keyCode, &syms) != 1) {
+ xkb_compose_state_feed(self->_composeState, XKB_KEY_NoSymbol);
+ return;
+ }
+ auto sym = syms[0];
+ if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
+ xkb_compose_state_feed(self->_composeState, sym);
+ auto status = xkb_compose_state_get_status(self->_composeState);
+ bool composeRelated = status != XKB_COMPOSE_NOTHING;
+ // use raw key code
+ jwm::Key jwmKey = KeyWayland::fromNative(keyCode);
+ self->_repeatingText = false;
+ self->_repeating = false;
+ self->submitKey(jwmKey, state);
+ if (composeRelated) {
+ int dacount;
+ switch (status) {
+ case XKB_COMPOSE_COMPOSING:
+ break;
+ case XKB_COMPOSE_COMPOSED:
+ // I am going to wager a guess that no one will ever have a compose key that binds to a
+ // key we actually parse.
+ // auto keysym = xkb_compose_state_get_one_sym(self->_composeState);
+ char textBuf[0x40];
+
+ dacount = xkb_compose_state_get_utf8(self->_composeState, textBuf, sizeof(textBuf) - 1);
+
+ if (dacount > 0 && (dacount < sizeof(textBuf) - 1)) {
+ JNIEnv* env = jwm::app.getJniEnv();
+
+ jwm::StringUTF16 converted = reinterpret_cast(textBuf);
+ jwm::JNILocal jtext = converted.toJString(env);
+
+ jwm::JNILocal eventTextInput(env, classes::EventTextInput::make(env, jtext.get()));
+
+
+ self->_focus->dispatch(eventTextInput.get());
+
+ }
+
+
+ xkb_compose_state_reset(self->_composeState);
+ break;
+ case XKB_COMPOSE_CANCELLED:
+ xkb_compose_state_reset(self->_composeState);
+ break;
+ }
+ return;
+ }
+ if (state != WL_KEYBOARD_KEY_STATE_PRESSED) {
+ return;
+ }
+ // ???
+ self->_lastPress = std::chrono::steady_clock::now();
+ self->_repeatKey = jwmKey;
+ bool shouldRepeat = xkb_keymap_key_repeats(self->_keymap, keyCode) && (self->_repeatRate > 0);
+ if (shouldRepeat && (jwmKey != jwm::Key::UNDEFINED)) {
+ self->_repeating = true;
+ self->_nextRepeat = self->_lastPress + std::chrono::milliseconds(self->_repeatDelay);
+ self->_wm.notifyLoop();
+ }
+ char textBuffer[0x40];
+ int count = xkb_state_key_get_utf8(self->_state, keyCode, textBuffer, sizeof(textBuffer)-1);
+ // ???
+ if (count >= sizeof(textBuffer) - 1) {
+ return;
+ }
+ if (count > 0) {
+ // ignore sinful control symbols
+ if (textBuffer[0] != 127 && textBuffer[0] > 0x1f) {
+ JNIEnv* env = jwm::app.getJniEnv();
+
+ jwm::StringUTF16 converted = reinterpret_cast(textBuffer);
+ self->_repeatText = converted;
+ if (shouldRepeat)
+ self->_repeatingText = true;
+ jwm::JNILocal jtext = converted.toJString(env);
+
+ jwm::JNILocal eventTextInput(env, classes::EventTextInput::make(env, jtext.get()));
+
+
+ self->_focus->dispatch(eventTextInput.get());
+ }
+ }
+}
+void kbModifiers(void* data, wl_keyboard* kb, uint32_t serial, uint32_t mods_depressed,
+ uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
+ auto self = reinterpret_cast(data);
+ if (!self->_state) return;
+ xkb_state_update_mask(self->_state,
+ mods_depressed, mods_latched, mods_locked,
+ 0, 0, group);
+}
+void kbRepeatInfo(void* data, wl_keyboard* kb, int32_t rate, int32_t delay) {
+ auto self = reinterpret_cast(data);
+ self->_repeatRate = rate;
+ self->_repeatDelay = delay;
+}
+
+wl_keyboard_listener Keyboard::_keyboardListener = {
+ .keymap = kbKeymap,
+ .enter = kbEnter,
+ .leave = kbLeave,
+ .key = kbKey,
+ .modifiers = kbModifiers,
+ .repeat_info = kbRepeatInfo
+};
+Keyboard::Keyboard(wl_keyboard* kb, WindowManagerWayland* wm):
+ _keyboard(kb),
+ _wm(*wm)
+{
+ wl_keyboard_add_listener(kb, &_keyboardListener, this);
+ _context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+}
+
+Keyboard::~Keyboard()
+{
+ if (_keyboard)
+ wl_keyboard_release(_keyboard);
+ if (_context)
+ xkb_context_unref(_context);
+ if (_keymap)
+ xkb_keymap_unref(_keymap);
+}
+
+void Keyboard::submitKey(jwm::Key key, uint32_t state) {
+ if (key != jwm::Key::UNDEFINED) {
+ jwm::KeyLocation location = jwm::KeyLocation::DEFAULT;
+ JNILocal keyEvent(
+ jwm::app.getJniEnv(),
+ classes::EventKey::make(
+ jwm::app.getJniEnv(),
+ key,
+ state == WL_KEYBOARD_KEY_STATE_PRESSED,
+ KeyWayland::getModifiers(_state),
+ location
+ )
+ );
+ _focus->dispatch(keyEvent.get());
+
+ if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+ _depressedKeys.push_back(key);
+ } else {
+ auto it = std::find(_depressedKeys.begin(), _depressedKeys.end(), key);
+ if (it != _depressedKeys.end()) {
+ _depressedKeys.erase(it);
+ }
+ }
+ }
+}
+
diff --git a/wayland/cc/Keyboard.hh b/wayland/cc/Keyboard.hh
new file mode 100644
index 00000000..04baf904
--- /dev/null
+++ b/wayland/cc/Keyboard.hh
@@ -0,0 +1,64 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include "KeyWayland.hh"
+#include "StringUTF16.hh"
+#include
+
+namespace jwm {
+ class WindowManagerWayland;
+ class WindowWayland;
+ class Keyboard {
+ public:
+ Keyboard(wl_keyboard* kb, jwm::WindowManagerWayland* wm);
+ ~Keyboard();
+
+ wl_keyboard* _keyboard;
+ wl_keyboard* getKeyboard() const {
+ return _keyboard;
+ }
+ xkb_context* _context = nullptr;
+ xkb_state* _state = nullptr;
+ xkb_keymap* _keymap = nullptr;
+ xkb_compose_table* _composeTable = nullptr;
+ xkb_compose_state* _composeState = nullptr;
+
+ xkb_state* getState() const {
+ return _state;
+ }
+ jwm::WindowWayland* _focus = nullptr;
+ jwm::WindowWayland* getFocus() const {
+ return _focus;
+ }
+ uint32_t _serial = 0;
+ uint32_t getSerial() const {
+ return _serial;
+ }
+ std::chrono::time_point _lastPress;
+ std::chrono::time_point _nextRepeat;
+ int32_t _repeatRate = 100;
+ int32_t _repeatDelay = 300;
+
+ void submitKey(jwm::Key key, uint32_t state);
+
+ jwm::StringUTF16 _repeatText;
+ jwm::Key _repeatKey = jwm::Key::UNDEFINED;
+ bool _repeating = false;
+ bool _repeatingText = false;
+
+ std::list _depressedKeys;
+
+ jwm::WindowManagerWayland& _wm;
+
+ static wl_keyboard_listener _keyboardListener;
+ private:
+ // no copy or move
+ Keyboard(const Keyboard&) = delete;
+ Keyboard(Keyboard&&) = delete;
+ Keyboard& operator=(const Keyboard&) = delete;
+ Keyboard& operator=(Keyboard&&) = delete;
+ };
+}
diff --git a/wayland/cc/LayerGLWayland.cc b/wayland/cc/LayerGLWayland.cc
new file mode 100644
index 00000000..815fa6fd
--- /dev/null
+++ b/wayland/cc/LayerGLWayland.cc
@@ -0,0 +1,241 @@
+#include
+#include
+#include
+#include "impl/Library.hh"
+#include "impl/RefCounted.hh"
+#include "WindowWayland.hh"
+#include
+#include
+#include
+#include
+#include
+#include "ILayerWayland.hh"
+#include
+#include
+#include
+
+namespace jwm {
+
+ class LayerGL: public RefCounted, public ILayerWayland {
+ public:
+ WindowWayland* fWindow;
+ wl_egl_window* _eglWindow = nullptr;
+ wl_region* _region = nullptr;
+ EGLContext _context = nullptr;
+ EGLDisplay _display = nullptr;
+ EGLSurface _surface = nullptr;
+ EGLConfig _config = nullptr;
+ bool _closed = false;
+
+ LayerGL() = default;
+ virtual ~LayerGL() = default;
+
+ void attach(WindowWayland* window) {
+ if (_closed) {
+ fprintf(stderr, "already closed\n");
+ throw std::runtime_error("Already closed");
+ }
+
+ fWindow = jwm::ref(window);
+ fWindow->setLayer(this);
+ if (fWindow->_windowManager._eglDisplay == EGL_NO_DISPLAY) {
+ fWindow->_windowManager._eglDisplay = eglGetDisplay(window->_windowManager.display);
+
+ eglInitialize(fWindow->_windowManager._eglDisplay, nullptr, nullptr);
+
+ fWindow->_windowManager.vendor = eglQueryString(_display, EGL_VENDOR);
+
+ if (fWindow->_windowManager.vendor != nullptr && (strcmp(fWindow->_windowManager.vendor, "NVIDIA") == 0)) {
+ // Thankfully observed from minecraft's sodium
+ // https://github.com/CaffeineMC/sodium-fabric/blob/dev/src/main/java/me/jellysquid/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java#L29
+ setenv("__GL_THREADED_OPTIMIZATIONS", "0", true);
+ }
+ }
+ _display = fWindow->_windowManager._eglDisplay;
+ if ( eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
+ throw new std::runtime_error("Cannot bind EGL Api");
+ }
+
+ if (_context == nullptr) {
+ EGLint attrList[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+ EGL_BLUE_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_RED_SIZE, 8,
+ EGL_NONE
+ };
+ EGLint contextAttr[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE
+ };
+ EGLint numConfig;
+ if ( ( eglGetConfigs(_display, nullptr, 0, &numConfig) != EGL_TRUE) || (numConfig == 0) ) {
+ throw std::runtime_error("No configuration");
+ }
+ if ( ( eglChooseConfig(_display, attrList, &_config, 1, &numConfig) != EGL_TRUE) || (numConfig != 1)) {
+ throw std::runtime_error("No/Amibguous configuration");
+ }
+ // :troll:
+ _context = eglCreateContext(_display,
+ _config,
+ EGL_NO_CONTEXT,
+ contextAttr);
+ if ( _context == EGL_NO_CONTEXT ) {
+ throw std::runtime_error("Couldn't make context");
+ }
+ }
+ if (fWindow->_waylandWindow)
+ wl_surface_set_buffer_scale(fWindow->_waylandWindow, 1);
+ if (fWindow->isConfigured()) {
+ attachBuffer();
+ fWindow->dispatch(jwm::classes::EventWindowScreenChange::kInstance);
+ }
+ makeCurrentForced();
+ }
+
+ void setVsyncMode(VSync v) override {
+ // vsync? what vsync?
+ }
+
+ void resize(int width, int height) {
+ if (!_surface || !_eglWindow) return;
+ // Make current to avoid artifacts in other windows
+ makeCurrentForced();
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 255);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, width, height);
+ // God is dead if _eglWindow is null
+ if (_eglWindow && fWindow && fWindow->_waylandWindow) {
+ // HACK: make new window with new scale
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/7217
+ if (fWindow->_scale != fWindow->_oldScale) {
+ detachBuffer();
+ attachBuffer();
+ wl_surface_set_buffer_scale(fWindow->_waylandWindow, fWindow->getIntScale());
+ fWindow->_oldScale = fWindow->_scale;
+ } else
+ wl_egl_window_resize(_eglWindow, width, height, 0, 0);
+ }
+ }
+
+ void swapBuffers() override {
+ if (_surface) {
+ makeCurrent();
+ eglSwapBuffers(_display, _surface);
+ }
+ }
+
+ void close() override {
+ if (_closed) {
+ fprintf(stderr, "already closed\n");
+ return;
+ }
+ _closed = true;
+ detachBuffer();
+ eglDestroyContext(_display, _context);
+
+ if (fWindow) {
+ fWindow->setLayer(nullptr);
+ jwm::unref(&fWindow);
+ }
+ }
+
+ void makeCurrentForced() override {
+ ILayer::makeCurrentForced();
+ if (_surface) {
+ eglMakeCurrent(_display,
+ _surface,
+ _surface,
+ _context);
+ eglSwapInterval(_display, 0);
+ } else {
+ eglMakeCurrent(_display,
+ EGL_NO_SURFACE,
+ EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ }
+ }
+ void attachBuffer() override {
+ if (fWindow && fWindow->_waylandWindow) {
+ if (!_eglWindow) {
+ _eglWindow = wl_egl_window_create(fWindow->_waylandWindow, fWindow->getWidth(), fWindow->getHeight());
+
+ if (_eglWindow == nullptr) {
+ fprintf(stderr, "failed to get window\n");
+ }
+ _surface = eglCreateWindowSurface(_display, _config, _eglWindow, nullptr);
+
+ if ( _surface == EGL_NO_SURFACE ) {
+ fprintf(stderr, "failed to get surface\n");
+ }
+ makeCurrentForced();
+ }
+ }
+ }
+ void detachBuffer() override {
+ ILayerWayland::detachBuffer();
+ if (_surface) {
+ eglDestroySurface(_display, _surface);
+ }
+ _surface = nullptr;
+ // force the current layer to update
+ makeCurrentForced();
+ if (_eglWindow) {
+ wl_egl_window_destroy(_eglWindow);
+ }
+ _eglWindow = nullptr;
+ }
+ };
+
+} // namespace jwm
+
+// JNI
+
+extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_LayerGL__1nMake
+ (JNIEnv* env, jclass jclass) {
+ jwm::LayerGL* instance = new jwm::LayerGL();
+ return reinterpret_cast(instance);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerGL__1nAttach
+ (JNIEnv* env, jobject obj, jobject windowObj) {
+ try {
+ jwm::LayerGL* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ jwm::WindowWayland* window = reinterpret_cast(jwm::classes::Native::fromJava(env, windowObj));
+ instance->attach(window);
+ } catch (const std::exception& e) {
+ printf("%s\n", e.what());
+ jwm::classes::Throwable::throwLayerNotSupportedException(env, "Failed to init OpenGL");
+ }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerGL__1nReconfigure
+ (JNIEnv* env, jobject obj) {
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerGL__1nResize
+ (JNIEnv* env, jobject obj, jint width, jint height) {
+ jwm::LayerGL* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ instance->resize(width, height);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerGL__1nMakeCurrent
+ (JNIEnv* env, jobject obj) {
+ jwm::LayerGL* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ instance->makeCurrent();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerGL__1nSwapBuffers
+ (JNIEnv* env, jobject obj) {
+ jwm::LayerGL* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ instance->swapBuffers();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerGL__1nClose
+ (JNIEnv* env, jobject obj) {
+ jwm::LayerGL* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ instance->close();
+}
diff --git a/wayland/cc/LayerRasterWayland.cc b/wayland/cc/LayerRasterWayland.cc
new file mode 100644
index 00000000..d64ef789
--- /dev/null
+++ b/wayland/cc/LayerRasterWayland.cc
@@ -0,0 +1,144 @@
+// JNI
+
+#include
+#include "impl/Library.hh"
+#include "impl/RefCounted.hh"
+#include "WindowWayland.hh"
+#include "Buffer.hh"
+#include
+#include
+
+namespace jwm {
+ class LayerRaster: public RefCounted, public ILayerWayland {
+ public:
+ WindowWayland* fWindow;
+ size_t _width = 0, _height = 0;
+ std::vector _imageData;
+ bool _attached = false;
+
+ LayerRaster() = default;
+ virtual ~LayerRaster() = default;
+
+ void attach(WindowWayland* window) {
+ fWindow = jwm::ref(window);
+ fWindow->setLayer(this);
+ if (fWindow->isConfigured()) {
+ attachBuffer();
+ // delay this as much as possible
+ fWindow->dispatch(jwm::classes::EventWindowScreenChange::kInstance);
+ }
+ }
+
+ void resize(int width, int height) {
+ // god is dead
+ _width = width;
+ _height = height;
+ _imageData = std::vector(_width * _height * sizeof(uint32_t));
+ }
+
+ const void* getPixelsPtr() const {
+ return _imageData.data();
+ }
+
+ int getRowBytes() const {
+
+ return _width * sizeof(uint32_t);
+ }
+
+ void swapNow() {
+ auto buf = Buffer::createShmBuffer(fWindow->_windowManager.shm, _width, _height, WL_SHM_FORMAT_XRGB8888);
+ void* daData = buf->getData();
+ size_t size = buf->getSize();
+ memcpy(daData, _imageData.data(), size);
+ wl_surface_attach(fWindow->_waylandWindow, buf->getBuffer(), 0, 0);
+ wl_surface_damage_buffer(fWindow->_waylandWindow, 0, 0, INT32_MAX, INT32_MAX);
+ wl_surface_set_buffer_scale(fWindow->_waylandWindow, fWindow->_scale);
+ wl_surface_commit(fWindow->_waylandWindow);
+
+ }
+ void swapBuffers() override {
+ if (_attached && fWindow->_waylandWindow) {
+ // all impls that I've seen have to make a new buffer every frame.
+ // God awful. Never use raster if you value performance.
+ swapNow();
+ }
+ }
+
+ void close() override {
+ detachBuffer();
+ if (fWindow) {
+ fWindow->setLayer(nullptr);
+ jwm::unref(&fWindow);
+ }
+ }
+
+ void makeCurrentForced() override {
+ ILayer::makeCurrentForced();
+ }
+
+ void setVsyncMode(VSync v) override {
+ }
+
+ void attachBuffer() override {
+ _attached = true;
+ }
+
+ void detachBuffer() override {
+ ILayerWayland::detachBuffer();
+ if (_attached && fWindow && fWindow->_waylandWindow) {
+ wl_surface_attach(fWindow->_waylandWindow, nullptr, 0, 0);
+ // commit is not meant to be used in intermediate states
+ // wl_surface_commit(fWindow->_waylandWindow);
+ }
+ _attached = false;
+ }
+ };
+
+}
+using namespace jwm;
+extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nMake
+ (JNIEnv* env, jclass jclass) {
+ jwm::LayerRaster* instance = new jwm::LayerRaster;
+ return reinterpret_cast(instance);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nAttach
+ (JNIEnv* env, jobject obj, jobject windowObj) {
+ jwm::LayerRaster* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ jwm::WindowWayland* window = reinterpret_cast(jwm::classes::Native::fromJava(env, windowObj));
+ instance->attach(window);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nReconfigure
+ (JNIEnv* env, jobject obj) {
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nResize
+ (JNIEnv* env, jobject obj, jint width, jint height) {
+ jwm::LayerRaster* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ instance->resize(width, height);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nSwapBuffers
+ (JNIEnv* env, jobject obj) {
+ jwm::LayerRaster* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ instance->swapBuffers();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nClose
+ (JNIEnv* env, jobject obj) {
+ jwm::LayerRaster* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ instance->close();
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nGetPixelsPtr
+ (JNIEnv* env, jobject obj) {
+ jwm::LayerRaster* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ return reinterpret_cast(instance->getPixelsPtr());
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nGetRowBytes
+ (JNIEnv* env, jobject obj) {
+ jwm::LayerRaster* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj));
+ return static_cast(instance->getRowBytes());
+}
diff --git a/wayland/cc/MouseButtonWayland.cc b/wayland/cc/MouseButtonWayland.cc
new file mode 100644
index 00000000..b81f1f37
--- /dev/null
+++ b/wayland/cc/MouseButtonWayland.cc
@@ -0,0 +1,29 @@
+#include "MouseButtonWayland.hh"
+#include
+
+jwm::MouseButton jwm::MouseButtonWayland::fromNative(uint32_t v) {
+ switch (v) {
+ case BTN_LEFT: return jwm::MouseButton::PRIMARY;
+ case BTN_MIDDLE: return jwm::MouseButton::MIDDLE;
+ case BTN_RIGHT: return jwm::MouseButton::SECONDARY;
+ // TODO: is this mapping consistent?
+ // I've gotten this from observing my mouse
+ case BTN_SIDE:
+ case BTN_BACK: return jwm::MouseButton::BACK;
+ case BTN_EXTRA:
+ case BTN_FORWARD: return jwm::MouseButton::FORWARD;
+ }
+ return jwm::MouseButton::PRIMARY;
+}
+
+bool jwm::MouseButtonWayland::isButton(uint32_t v) {
+ return v >= 0x110 && v <= 0x116; // mouse wheel buttons
+}
+
+int jwm::MouseButtonWayland::fromNativeMask(unsigned v) {
+ int res = 0;
+ if (v & 0x100) res |= int(jwm::MouseButton::PRIMARY);
+ if (v & 0x400) res |= int(jwm::MouseButton::SECONDARY);
+ if (v & 0x200) res |= int(jwm::MouseButton::MIDDLE);
+ return res;
+}
diff --git a/wayland/cc/MouseButtonWayland.hh b/wayland/cc/MouseButtonWayland.hh
new file mode 100644
index 00000000..1cf9f960
--- /dev/null
+++ b/wayland/cc/MouseButtonWayland.hh
@@ -0,0 +1,12 @@
+#pragma once
+
+#include
+#include "MouseButton.hh"
+
+namespace jwm {
+ namespace MouseButtonWayland {
+ MouseButton fromNative(uint32_t v);
+ int fromNativeMask(unsigned v);
+ bool isButton(uint32_t v);
+ }
+}
diff --git a/wayland/cc/Output.cc b/wayland/cc/Output.cc
new file mode 100644
index 00000000..1cb9d0a2
--- /dev/null
+++ b/wayland/cc/Output.cc
@@ -0,0 +1,64 @@
+#include "Output.hh"
+#include "AppWayland.hh"
+
+
+using namespace jwm;
+
+wl_output_listener Output::_outputListener = {
+ .geometry = Output::outputGeometry,
+ .mode = Output::outputMode,
+ .done = Output::outputDone,
+ .scale = Output::outputScale,
+ .name = Output::outputName,
+ .description = Output::outputDescription
+};
+Output::Output(wl_output* output, uint32_t name):
+ _output(output),
+ _name(name)
+ {
+ wl_output_add_listener(output, &_outputListener, this);
+ wl_proxy_set_tag((wl_proxy*) output, &AppWayland::proxyTag);
+ }
+Output::~Output()
+{
+ if (_output)
+ wl_output_release(_output);
+}
+
+ScreenInfo Output::getScreenInfo() const {
+ return {
+ .id = _name,
+ .bounds = jwm::IRect::makeXYWH(0, 0, width, height),
+ .isPrimary = false,
+ .scale = scale
+ };
+}
+void Output::outputGeometry(void* data, wl_output* output, int x, int y, int physWidth, int physHeight,
+ int subPixel, const char* make, const char* model, int transform) {}
+void Output::outputMode(void* data, wl_output* output, uint32_t flags, int width, int height, int refresh) {
+ Output* self = reinterpret_cast