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(data); + self->width = width; + self->height = height; +} +void Output::outputDone(void* data, wl_output* output) {} +void Output::outputScale(void* data, wl_output* output, int factor) { + Output* self = reinterpret_cast(data); + self->scale = factor; +} +void Output::outputName(void* data, wl_output* output, const char* name) { + +} +void Output::outputDescription(void* data, wl_output* output, const char* desc) {} + +Output* Output::getForNative(wl_output* output) { + if (!output) return nullptr; + + if (ownOutput(output)) { + return reinterpret_cast(wl_output_get_user_data(output)); + } + return nullptr; +} + +bool Output::ownOutput(wl_output* output) { + return AppWayland::ownProxy((wl_proxy*) output); +} diff --git a/wayland/cc/Output.hh b/wayland/cc/Output.hh new file mode 100644 index 00000000..b137d5a3 --- /dev/null +++ b/wayland/cc/Output.hh @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include "ScreenInfo.hh" + +namespace jwm { + class Output { + public: + Output(wl_output* output, uint32_t name); + ~Output(); + + wl_output* _output = nullptr; + uint32_t _name; + int scale = 1; + int width = 0; + int height = 0; + + ScreenInfo getScreenInfo() const; + + static wl_output_listener _outputListener; + static void outputGeometry(void* data, wl_output* output, int x, int y, int physWidth, int physHeight, + int subPixel, const char* make, const char* model, int transform); + static void outputMode(void* data, wl_output* output, uint32_t flags, int width, int height, int refresh); + static void outputDone(void* data, wl_output* output); + static void outputScale(void* data, wl_output* output, int factor); + static void outputName(void* data, wl_output* output, const char* name); + static void outputDescription(void* data, wl_output* output, const char* desc); + + static Output* getForNative(wl_output* output); + static bool ownOutput(wl_output* output); + private: + Output(const Output&) = delete; + Output(Output&&) = delete; + Output& operator=(const Output&) = delete; + Output& operator=(Output&&) = delete; + }; +} diff --git a/wayland/cc/Pointer.cc b/wayland/cc/Pointer.cc new file mode 100644 index 00000000..b131ef4d --- /dev/null +++ b/wayland/cc/Pointer.cc @@ -0,0 +1,526 @@ +#include "Pointer.hh" +#include "WindowManagerWayland.hh" +#include "WindowWayland.hh" +#include "MouseButtonWayland.hh" +#include "AppWayland.hh" +#include "KeyWayland.hh" +#include +#include +#include +#include +#include + +using namespace jwm; + +static void pointerEnter(void* data, wl_pointer* pointer, uint32_t serial, + wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y + ) +{ + if (!Pointer::ownPointer(pointer)) { + return; + } + auto self = reinterpret_cast(data); + DecorationFocus focus = DECORATION_FOCUS_MAIN; + if (auto window = self->_wm.getWindowForNative(surface)) { + self->_serial = serial; + self->_focusedSurface = jwm::ref(window); + window->setCursorMaybe(jwm::MouseCursor::ARROW, true); + // frame probably isn't called so I immediately call + self->mouseUpdateUnscaled(wl_fixed_to_int(surface_x), wl_fixed_to_int(surface_y), 0, 0, self->_mouseMask); + } else if (auto decoration = Decoration::getDecorationForSurface(surface, &focus)) { + self->_serial = serial; + auto window = jwm::ref(&decoration->_window); + self->_focusedSurface = window; + window->setCursorMaybe(jwm::MouseCursor::ARROW, true); + } + self->_decorationFocus = focus; +} + +static void pointerLeave(void* data, wl_pointer* pointer, uint32_t serial, + wl_surface* surface) +{ + if (!Pointer::ownPointer(pointer)) { + return; + } + auto self = reinterpret_cast(data); + DecorationFocus focus = DECORATION_FOCUS_MAIN; + Decoration* decoration = Decoration::getDecorationForSurface(surface, &focus); + if (self->_focusedSurface && self->_focusedSurface->isNativeSelf(surface) && (!decoration || !decoration->_window.isNativeSelf(surface))) { + jwm::unref(&self->_focusedSurface); + self->_mouseMask = 0; + self->_serial = 0; + self->_decorationFocus = DECORATION_FOCUS_MAIN; + } +} +static xdg_toplevel_resize_edge resizeEdge(DecorationFocus focus, WindowWayland& window, int x, int y, xkb_state* state) { + int topEdge = window._decoration->_isVisible ? DECORATION_TITLE_Y : 0; + int rightEdge = window.getUnscaledWidth(); + int bottomEdge = window.getUnscaledHeight(); + switch (focus) { + case DECORATION_FOCUS_MAIN: + // ??? + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + case DECORATION_FOCUS_TITLE: + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + case DECORATION_FOCUS_BORDER: + if (state) { + if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE)) { + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + } + } + if (y < topEdge) { + if (x < 0) + return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; + else if (x > rightEdge) + return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; + else { + return XDG_TOPLEVEL_RESIZE_EDGE_TOP; + } + } + else if (y > bottomEdge) { + if (x < 0) + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + else if (x > rightEdge) + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + else + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + } else { + if (x < 0) + return XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + else if (x > rightEdge) + return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + else + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + } + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + default: + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + } +} +static void pointerMotion(void* data, wl_pointer* pointer, uint32_t time, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + if (!Pointer::ownPointer(pointer)) { + return; + } + auto self = reinterpret_cast(data); + self->unhide(); + if (self->_decorationFocus != DECORATION_FOCUS_MAIN && !self->_focusedSurface) + return; + int x = wl_fixed_to_int(surface_x); + int y = wl_fixed_to_int(surface_y); + // ??? + switch (self->_decorationFocus) { + case DECORATION_FOCUS_MAIN: + self->_absX = x; + self->_absY = y; + self->_movement = true; + break; + default: + if (self->_decorationFocus == DECORATION_FOCUS_BORDER) { + self->_absX = x - DECORATION_WIDTH; + self->_absY = y - DECORATION_WIDTH; + if (self->_focusedSurface->_decoration->_isVisible) + self->_absY -= DECORATION_TOP_HEIGHT; + } else if (self->_decorationFocus == DECORATION_FOCUS_TITLE) { + self->_absX = x - DECORATION_WIDTH; + self->_absY = y - DECORATION_TOP_HEIGHT; + } + auto edge = resizeEdge(self->_decorationFocus, *self->_focusedSurface, self->_absX, self->_absY, + self->_wm.getXkbState()); + switch (edge) { + case XDG_TOPLEVEL_RESIZE_EDGE_TOP: + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM: + self->_focusedSurface->setCursor(jwm::MouseCursor::RESIZE_NS); + break; + case XDG_TOPLEVEL_RESIZE_EDGE_LEFT: + case XDG_TOPLEVEL_RESIZE_EDGE_RIGHT: + self->_focusedSurface->setCursor(jwm::MouseCursor::RESIZE_WE); + break; + case XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT: + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT: + self->_focusedSurface->setCursor(jwm::MouseCursor::RESIZE_NWSE); + break; + case XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT: + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT: + self->_focusedSurface->setCursor(jwm::MouseCursor::RESIZE_NESW); + break; + default: + self->_focusedSurface->setCursor(jwm::MouseCursor::POINTING_HAND); + break; + } + break; + + } + // I only unhide here, bc i'm unsure of what exactly is wanted with mouse lock. + // This event isn't sent on mouse lock, only relative events are sent. +} + +static void pointerButton(void* data, wl_pointer* pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state) +{ + using namespace classes; + auto self = reinterpret_cast(data); + auto window = self->_focusedSurface; + if (!window) return; + int scale = window->getIntScale(); + if (self->_decorationFocus != DECORATION_FOCUS_MAIN) { + if (state == 0) + return; + if (button == BTN_RIGHT) { + if (self->_absY < 0) + xdg_toplevel_show_window_menu(window->_decoration->_xdgToplevel, self->_seat, serial, + self->_absX + DECORATION_WIDTH, self->_absY + window->_decoration->getTopSize()); + return; + } + if (button != BTN_LEFT) + return; + switch (self->_decorationFocus) { + case DECORATION_FOCUS_CLOSE_BUTTON: + window->dispatch(EventWindowCloseRequest::kInstance); + break; + case DECORATION_FOCUS_MIN_BUTTON: + xdg_toplevel_set_minimized(window->_decoration->_xdgToplevel); + break; + case DECORATION_FOCUS_MAX_BUTTON: + if (window->_decoration->_maximized) + xdg_toplevel_unset_maximized(window->_decoration->_xdgToplevel); + else + xdg_toplevel_set_maximized(window->_decoration->_xdgToplevel); + break; + case DECORATION_FOCUS_BORDER: + case DECORATION_FOCUS_TITLE: + xdg_toplevel_resize_edge edge = resizeEdge(self->_decorationFocus, *window, self->_absX, self->_absY, self->_wm.getXkbState()); + if (edge == XDG_TOPLEVEL_RESIZE_EDGE_NONE) + xdg_toplevel_move(window->_decoration->_xdgToplevel, self->_seat, serial); + else + xdg_toplevel_resize(window->_decoration->_xdgToplevel, self->_seat, serial, static_cast(edge)); + break; + } + return; + } + if (state == 0) { + // release + switch (button) { + // primary + case BTN_LEFT: + self->_mouseMask &= ~0x100; + break; + // secondary + case BTN_RIGHT: + self->_mouseMask &= ~0x400; + break; + // middle + case BTN_MIDDLE: + self->_mouseMask &= ~0x200; + break; + default: + break; + } + + if (MouseButtonWayland::isButton(button)) { + jwm::JNILocal eventButton( + app.getJniEnv(), + jwm::classes::EventMouseButton::make( + app.getJniEnv(), + MouseButtonWayland::fromNative(button), + false, + self->_absX * scale, + self->_absY * scale, + jwm::KeyWayland::getModifiers(self->_wm.getXkbState()) + ) + ); + window->dispatch(eventButton.get()); + } + } else { + // down + switch (button) { + // primary + case BTN_LEFT: + self->_mouseMask |= 0x100; + break; + // secondary + case BTN_RIGHT: + self->_mouseMask |= 0x400; + break; + // middle + case BTN_MIDDLE: + self->_mouseMask |= 0x200; + break; + default: + break; + } + + if (MouseButtonWayland::isButton(button)) { + jwm::JNILocal eventButton( + app.getJniEnv(), + jwm::classes::EventMouseButton::make( + app.getJniEnv(), + MouseButtonWayland::fromNative(button), + true, + self->_absX * scale, + self->_absY * scale, + jwm::KeyWayland::getModifiers(self->_wm.getXkbState()) + ) + ); + window->dispatch(eventButton.get()); + } + } +} + +static void pointerAxis(void* data, wl_pointer* pointer, uint32_t time, + uint32_t axis, wl_fixed_t value) +{ + auto self = reinterpret_cast(data); + if (!self->_focusedSurface) return; + float fvalue = static_cast(wl_fixed_to_double(value)); + switch (axis) { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + self->_dY += -fvalue; + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + self->_dX += fvalue; + break; + default: + break; + } +} + +static void pointerFrame(void* data, wl_pointer* pointer) +{ + auto self = reinterpret_cast(data); + auto win = self->_focusedSurface; + if (!win) return; + if (self->_decorationFocus != DECORATION_FOCUS_MAIN) return; + if (self->_dX != 0.0f || self->_dY != 0.0f) { + auto env = app.getJniEnv(); + + jwm::JNILocal eventAxis( + env, + jwm::classes::EventMouseScroll::make( + env, + self->_dX * win->_scale, + self->_dY * win->_scale, + 0.0f, + 0.0f, + 0.0f, + self->_absX * win->_scale, + self->_absY * win->_scale, + jwm::KeyWayland::getModifiers(self->_wm.getXkbState()) + ) + ); + win->dispatch(eventAxis.get()); + + self->_dX = 0.0f; + self->_dY = 0.0f; + } + if (self->_movement || self->_dXPos != 0.0 || self->_dYPos != 0.0) { + auto scale = win->_scale; + // rounding inaccuracy? + self->mouseUpdateUnscaled(self->_absX, self->_absY, static_cast(self->_dXPos), static_cast(self->_dYPos), self->_mouseMask); + self->_movement = false; + self->_dXPos = 0.0; + self->_dYPos = 0.0; + } +} + +static void pointerAxisSource(void* data, wl_pointer* pointer, uint32_t source) {} +static void pointerAxisStop(void* data, wl_pointer* pointer, uint32_t time, uint32_t axis) {} +static void pointerAxisDiscrete(void* data, wl_pointer* pointer, uint32_t axis, int discrete) {} + +wl_pointer_listener Pointer::_pointerListener = { + .enter = pointerEnter, + .leave = pointerLeave, + .motion = pointerMotion, + .button = pointerButton, + .axis = pointerAxis, + .frame = pointerFrame, + .axis_source = pointerAxisSource, + .axis_stop = pointerAxisStop, + .axis_discrete = pointerAxisDiscrete +}; + +static void relativePointerRelativeMotion(void* data, zwp_relative_pointer_v1* relative, uint32_t utime_hi, uint32_t utime_lo, + wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) { + auto self = reinterpret_cast(data); + if (self->_decorationFocus != DECORATION_FOCUS_MAIN) return; + self->_dXPos += static_cast(wl_fixed_to_double(dx)); + self->_dYPos += static_cast(wl_fixed_to_double(dy)); +} +static zwp_relative_pointer_v1_listener relativePointerListener = { + .relative_motion = relativePointerRelativeMotion +}; +Pointer::Pointer(wl_seat* seat, wl_pointer* pointer, WindowManagerWayland* wm): + _pointer(pointer), + _wm(*wm), + _seat(seat) +{ + _surface = wl_compositor_create_surface(_wm.compositor); + wl_pointer_add_listener(pointer, &_pointerListener, this); + wl_proxy_set_tag((wl_proxy*)pointer, &AppWayland::proxyTag); + if (_wm.relativePointerManager) { + _relative = zwp_relative_pointer_manager_v1_get_relative_pointer(_wm.relativePointerManager, pointer); + zwp_relative_pointer_v1_add_listener(_relative, &relativePointerListener, this); + } +} + +Pointer::~Pointer() +{ + if (_pointer) + wl_pointer_release(_pointer); + if (_surface) + wl_surface_destroy(_surface); +} + +void Pointer::mouseUpdate(uint32_t x, uint32_t y, int32_t relX, int32_t relY, uint32_t mask) { + auto window = _focusedSurface; + if (!window) + return; + + jwm::JNILocal eventMove( + app.getJniEnv(), + jwm::classes::EventMouseMove::make(app.getJniEnv(), + x, + y, + relX, + relY, + jwm::MouseButtonWayland::fromNativeMask(mask), + // impl me! + jwm::KeyWayland::getModifiers(_wm.getXkbState()) + ) + ); + window->dispatch(eventMove.get()); +} + +void Pointer::mouseUpdateUnscaled(uint32_t x, uint32_t y, int32_t relX, int32_t relY, uint32_t mask) { + if (!_focusedSurface) return; + auto newX = x * _focusedSurface->_scale; + auto newY = y * _focusedSurface->_scale; + auto newDX = relX * _focusedSurface->_scale; + auto newDY = relY * _focusedSurface->_scale; + + mouseUpdate(newX, newY, newDX, newDY, mask); +} + +void Pointer::updateHotspot(int x, int y) { + if (!_focusedSurface) return; + wl_pointer_set_cursor(_pointer, _serial, _surface, x / _focusedSurface->_scale, y / _focusedSurface->_scale); +} + +wl_cursor_theme* Pointer::_makeCursors(int scale) { + auto theme = wl_cursor_theme_load(nullptr, 24 * scale, _wm.shm); + _cursorThemes[scale] = theme; + return theme; +} + +wl_cursor_theme* Pointer::getThemeFor(int scale) { + auto it = _cursorThemes.find(scale); + if (it != _cursorThemes.end()) + return it->second; + return _makeCursors(scale); +} +wl_cursor* Pointer::getCursorFor(int scale, jwm::MouseCursor cursor) { + auto theme = getThemeFor(scale); + + switch (cursor) { + case jwm::MouseCursor::ARROW: + return wl_cursor_theme_get_cursor(theme, "default"); + case jwm::MouseCursor::CROSSHAIR: + return wl_cursor_theme_get_cursor(theme, "crosshair"); + case jwm::MouseCursor::HELP: + return wl_cursor_theme_get_cursor(theme, "help"); + case jwm::MouseCursor::POINTING_HAND: + return wl_cursor_theme_get_cursor(theme, "pointer"); + case jwm::MouseCursor::IBEAM: + return wl_cursor_theme_get_cursor(theme, "text"); + case jwm::MouseCursor::NOT_ALLOWED: + return wl_cursor_theme_get_cursor(theme, "not-allowed"); + case jwm::MouseCursor::WAIT: + return wl_cursor_theme_get_cursor(theme, "watch"); + case jwm::MouseCursor::WIN_UPARROW: + return wl_cursor_theme_get_cursor(theme, "up-arrow"); + case jwm::MouseCursor::RESIZE_NS: + return wl_cursor_theme_get_cursor(theme, "ns-resize"); + case jwm::MouseCursor::RESIZE_WE: + return wl_cursor_theme_get_cursor(theme, "ew-resize"); + case jwm::MouseCursor::RESIZE_NESW: + return wl_cursor_theme_get_cursor(theme, "nesw-resize"); + case jwm::MouseCursor::RESIZE_NWSE: + return wl_cursor_theme_get_cursor(theme, "nwse-resize"); + } + return nullptr; +} + +bool Pointer::ownPointer(wl_pointer* pointer) { + return AppWayland::ownProxy((wl_proxy*) pointer); +} + +static void lockLocked(void* data, zwp_locked_pointer_v1* pointer) { + auto self = reinterpret_cast(data); + + self->_locked = true; +} +static void lockUnlocked(void* data, zwp_locked_pointer_v1* pointer) { + zwp_locked_pointer_v1_destroy(pointer); + + auto self = reinterpret_cast(data); + + self->_lock = nullptr; + if (self->_locked) { + // Request a new lock on surface reenter + self->lock(); + } +} + +static zwp_locked_pointer_v1_listener lockListener = { + .locked = lockLocked, + .unlocked = lockUnlocked +}; +void Pointer::lock() { + if (_wm.pointerConstraints && _focusedSurface && _focusedSurface->_waylandWindow) { + if (_lock) { + zwp_locked_pointer_v1_destroy(_lock); + } + _lock = zwp_pointer_constraints_v1_lock_pointer(_wm.pointerConstraints, _focusedSurface->_waylandWindow, + _pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); + zwp_locked_pointer_v1_add_listener(_lock, &lockListener, this); + hide(); + } +} +void Pointer::unlock() { + if (_lock) { + zwp_locked_pointer_v1_destroy(_lock); + } + unhide(); + _lock = nullptr; + _locked = false; +} + +void Pointer::hide() { + if (_hidden) return; + _hidden = true; + if (_surface) { + wl_surface_attach(_surface, nullptr, 0, 0); + wl_surface_commit(_surface); + } +} + +void Pointer::unhide() { + if (!_hidden) return; + _hidden = false; + setCursor(_scale, _cursor, true); +} + +void Pointer::setCursor(int scale, jwm::MouseCursor cursor, bool force) { + if (!force && _cursor == cursor && _scale == scale) + return; + _cursor = cursor; + _scale = scale; + auto wayCursor = getCursorFor(scale, cursor)->images[0]; + auto buf = wl_cursor_image_get_buffer(wayCursor); + wl_surface_attach(_surface, buf, 0, 0); + wl_surface_set_buffer_scale(_surface, scale); + wl_surface_damage_buffer(_surface, 0, 0, INT32_MAX, INT32_MAX); + updateHotspot(wayCursor->hotspot_x, wayCursor->hotspot_y); + wl_surface_commit(_surface); +} diff --git a/wayland/cc/Pointer.hh b/wayland/cc/Pointer.hh new file mode 100644 index 00000000..739a3399 --- /dev/null +++ b/wayland/cc/Pointer.hh @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include +#include "MouseCursor.hh" +#include +#include +#include "Decoration.hh" + +namespace jwm { + class WindowManagerWayland; + class WindowWayland; + class Pointer { + public: + Pointer(wl_seat* seat, wl_pointer* pointer, jwm::WindowManagerWayland* wm); + ~Pointer(); + + wl_seat* _seat = nullptr; + + wl_pointer* _pointer = nullptr; + wl_pointer* getPointer() const { + return _pointer; + } + + uint32_t _serial = 0; + uint32_t getSerial() { + return _serial; + } + + wl_surface* _surface = nullptr; + wl_surface* getSurface() const { + return _surface; + } + + WindowWayland* _focusedSurface = nullptr; + DecorationFocus _decorationFocus = DECORATION_FOCUS_MAIN; + WindowWayland* getFocusedSurface() const { + return _focusedSurface; + } + + zwp_locked_pointer_v1* _lock = nullptr; + bool _locked = false; + void lock(); + void unlock(); + bool isLocked() { + return _locked; + } + + bool _hidden = false; + void hide(); + void unhide(); + bool isHidden() { + return _hidden; + } + + zwp_relative_pointer_v1* _relative = nullptr; + bool _movement = false; + int _absX = 0; + int _absY = 0; + float _dXPos = 0.0; + float _dYPos = 0.0; + float _dX = 0.0; + float _dY = 0.0; + + int _mouseMask = 0; + jwm::WindowManagerWayland& _wm; + + static wl_pointer_listener _pointerListener; + + void mouseUpdate(uint32_t x, uint32_t y, int32_t relX, int32_t relY, uint32_t mask); + void mouseUpdateUnscaled(uint32_t x, uint32_t y, int32_t relX, int32_t relY, uint32_t mask); + + void updateHotspot(int x, int y); + + std::map _cursorThemes; + + wl_cursor_theme* _makeCursors(int scale); + wl_cursor_theme* getThemeFor(int scale); + wl_cursor* getCursorFor(int scale, jwm::MouseCursor cursor); + + jwm::MouseCursor _cursor = jwm::MouseCursor::ARROW; + int _scale = 1; + + void setCursor(int scale, jwm::MouseCursor cursor, bool force); + + static bool ownPointer(wl_pointer* pointer); + + + private: + Pointer(const Pointer& other) = delete; + Pointer(Pointer&&) = delete; + Pointer& operator=(const Pointer& other) = delete; + Pointer& operator=(Pointer&&) = delete; + }; +} diff --git a/wayland/cc/ScreenInfo.cc b/wayland/cc/ScreenInfo.cc new file mode 100644 index 00000000..0dc625b2 --- /dev/null +++ b/wayland/cc/ScreenInfo.cc @@ -0,0 +1,5 @@ +#include "ScreenInfo.hh" +#include "AppWayland.hh" +jobject jwm::ScreenInfo::asJavaObject(JNIEnv* env) const { + return jwm::classes::Screen::make(env, id, isPrimary, bounds, bounds, scale); +} diff --git a/wayland/cc/ScreenInfo.hh b/wayland/cc/ScreenInfo.hh new file mode 100644 index 00000000..99d3eb9d --- /dev/null +++ b/wayland/cc/ScreenInfo.hh @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace jwm +{ + struct ScreenInfo { + long id; + IRect bounds; + bool isPrimary; + int scale; + jobject asJavaObject(JNIEnv* env) const; + }; +} // namespace jwm diff --git a/wayland/cc/Token.cc b/wayland/cc/Token.cc new file mode 100644 index 00000000..d95034b2 --- /dev/null +++ b/wayland/cc/Token.cc @@ -0,0 +1,31 @@ +#include "Token.hh" +#include "WindowManagerWayland.hh" + + +static void _xdgTokenDone(void* data, xdg_activation_token_v1* token, const char* tokenStr) { + xdg_activation_token_v1_destroy(token); + std::string* str = reinterpret_cast(data); + *str = std::string(tokenStr); +} +static xdg_activation_token_v1_listener _tokenListener = { + .done = _xdgTokenDone +}; +jwm::Token jwm::Token::make(jwm::WindowManagerWayland& wm, wl_surface* surface) { + if (!wm.xdgActivation) + return {}; + auto token = xdg_activation_v1_get_activation_token(wm.xdgActivation); + if (surface) + xdg_activation_token_v1_set_surface(token, surface); + std::string str; + xdg_activation_token_v1_add_listener(token, &_tokenListener, &str); + xdg_activation_token_v1_commit(token); + wl_display_roundtrip(wm.display); + + return {str}; +} + +void jwm::Token::grab(jwm::WindowManagerWayland& wm, wl_surface* surface) { + if (!wm.xdgActivation) + return; + xdg_activation_v1_activate(wm.xdgActivation, token.c_str(), surface); +} diff --git a/wayland/cc/Token.hh b/wayland/cc/Token.hh new file mode 100644 index 00000000..23f781cf --- /dev/null +++ b/wayland/cc/Token.hh @@ -0,0 +1,14 @@ +#include +#include +#include + +namespace jwm { + class WindowManagerWayland; + struct Token { + std::string token; + // blocks + static Token make(WindowManagerWayland& wm, wl_surface* surface); + + void grab(WindowManagerWayland& wm, wl_surface* surface); + }; +} diff --git a/wayland/cc/WindowManagerWayland.cc b/wayland/cc/WindowManagerWayland.cc new file mode 100644 index 00000000..14bc4f5a --- /dev/null +++ b/wayland/cc/WindowManagerWayland.cc @@ -0,0 +1,447 @@ +#include "WindowManagerWayland.hh" +#include "WindowWayland.hh" +#include +#include +#include +#include +#include +#include "AppWayland.hh" +#include +#include +#include +#include "KeyWayland.hh" +#include "MouseButtonWayland.hh" +#include "StringUTF16.hh" +#include +#include +#include "Log.hh" +#include +#include "Output.hh" +#include +#include +#include +#include +#include +#include + +using namespace jwm; + +wl_registry_listener WindowManagerWayland::_registryListener = { + .global = WindowManagerWayland::registryHandleGlobal, + .global_remove = WindowManagerWayland::registryHandleGlobalRemove +}; +wl_seat_listener WindowManagerWayland::_seatListener = { + .capabilities = WindowManagerWayland::seatCapabilities, + .name = WindowManagerWayland::seatName +}; + +static void xdgWmBasePing(void* data, xdg_wm_base* wm, uint32_t serial) { + xdg_wm_base_pong(wm, serial); +} +static xdg_wm_base_listener _wmListener = { + .ping = xdgWmBasePing +}; +WindowManagerWayland::WindowManagerWayland(): + display(wl_display_connect(nullptr)) { + registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, &_registryListener, this); + wl_display_roundtrip(display); + + + + if (!(shm && compositor && deviceManager && seat && xdgWm && subcompositor && viewporter)) { + // ??? + // Bad. Means our compositor no supportie : ( + throw std::system_error(ENOTSUP, std::generic_category(), "Unsupported compositor"); + } + + xdg_wm_base_add_listener(xdgWm, &_wmListener, nullptr); + // ???: Moving this after libdecor_new causes input to not work + wl_seat_add_listener(seat, &_seatListener, this); + dataDevice = wl_data_device_manager_get_data_device(deviceManager, seat); + wl_data_device_add_listener(dataDevice, &_deviceListener, this); + + + wl_display_roundtrip(display); + +} + + + + +void WindowManagerWayland::runLoop() { + _runLoop = true; + int pipes[2]; + + char buf[100]; + if (pipe(pipes)) { + printf("failed to open pipe\n"); + return; + } + notifyFD = pipes[1]; + fcntl(pipes[1], F_SETFL, O_NONBLOCK); // notifyLoop no blockie : ) + struct pollfd wayland_out = {.fd=wl_display_get_fd(display),.events=POLLOUT}; + struct pollfd ps[] = { + {.fd=wl_display_get_fd(display), .events=POLLIN}, + {.fd=pipes[0], .events=POLLIN}, + }; + + // who be out here running they loop + while (_runLoop) { + if (jwm::classes::Throwable::exceptionThrown(app.getJniEnv())) + _runLoop = false; + while (wl_display_prepare_read(display) != 0) { + wl_display_dispatch_pending(display); + + } + while (true) { + int res = wl_display_flush(display); + if (res >= 0) + break; + + switch (errno) { + case EPIPE: + wl_display_read_events(display); + throw std::system_error(errno, std::generic_category(), "connection to wayland server unexpectedly terminated"); + break; + case EAGAIN: + if (poll(&wayland_out, 1, -1) < 0) { + throw std::system_error(EPIPE, std::generic_category(), "poll failed"); + } + break; + default: + throw std::system_error(errno, std::generic_category(), "failed to flush requests"); + break; + } + + } + // block until event : ) + int timeout = -1; + if (_keyboard && _keyboard->_repeating && _keyboard->getFocus()) { + if (_keyboard->_repeatRate > 0) { + auto now = std::chrono::steady_clock::now(); + auto target = _keyboard->_nextRepeat; + if (now < target) { + timeout = std::chrono::duration_cast(target - now).count(); + } else { + _processKeyboard(); + } + } + } + if (poll(&ps[0], 2, timeout) < 0) { + printf("error with pipe\n"); + wl_display_cancel_read(display); + break; + } + if (ps[0].revents & POLLIN) { + if (wl_display_read_events(display) < 0) { + std::perror("events failed"); + break; + } + } else { + wl_display_cancel_read(display); + } + + if (ps[1].revents & POLLIN) { + while (read(pipes[0], buf, sizeof(buf)) == sizeof(buf)) { } + } + if (ps[0].revents & POLLIN || ps[1].revents & POLLIN) { + _processCallbacks(); + } else { + // don't test if we already calculated earlier + _processKeyboard(); + } + + wl_display_dispatch_pending(display); + notifyBool.store(false); + } + + notifyFD = -1; + close(pipes[0]); + close(pipes[1]); + +} + +void WindowManagerWayland::_processCallbacks() { + { + // process ui thread callbacks + std::unique_lock lock(_taskQueueLock); + while (!_taskQueue.empty()) { + auto callback = std::move(_taskQueue.front()); + _taskQueue.pop(); + lock.unlock(); + callback(); + lock.lock(); + } + } + { + std::list copy(_windows); + // process redraw requests + for (auto p : copy) { + if (p->isRedrawRequested()) { + p->unsetRedrawRequest(); + if (p->_visible && p->isConfigured()) { + if (p->_layer) { + p->_layer->makeCurrent(); + } + p->dispatch(classes::EventFrame::kInstance); + } + } + } + } + +} +void WindowManagerWayland::_processKeyboard() { + if (!_keyboard || !_keyboard->_repeating) return; + auto focus = _keyboard->getFocus(); + if (!focus) return; + auto now = std::chrono::steady_clock::now(); + _keyboard->_nextRepeat = now + std::chrono::milliseconds(_keyboard->_repeatRate); + auto env = jwm::app.getJniEnv(); + jwm::KeyLocation location = jwm::KeyLocation::DEFAULT; + JNILocal keyOffEvent( + env, + classes::EventKey::make( + env, + _keyboard->_repeatKey, + false, + KeyWayland::getModifiers(_keyboard->_state), + location + ) + ); + + JNILocal keyEvent( + env, + classes::EventKey::make( + env, + _keyboard->_repeatKey, + true, + KeyWayland::getModifiers(_keyboard->_state), + location + ) + ); + focus->dispatch(keyOffEvent.get()); + focus->dispatch(keyEvent.get()); + if (_keyboard->_repeatingText) { + jwm::JNILocal jtext = _keyboard->_repeatText.toJString(env); + jwm::JNILocal eventTextInput(env, classes::EventTextInput::make(env, jtext.get())); + + focus->dispatch(eventTextInput.get()); + } + +} + +void WindowManagerWayland::registryHandleGlobal(void* data, wl_registry *registry, + uint32_t name, const char* interface, uint32_t version) { + WindowManagerWayland* self = reinterpret_cast(data); + if (strcmp(interface, wl_compositor_interface.name) == 0) { + // EGL apparently requires at least a version of 4 here : ) + self->compositor = (wl_compositor*)wl_registry_bind(registry, name, + &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + self->shm = (wl_shm*)wl_registry_bind(registry, name, + &wl_shm_interface, 1); + } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + self->deviceManager = (wl_data_device_manager*)wl_registry_bind(registry, name, + &wl_data_device_manager_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + self->seat = (wl_seat*)wl_registry_bind(registry, name, + &wl_seat_interface, 5); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + wl_output* output = (wl_output*)wl_registry_bind(registry, name, + &wl_output_interface, 2); + Output* good = new Output(output, name); + self->outputs.push_back(std::move(good)); + } else if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { + self->pointerConstraints = (zwp_pointer_constraints_v1*)wl_registry_bind(registry, name, + &zwp_pointer_constraints_v1_interface, 1); + } else if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) { + self->relativePointerManager = (zwp_relative_pointer_manager_v1*)wl_registry_bind(registry, name, + &zwp_relative_pointer_manager_v1_interface, 1); + } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + self->viewporter = (wp_viewporter*)wl_registry_bind(registry, name, + &wp_viewporter_interface, 1); + } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { + self->subcompositor = (wl_subcompositor*)wl_registry_bind(registry, name, + &wl_subcompositor_interface, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + self->xdgWm = (xdg_wm_base*)wl_registry_bind(registry, name, + &xdg_wm_base_interface, 1); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + self->decorationManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, + &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { + self->xdgActivation = (xdg_activation_v1*)wl_registry_bind(registry, name, + &xdg_activation_v1_interface, 1); + } +} +void WindowManagerWayland::registryHandleGlobalRemove(void* data, wl_registry *registry, uint32_t name) { + auto self = reinterpret_cast(data); + for (std::list::iterator it = self->outputs.begin(); it != self->outputs.end();) { + if ((*it)->_name == name) { + self->outputs.erase(it); + break; + } + ++it; + } +} +WindowWayland* WindowManagerWayland::getWindowForNative(wl_surface* surface) { + if (!surface) return nullptr; + // the tag makes it safe. Should:TM: be faster than searching a list every time + if (!WindowWayland::ownSurface(surface)) + return nullptr; + return reinterpret_cast(wl_surface_get_user_data(surface)); +} +void WindowManagerWayland::seatCapabilities(void* data, wl_seat* seat, uint32_t capabilities) { + auto self = reinterpret_cast(data); + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && + !self->_pointer) { + self->_pointer.reset(new Pointer(seat, wl_seat_get_pointer(seat), self)); + } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && self->_pointer) { + self->_pointer.reset(); + } + + if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && + !self->_keyboard) { + self->_keyboard.reset(new Keyboard(wl_seat_get_keyboard(seat), self)); + } else if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && + self->_keyboard) { + self->_keyboard.reset(); + } +} +void WindowManagerWayland::seatName(void* data, wl_seat* seat, const char* name) { + // who cares +} +static void offerOffer(void* data, wl_data_offer* offer, const char* mimeType) { + auto self = reinterpret_cast(data); + self->_currentMimeTypes.push_back(std::string(mimeType)); +} +wl_data_offer_listener WindowManagerWayland::_offerListener = { + .offer = offerOffer +}; +static void deviceDataOffer(void* data, wl_data_device* device, wl_data_offer* offer) { + auto self = reinterpret_cast(data); + self->_currentMimeTypes = {}; + wl_data_offer_add_listener(offer, &WindowManagerWayland::_offerListener, data); +} +static void deviceSelection(void* data, wl_data_device* device, wl_data_offer* offer) { + auto self = reinterpret_cast(data); + // if null then w/e + self->currentOffer = offer; +} +wl_data_device_listener WindowManagerWayland::_deviceListener = { + .data_offer = deviceDataOffer, + .selection = deviceSelection + +}; +std::vector WindowManagerWayland::getClipboardFormats() { + return { _currentMimeTypes.begin(), _currentMimeTypes.end()}; +} +jwm::ByteBuf WindowManagerWayland::getClipboardContents(const std::string& type) { + auto it = _myClipboardContents.find(type); + if (it != _myClipboardContents.end()) { + return it->second; + } else if (currentSource) { + // Self paste + auto it2 = _myClipboardSource.find(type); + if (it2 != _myClipboardSource.end()) { + _myClipboardContents[type] = it2->second; + return it2->second; + } + } else if (currentOffer) { + auto it2 = std::find(_currentMimeTypes.begin(), _currentMimeTypes.end(), type); + if (it2 != _currentMimeTypes.end()) { + auto mimeType = *it2; + // pull down offer + int fds[2]; + pipe(fds); + wl_data_offer_receive(currentOffer, mimeType.c_str(), fds[1]); + wl_display_flush(display); + close(fds[1]); + ByteBuf res; + + while (true) { + char buf[1024]; + ssize_t n = read(fds[0], buf, sizeof(buf)); + if (n <= 0) + break; + res.insert(res.end(), buf, buf + n); + } + // cache + _myClipboardContents[mimeType] = res; + close(fds[0]); + return res; + } + } + return {}; +} + +void WindowManagerWayland::registerWindow(WindowWayland* window) { + _windows.push_back(window); +} + +void WindowManagerWayland::unregisterWindow(WindowWayland* window) { + auto it = std::find(_windows.begin(), _windows.end(), window); + if (it != _windows.end()) { + _windows.erase(it); + } +} + +void WindowManagerWayland::terminate() { + _runLoop = false; +} + +static void dataSourceSend(void* data, wl_data_source* source, const char* mimeType, int fd) { + auto self = reinterpret_cast(data); + auto it = self->_myClipboardSource.find(std::string(mimeType)); + if (it != self->_myClipboardSource.end()) { + write(fd, it->second.data(), it->second.size()); + } + close(fd); +} +static void dataSourceCancelled(void* data, wl_data_source* source) { + auto self = reinterpret_cast(data); + wl_data_source_destroy(source); + self->currentSource = nullptr; + self->_myClipboardSource = {}; +} +wl_data_source_listener WindowManagerWayland::_sourceListener = { + .send = dataSourceSend, + .cancelled = dataSourceCancelled +}; +void WindowManagerWayland::setClipboardContents(std::map&& c) { + _myClipboardSource = c; + + if (!deviceManager) return; + + currentSource = wl_data_device_manager_create_data_source(deviceManager); + + wl_data_source_add_listener(currentSource, &_sourceListener, this); + + _currentMimeTypes = {}; + for (auto it : c) { + _currentMimeTypes.push_back(it.first.c_str()); + wl_data_source_offer(currentSource, it.first.c_str()); + } + + if (getKeyboardSerial() > 0) + wl_data_device_set_selection(dataDevice, currentSource, getKeyboardSerial()); + +} + +void WindowManagerWayland::enqueueTask(const std::function& task) { + std::unique_lock lock(_taskQueueLock); + _taskQueue.push(task); + _taskQueueNotify.notify_one(); + notifyLoop(); +} + +void WindowManagerWayland::notifyLoop() { + // maybe just do nothing? + if (notifyFD==-1) return; + // fast notifyBool path to not make system calls when not necessary + if (!notifyBool.exchange(true)) { + char dummy[1] = {0}; + int unused = write(notifyFD, dummy, 1); // this really shouldn't fail, but if it does, the pipe should either be full (good), or dead (bad, but not our business) + } +} diff --git a/wayland/cc/WindowManagerWayland.hh b/wayland/cc/WindowManagerWayland.hh new file mode 100644 index 00000000..d9c2bb08 --- /dev/null +++ b/wayland/cc/WindowManagerWayland.hh @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include +#include +#include "Types.hh" +#include +#include +#include +#include +#include "MouseCursor.hh" +#include +#include +#include +#include "Output.hh" +#include +#include +#include +#include +#include "Keyboard.hh" +#include "Pointer.hh" +#include +#include +#include +#include +#include +#include +#include + +namespace jwm { + class WindowWayland; + class WindowManagerWayland { + public: + WindowManagerWayland(); + + void runLoop(); + void terminate(); + + void registerWindow(WindowWayland* window); + void unregisterWindow(WindowWayland* window); + + void _processCallbacks(); + void _processKeyboard(); + void notifyLoop(); + void enqueueTask(const std::function& task); + + WindowWayland* getWindowForNative(wl_surface* surface); + + static wl_registry_listener _registryListener; + + static void registryHandleGlobal(void* data, wl_registry *registry, + uint32_t name, const char* interface, uint32_t version); + static void registryHandleGlobalRemove(void* data, wl_registry *registry, + uint32_t name); + + static wl_seat_listener _seatListener; + + static void seatCapabilities(void* data, wl_seat* seat, uint32_t capabilities); + static void seatName(void* data, wl_seat* seat, const char* name); + + static wl_data_source_listener _sourceListener; + static wl_data_offer_listener _offerListener; + static wl_data_device_listener _deviceListener; + + ByteBuf getClipboardContents(const std::string& type); + std::vector getClipboardFormats(); + + wl_display* display = nullptr; + wl_registry* registry = nullptr; + wl_shm* shm = nullptr; + wl_compositor* compositor = nullptr; + wl_data_device_manager* deviceManager = nullptr; + // no multiseat? + wl_seat* seat = nullptr; + zwp_pointer_constraints_v1* pointerConstraints = nullptr; + zwp_relative_pointer_manager_v1* relativePointerManager = nullptr; + xdg_wm_base* xdgWm = nullptr; + wp_viewporter* viewporter = nullptr; + zxdg_decoration_manager_v1* decorationManager = nullptr; + wl_subcompositor* subcompositor = nullptr; + xdg_activation_v1* xdgActivation = nullptr; + std::unique_ptr _pointer = nullptr; + Pointer* getPointer() const { + return _pointer.get(); + } + std::unique_ptr _keyboard = nullptr; + wl_data_device* dataDevice = nullptr; + wl_data_source* currentSource = nullptr; + wl_data_offer* currentOffer = nullptr; + uint32_t getMouseSerial() const { + if (_pointer) + return _pointer->getSerial(); + return 0; + } + uint32_t getKeyboardSerial() const { + if (_keyboard) + return _keyboard->getSerial(); + return 0; + } + xkb_state* getXkbState() const { + if (_keyboard) + return _keyboard->getState(); + return nullptr; + } + + + EGLDisplay _eglDisplay = EGL_NO_DISPLAY; + std::list outputs; + + bool _runLoop; + int notifyFD = -1; + std::atomic_bool notifyBool{false}; + + std::list _windows; + std::map _myClipboardContents; + std::map _myClipboardSource; + std::list _currentMimeTypes; + const char* vendor = nullptr; + + wl_surface* getCursorSurface() const { + if (_pointer) + return _pointer->getSurface(); + return nullptr; + } + WindowWayland* getFocusedSurface() const { + if (_pointer) + return _pointer->getFocusedSurface(); + return nullptr; + } + // Is holding all cursors in memory a good idea? + wl_cursor_image* _cursors[static_cast(jwm::MouseCursor::COUNT)]; + + + std::mutex _taskQueueLock; + std::condition_variable _taskQueueNotify; + std::queue> _taskQueue; + + + void setClipboardContents(std::map&& c); + }; +} diff --git a/wayland/cc/WindowWayland.cc b/wayland/cc/WindowWayland.cc new file mode 100644 index 00000000..a65eddbe --- /dev/null +++ b/wayland/cc/WindowWayland.cc @@ -0,0 +1,496 @@ +#include "WindowWayland.hh" +#include +#include +#include +#include "AppWayland.hh" +#include "impl/Library.hh" +#include "impl/JNILocal.hh" +#include +#include +#include +#include "Token.hh" + +using namespace jwm; + + +wl_surface_listener WindowWayland::_surfaceListener = { + .enter = WindowWayland::surfaceEnter, + .leave = WindowWayland::surfaceLeave, +#ifdef HAVE_WAYLAND_1_22 + .preferred_buffer_scale = WindowWayland::surfacePreferredBufferScale, + .preferred_buffer_transform = WindowWayland::surfacePreferredBufferTransform +#endif +}; + +WindowWayland::WindowWayland(JNIEnv* env, WindowManagerWayland& windowManager): + jwm::Window(env), + _windowManager(windowManager), + _title("") + +{ +} + +WindowWayland::~WindowWayland() { + // TODO: close gets called twice? + close(); +} + + +void WindowWayland::setTitle(const std::string& title) { + _title = title; + if (_decoration) + _decoration->setTitle(title); +} + +void WindowWayland::setTitlebarVisible(bool isVisible) { + _titlebarVisible = isVisible; + if (_decoration) + _decoration->setTitlebarVisible(isVisible); +} + +void WindowWayland::close() { + if (_closed) return; + _closed = true; + hide(); + // TODO: more destruction! +} +void WindowWayland::hide() { + _visible = false; + if (_layer) { + _layer->detachBuffer(); + } + if (_waylandWindow) { + wl_surface_destroy(_waylandWindow); + } + _waylandWindow = nullptr; + if (_decoration) { + _decoration->close(); + } + _decoration = nullptr; + _windowManager.unregisterWindow(this); +} +void WindowWayland::maximize() { + if (!_visible || !_decoration) return; + xdg_toplevel_set_maximized(_decoration->_xdgToplevel); +} + +void WindowWayland::minimize() { + if (!_visible || !_decoration) return; + xdg_toplevel_set_minimized(_decoration->_xdgToplevel); +} + +void WindowWayland::restore() { + // Not possible for minimize + if (!_visible || !_decoration) return; + xdg_toplevel_unset_maximized(_decoration->_xdgToplevel); +} + +void WindowWayland::setFullScreen(bool isFullScreen) { + if (!_visible || !_decoration) return; + if (_decoration->_fullscreen == isFullScreen) return; + if (isFullScreen) + xdg_toplevel_set_fullscreen(_decoration->_xdgToplevel, nullptr); + else + xdg_toplevel_unset_fullscreen(_decoration->_xdgToplevel); +} + +bool WindowWayland::isFullScreen() { + if (_decoration) + return _decoration->_fullscreen; + return false; +} + +void WindowWayland::setLayer(ILayerWayland* layer) { + _layer = layer; + +} +void WindowWayland::getDecorations(int& left, int& top, int& right, int& bottom) { + // impl me : ) + if (_decoration) { + _decoration->getBorders(left, top, right, bottom); + } else { + left = 0; + right = 0; + top = 0; + bottom = 0; + } + +} + +void WindowWayland::getContentPosition(int& posX, int& posY) { + posX = 0; + posY = 0; + +} + +bool WindowWayland::resize(int width, int height) { + if (width < 0 || height < 0) + return false; + // Width and height are in absolute pixel units, and wayland will + // complain if you try to set a width/height that isn't a multiple of scale. + if ((width % _scale) != 0 || (height % _scale) != 0) + return false; + + // TODO: adapting size while visible causes issues? + // I've noticed the frame not updating + if (_visible) + return false; + _width = width / _scale; + _height = height / _scale; + return true; +} + +int WindowWayland::getLeft() { + int x, y; + getContentPosition(x, y); + return x; +} + +int WindowWayland::getTop() { + int x, y; + getContentPosition(x, y); + return y; +} + +int WindowWayland::getWidth() { + return getUnscaledWidth() * _scale; +} +int WindowWayland::getUnscaledWidth() { + return _width <= 0 ? _floatingWidth : _width; +} +int WindowWayland::getHeight() { + return getUnscaledHeight() * _scale; +} +int WindowWayland::getUnscaledHeight() { + return _height <= 0 ? _floatingHeight : _height; +} + +float WindowWayland::getScale() { + return getIntScale(); +} +int WindowWayland::getIntScale() { + return _scale; +} +wl_cursor* WindowWayland::_getCursorFor(jwm::MouseCursor cursor) { + if (auto ptr = _windowManager.getPointer()) + return ptr->getCursorFor(_scale, cursor); + return nullptr; +} +bool WindowWayland::init() { + return true; +} +bool WindowWayland::isConfigured() { + if (_decoration) + return _decoration->_configured; + return false; +} +void WindowWayland::show() +{ + _waylandWindow = wl_compositor_create_surface(_windowManager.compositor); + wl_surface_add_listener(_waylandWindow, &_surfaceListener, this); + wl_proxy_set_tag((wl_proxy*) _waylandWindow, &AppWayland::proxyTag); + _windowManager.registerWindow(this); + wl_display_roundtrip(_windowManager.display); + _decoration = new Decoration(*this); + setTitle(_title); + setTitlebarVisible(_titlebarVisible); + // map + wl_surface_commit(_waylandWindow); + _visible = true; +} + +ScreenInfo WindowWayland::getScreen() { + if (!_outputs.empty()) { + return _outputs.front()->getScreenInfo(); + } else { + return { + .id = -1, + .bounds = jwm::IRect::makeXYWH(0, 0, getWidth(), getHeight()), + .isPrimary = false, + .scale = _scale + }; + } +} +void WindowWayland::setVisible(bool isVisible) { + if (_visible != isVisible) { + _visible = isVisible; + if (_visible) { + show(); + } else { + hide(); + } + } +} + +void jwm::WindowWayland::setCursorMaybe(jwm::MouseCursor cursor, bool force) { + if (!_windowManager.getPointer()) return; + _windowManager.getPointer()->setCursor(_scale, cursor, force); +} +void jwm::WindowWayland::setCursor(jwm::MouseCursor cursor) { + setCursorMaybe(cursor, false); +} + +// what do??? +void jwm::WindowWayland::surfaceEnter(void* data, wl_surface* surface, wl_output* output) { + // doesn't crash : ) + WindowWayland* self = reinterpret_cast(data); + + if (auto out = Output::getForNative(output)) { + self->_outputs.push_back(out); + int scale = 1; + for (auto i : self->_outputs) { + if (i->scale > scale) + scale = i->scale; + } + self->_scale = scale; + self->_adaptSize(self->getUnscaledWidth(), self->getUnscaledHeight()); + } +} +bool jwm::WindowWayland::isNativeSelf(wl_surface* surface) { + if (!_waylandWindow) return false; + return surface == _waylandWindow; +} +bool jwm::WindowWayland::ownSurface(wl_surface* surface) { + return AppWayland::ownProxy((wl_proxy*) surface); +} +void jwm::WindowWayland::surfaceLeave(void* data, wl_surface* surface, wl_output* output) { + auto self = reinterpret_cast(data); + + if (auto out = Output::getForNative(output)) { + auto it = std::find(self->_outputs.begin(), self->_outputs.end(), out); + + if (it != self->_outputs.end()) + self->_outputs.erase(it); + + int scale = 1; + for (auto i : self->_outputs) { + if (i->scale > scale) + scale = i->scale; + } + self->_scale = scale; + self->_adaptSize(self->getUnscaledWidth(), self->getUnscaledHeight()); + } +} +void jwm::WindowWayland::surfacePreferredBufferScale(void* data, wl_surface* surface, int factor) { + WindowWayland* self = (WindowWayland*) data; + if (factor < 1) { + return; + } + self->_scale = factor; + // do I pinky promise here? + // yes : ) + self->_adaptSize(self->getUnscaledWidth(), self->getUnscaledHeight()); +} +void jwm::WindowWayland::surfacePreferredBufferTransform(void* data, wl_surface* surface, uint32_t transform) {} + +static void frameCallbackDone(void* data, wl_callback* cb, uint32_t cb_data) { + auto self = reinterpret_cast(data); + self->_adaptSize(self->_newWidth, self->_newHeight); +} +wl_callback_listener jwm::WindowWayland::_frameCallback = { + .done = frameCallbackDone +}; +void jwm::WindowWayland::_adaptSize(int newWidth, int newHeight) { + using namespace classes; + if (!isConfigured()) { + return; + } + if (newWidth == _width && newHeight == _height && _scale == _oldScale) return; + _width = newWidth; + _height = newHeight; + int scaledWidth = _width * _scale; + int scaledHeight = _height * _scale; + jwm::JNILocal eventWindowResize( + app.getJniEnv(), + EventWindowResize::make( + app.getJniEnv(), + scaledWidth, + scaledHeight, + scaledWidth, + scaledHeight + ) + ); + dispatch(eventWindowResize.get()); + // In Java Wayland doesn't actually cause a frame: + // however decorFrameCommit will cause a redraw anyway. + // Not doing it in wayland lets me not cause an exception on hide. +} + +void jwm::WindowWayland::lockCursor(bool locked) { + auto pointer = _windowManager.getPointer(); + if (!pointer) return; + if (pointer->getFocusedSurface() == this) { + if (locked) + pointer->lock(); + else + pointer->unlock(); + } +} +void jwm::WindowWayland::hideCursor(bool hidden) { + auto pointer = _windowManager.getPointer(); + if (!pointer) return; + if (pointer->getFocusedSurface() == this) { + if (hidden) + pointer->hide(); + else + pointer->unhide(); + } +} + +void jwm::WindowWayland::focus() { + if (!_waylandWindow) return; + auto token = Token::make(_windowManager, _waylandWindow); + token.grab(_windowManager, _waylandWindow); +} +// JNI + +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nMake + (JNIEnv* env, jclass jclass) { + std::unique_ptr instance = std::make_unique(env, jwm::app.getWindowManager()); + if (instance->init()) { + return reinterpret_cast(instance.release()); + } + return 0; +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetVisible + (JNIEnv* env, jobject obj, jboolean isVisible) { + + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->setVisible(isVisible); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetContentSize + (JNIEnv* env, jobject obj, jint width, jint height) { + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->resize(width, height); +} +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nGetScreen + (JNIEnv* env, jobject obj) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + return instance->getScreen().asJavaObject(env); +} + +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nGetWindowRect + (JNIEnv* env, jobject obj) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + int left, top, right, bottom; + instance->getDecorations(left, top, right, bottom); + int x, y; + instance->getContentPosition(x, y); + return jwm::classes::IRect::toJavaXYWH( + env, + x-left, + y-top, + instance->getWidth()+left+right, + instance->getHeight()+top+bottom + ); +} + +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nGetContentRect + (JNIEnv* env, jobject obj) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + int left, top, right, bottom; + instance->getDecorations(left, top, right, bottom); + return jwm::classes::IRect::toJavaXYWH( + env, + left, + top, + instance->getWidth(), + instance->getHeight() + ); +} + + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nRequestFrame + (JNIEnv* env, jobject obj) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->requestRedraw(); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nMaximize + (JNIEnv* env, jobject obj) { + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->maximize(); +} + + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nMinimize + (JNIEnv* env, jobject obj) { + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->minimize(); +} + + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nRestore + (JNIEnv* env, jobject obj) { + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->restore(); +} + + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nClose + (JNIEnv* env, jobject obj) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->close(); +} +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetTitle + (JNIEnv* env, jobject obj, jstring title) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + + const jchar* bytes = env->GetStringChars(title, nullptr); + jsize length = env->GetStringLength(title); + std::u16string thingie = {reinterpret_cast(bytes), static_cast(length)}; + + std::string titleS = std::wstring_convert< + std::codecvt_utf8_utf16, char16_t>{}.to_bytes(thingie); + env->ReleaseStringChars(title, bytes); + instance->setTitle(titleS); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetTitlebarVisible + (JNIEnv* env, jobject obj, jboolean isVisible) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->setTitlebarVisible(isVisible); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetMouseCursor + (JNIEnv* env, jobject obj, jint idx) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + + instance->setCursor(static_cast(idx)); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetFullScreen + (JNIEnv* env, jobject obj, jboolean isFullScreen) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->setFullScreen(isFullScreen); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nIsFullScreen + (JNIEnv* env, jobject obj) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + return instance->isFullScreen(); +} + +extern "C" JNIEXPORT jfloat JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nGetScale + (JNIEnv* env, jobject obj) { + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + return instance->getScale(); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nLockMouseCursor + (JNIEnv* env, jobject obj, jboolean locked) +{ + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->lockCursor(locked); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nHideMouseCursor + (JNIEnv* env, jobject obj, jboolean hidden) +{ + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->hideCursor(hidden); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nFocus + (JNIEnv* env, jobject obj) +{ + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->focus(); +} diff --git a/wayland/cc/WindowWayland.hh b/wayland/cc/WindowWayland.hh new file mode 100644 index 00000000..6a5e0a5e --- /dev/null +++ b/wayland/cc/WindowWayland.hh @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include "Window.hh" +#include "WindowManagerWayland.hh" +#include "ILayerWayland.hh" +#include "ScreenInfo.hh" +#include +#include "Decoration.hh" + +namespace jwm { + class WindowWayland: public jwm::Window { + public: + WindowWayland(JNIEnv* env, WindowManagerWayland& windowManager); + WindowWayland() = delete; + ~WindowWayland() override; + + void getDecorations(int& left, int& top, int& right, int& bottom); + void getContentPosition(int& posX, int& posY); + void setVisible(bool isVisible); + bool resize(int width, int height); + void close(); + void hide(); + bool init(); + void show(); + int getLeft(); + int getTop(); + int getWidth(); + int getUnscaledWidth(); + int getHeight(); + int getUnscaledHeight(); + float getScale(); + int getIntScale(); + void requestRedraw() { + _isRedrawRequested = true; + _windowManager.notifyLoop(); + } + void unsetRedrawRequest() { + _isRedrawRequested = false; + } + bool isRedrawRequested() { + return _isRedrawRequested; + } + void setTitle(const std::string& title); + void setTitlebarVisible(bool isVisible); + + void maximize(); + void minimize(); + void restore(); + + void setFullScreen(bool isFullScreen); + bool isFullScreen(); + + void setCursorMaybe(jwm::MouseCursor cursor, bool force); + void setCursor(jwm::MouseCursor cursor); + void setLayer(ILayerWayland* layer); + void focus(); + + wl_cursor* _getCursorFor(jwm::MouseCursor cursor); + ScreenInfo getScreen(); + + static void surfaceEnter(void* data, wl_surface* surface, wl_output* output); + static void surfaceLeave(void* data, wl_surface* surface, wl_output* output); + static void surfacePreferredBufferScale(void* data, wl_surface* surface, int factor); + static void surfacePreferredBufferTransform(void* data, wl_surface* surface, uint32_t transform); + + void _adaptSize(int newWidth, int newHeight); + + bool isNativeSelf(wl_surface* surface); + static bool ownSurface(wl_surface* surface); + + void lockCursor(bool locked); + + void hideCursor(bool hidden); + + bool isConfigured(); + + int _posX = -1; + int _posY = -1; + int _width = -1; + int _newWidth = -1; + int _scale = 1; + int _oldScale = 1; + int _height = -1; + int _newHeight = -1; + int _floatingWidth = 400; + int _floatingHeight = 400; + bool _canMinimize = false; + bool _canMaximize = false; + bool _canFullscreen = false; + bool _visible = false; + bool _isRedrawRequested = false; + std::string _title; + bool _titlebarVisible = true; + + bool _closed = false; + + bool isClosed() const { + return _closed; + } + + WindowManagerWayland& _windowManager; + ILayerWayland* _layer = nullptr; + wl_surface* _waylandWindow = nullptr; + Decoration* _decoration = nullptr; + std::list _outputs; + wl_cursor_theme* theme = nullptr; + + static wl_surface_listener _surfaceListener; + + static wl_callback_listener _frameCallback; + + }; +} diff --git a/wayland/java/WindowWayland.java b/wayland/java/WindowWayland.java new file mode 100644 index 00000000..59d94310 --- /dev/null +++ b/wayland/java/WindowWayland.java @@ -0,0 +1,246 @@ +package io.github.humbleui.jwm; + +import java.io.*; +import java.util.concurrent.*; +import java.util.function.*; +import lombok.*; +import org.jetbrains.annotations.*; +import io.github.humbleui.jwm.*; +import io.github.humbleui.jwm.impl.*; +import io.github.humbleui.types.*; + +public class WindowWayland extends Window { + @ApiStatus.Internal + public WindowWayland() { + super(_nMake()); + } + + @Override + public Window setTextInputEnabled(boolean enabled) { + assert _onUIThread() : "Should be run on UI thread"; + // TODO: impl me + return this; + } + + @Override + public void unmarkText() { + assert _onUIThread() : "Should be run on UI thread"; + // TODO: impl me! + } + + @Override + public IRect getWindowRect() { + assert _onUIThread() : "Should be run on UI thread"; + return _nGetWindowRect(); + } + + @Override + public IRect getContentRect() { + assert _onUIThread() : "Should be run on UI thread"; + return _nGetContentRect(); + } + + @Override + public Window setWindowPosition(int left, int top) { + assert _onUIThread() : "Should be run on UI thread"; + // no : ) + return this; + } + + @Override + public Window setWindowSize(int width, int height) { + assert _onUIThread() : "Should be run on UI thread"; + // TODO: don't assume bounds + setContentSize(width, height); + return this; + } + + @Override + public Window setContentSize(int width, int height) { + assert _onUIThread() : "Should be run on UI thread"; + _nSetContentSize(width, height); + return this; + } + + @Override + public Window setTitle(String title) { + assert _onUIThread() : "Should be run on UI thread"; + _nSetTitle(title); + return this; + } + + @Override + public Window setIcon(File icon) { + // TODO #95 + return this; + } + + @Override + public Window setTitlebarVisible(boolean value) { + assert _onUIThread() : "Should be run on UI thread"; + _nSetTitlebarVisible(value); + return this; + } + + @Override + public Window setVisible(boolean isVisible) { + assert _onUIThread() : "Should be run on UI thread"; + _nSetVisible(isVisible); + // this calls a screen change, which will cause a crash because GL context isn't ready yet + // return super.setVisible(true); + return this; + } + + @Override + public Window hideMouseCursorUntilMoved(boolean value) { + assert _onUIThread() : "Should be run on UI thread"; + _nHideMouseCursor(value); + return this; + } + + @Override + public Window lockMouseCursor(boolean value) { + assert _onUIThread() : "Should be run on UI thread"; + _nLockMouseCursor(value); + return this; + } + + @Override + public Window setOpacity(float opacity) { + // TODO: impl me! + return this; + } + + @Override + public float getOpacity(){ + throw new UnsupportedOperationException("impl me!"); + } + + @Override + public Screen getScreen() { + assert _onUIThread() : "Should be run on UI thread"; + return _nGetScreen(); + } + + @Override + public void requestFrame() { + if (!isClosed()) { + App.runOnUIThread(() -> { + if (!isClosed()) { + _nRequestFrame(); + } + }); + } + } + + @Override + public void close() { + assert _onUIThread() && !isClosed(); + _nClose(); + super.close(); + } + + @Override + public Window maximize() { + _nMaximize(); + return this; + } + + @Override + public Window minimize() { + _nMinimize(); + return this; + } + + @Override + public Window focus() { + assert _onUIThread() : "Should be run on UI thread"; + _nFocus(); + return this; + } + + @Override + public Window bringToFront() { + assert _onUIThread() : "Should be run on UI thread"; + // TODO implement + return this; + } + + @Override + public boolean isFront() { + assert _onUIThread() : "Should be run on UI thread"; + // TODO: impl me + return false; + } + + @Override + public ZOrder getZOrder() { + assert _onUIThread() : "Should be run on UI thread"; + return ZOrder.NORMAL; + } + + @Override + public Window setZOrder(ZOrder order) { + assert _onUIThread() : "Should be run on UI thread"; + // TODO implement + return this; + } + + @Override + public float getProgressBar() { + throw new UnsupportedOperationException("impl me!"); + } + + @Override + public Window setProgressBar(float progress) { + throw new UnsupportedOperationException("impl me!"); + } + + @Override + public Window restore() { + _nRestore(); + return this; + } + + @Override + public Window setFullScreen(boolean value) { + assert _onUIThread() : "Should be run on UI thread"; + _nSetFullScreen(value); + return this; + } + + @Override + public boolean isFullScreen() { + assert _onUIThread() : "Should be run on UI thread"; + return _nIsFullScreen(); + } + + @Override + public float getScale() { + assert _onUIThread() : "Should be run on UI Thread"; + return _nGetScale(); + } + + @ApiStatus.Internal public static native long _nMake(); + @ApiStatus.Internal public native void _nSetVisible(boolean isVisible); + @ApiStatus.Internal public native IRect _nGetWindowRect(); + @ApiStatus.Internal public native IRect _nGetContentRect(); + // @ApiStatus.Internal public native void _nSetWindowPosition(int left, int top); + // @ApiStatus.Internal public native void _nSetWindowSize(int width, int height); + @ApiStatus.Internal public native void _nSetMouseCursor(int cursorId); + @ApiStatus.Internal public native void _nSetContentSize(int width, int height); + @ApiStatus.Internal public native Screen _nGetScreen(); + @ApiStatus.Internal public native void _nRequestFrame(); + @ApiStatus.Internal public native void _nClose(); + @ApiStatus.Internal public native void _nMaximize(); + @ApiStatus.Internal public native void _nMinimize(); + @ApiStatus.Internal public native void _nRestore(); + @ApiStatus.Internal public native Screen _nSetTitle(String title); + @ApiStatus.Internal public native void _nSetTitlebarVisible(boolean isVisible); + @ApiStatus.Internal public native void _nSetFullScreen(boolean isFullScreen); + @ApiStatus.Internal public native boolean _nIsFullScreen(); + @ApiStatus.Internal public native float _nGetScale(); + @ApiStatus.Internal public native void _nLockMouseCursor(boolean locked); + @ApiStatus.Internal public native void _nHideMouseCursor(boolean hidden); + @ApiStatus.Internal public native void _nFocus(); +} diff --git a/windows/java/WindowWin32.java b/windows/java/WindowWin32.java index 1379268e..496d97e7 100644 --- a/windows/java/WindowWin32.java +++ b/windows/java/WindowWin32.java @@ -172,7 +172,10 @@ public Window bringToFront() { _nBringToFront(); return this; } - + + public float getScale() { + return this.getScreen().getScale(); + } @Override public boolean isFront() { assert _onUIThread() : "Should be run on UI thread"; diff --git a/linux/CMakeLists.txt b/x11/CMakeLists.txt similarity index 84% rename from linux/CMakeLists.txt rename to x11/CMakeLists.txt index ef67fc66..36bfff99 100644 --- a/linux/CMakeLists.txt +++ b/x11/CMakeLists.txt @@ -18,7 +18,9 @@ endif() find_package(X11 REQUIRED) find_package(OpenGL REQUIRED) -file(GLOB SOURCES_CXX ${CMAKE_CURRENT_LIST_DIR}/../shared/cc/*.cc ${CMAKE_CURRENT_LIST_DIR}/cc/*.cc) +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}) @@ -33,9 +35,9 @@ if (NOT JAVA_HOME) endif() endif() -target_include_directories(jwm PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../shared/cc ${JAVA_HOME}/include ${JAVA_HOME}/include/linux) +target_include_directories(jwm PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../shared/cc ${CMAKE_CURRENT_LIST_DIR}/../linux/cc ${JAVA_HOME}/include ${JAVA_HOME}/include/linux) set_target_properties(jwm PROPERTIES OUTPUT_NAME "jwm_${JWM_ARCH}") target_link_libraries(jwm PRIVATE X11::X11 X11::Xrandr X11::Xcursor X11::Xi) -target_link_libraries(jwm PRIVATE OpenGL::GL) \ No newline at end of file +target_link_libraries(jwm PRIVATE OpenGL::GL) diff --git a/linux/cc/AppX11.cc b/x11/cc/AppX11.cc similarity index 100% rename from linux/cc/AppX11.cc rename to x11/cc/AppX11.cc diff --git a/linux/cc/AppX11.hh b/x11/cc/AppX11.hh similarity index 100% rename from linux/cc/AppX11.hh rename to x11/cc/AppX11.hh diff --git a/linux/cc/ClipboardX11.cc b/x11/cc/ClipboardX11.cc similarity index 100% rename from linux/cc/ClipboardX11.cc rename to x11/cc/ClipboardX11.cc diff --git a/linux/cc/KeyX11.cc b/x11/cc/KeyX11.cc similarity index 99% rename from linux/cc/KeyX11.cc rename to x11/cc/KeyX11.cc index fb573d29..0071718d 100644 --- a/linux/cc/KeyX11.cc +++ b/x11/cc/KeyX11.cc @@ -226,4 +226,4 @@ jwm::Key jwm::KeyX11::fromNative(uint32_t v, jwm::KeyLocation& location, int& mo case XK_Z: return Key::Z; default: return Key::UNDEFINED; } -} \ No newline at end of file +} diff --git a/linux/cc/KeyX11.hh b/x11/cc/KeyX11.hh similarity index 100% rename from linux/cc/KeyX11.hh rename to x11/cc/KeyX11.hh diff --git a/linux/cc/LayerGL.cc b/x11/cc/LayerGLX11.cc similarity index 99% rename from linux/cc/LayerGL.cc rename to x11/cc/LayerGLX11.cc index 4beb64eb..aa78e4ee 100644 --- a/linux/cc/LayerGL.cc +++ b/x11/cc/LayerGLX11.cc @@ -120,4 +120,4 @@ 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(); -} \ No newline at end of file +} diff --git a/linux/cc/LayerRaster.cc b/x11/cc/LayerRasterX11.cc similarity index 99% rename from linux/cc/LayerRaster.cc rename to x11/cc/LayerRasterX11.cc index 2ec70c85..4928aee6 100644 --- a/linux/cc/LayerRaster.cc +++ b/x11/cc/LayerRasterX11.cc @@ -120,4 +120,4 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nGe (JNIEnv* env, jobject obj) { jwm::LayerRaster* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); return static_cast(instance->getRowBytes()); -} \ No newline at end of file +} diff --git a/linux/cc/MouseButtonX11.cc b/x11/cc/MouseButtonX11.cc similarity index 100% rename from linux/cc/MouseButtonX11.cc rename to x11/cc/MouseButtonX11.cc diff --git a/linux/cc/MouseButtonX11.hh b/x11/cc/MouseButtonX11.hh similarity index 100% rename from linux/cc/MouseButtonX11.hh rename to x11/cc/MouseButtonX11.hh diff --git a/linux/cc/ScreenInfo.cc b/x11/cc/ScreenInfo.cc similarity index 100% rename from linux/cc/ScreenInfo.cc rename to x11/cc/ScreenInfo.cc diff --git a/linux/cc/ScreenInfo.hh b/x11/cc/ScreenInfo.hh similarity index 100% rename from linux/cc/ScreenInfo.hh rename to x11/cc/ScreenInfo.hh diff --git a/linux/cc/WindowManagerX11.cc b/x11/cc/WindowManagerX11.cc similarity index 100% rename from linux/cc/WindowManagerX11.cc rename to x11/cc/WindowManagerX11.cc diff --git a/linux/cc/WindowManagerX11.hh b/x11/cc/WindowManagerX11.hh similarity index 100% rename from linux/cc/WindowManagerX11.hh rename to x11/cc/WindowManagerX11.hh diff --git a/linux/cc/WindowX11.cc b/x11/cc/WindowX11.cc similarity index 99% rename from linux/cc/WindowX11.cc rename to x11/cc/WindowX11.cc index 0bd16de0..177d8e0e 100644 --- a/linux/cc/WindowX11.cc +++ b/x11/cc/WindowX11.cc @@ -342,9 +342,6 @@ int WindowX11::getHeight() { return _height; } -float WindowX11::getScale() { - return jwm::app.getScale(); -} bool WindowX11::init() { @@ -590,3 +587,8 @@ extern "C" JNIEXPORT jboolean JNICALL Java_io_github_humbleui_jwm_WindowX11__1nI jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); return instance->isFullScreen(); } + +extern "C" JNIEXPORT jfloat JNICALL Java_io_github_humbleui_jwm_WindowX11__1nGetScale + (JNIEnv* env, jobject obj) { + return jwm::app.getScale(); +} diff --git a/linux/cc/WindowX11.hh b/x11/cc/WindowX11.hh similarity index 98% rename from linux/cc/WindowX11.hh rename to x11/cc/WindowX11.hh index 55f5befe..e2fc7ea0 100644 --- a/linux/cc/WindowX11.hh +++ b/x11/cc/WindowX11.hh @@ -24,7 +24,7 @@ namespace jwm { int getTop(); int getWidth(); int getHeight(); - float getScale(); + void move(int left, int top); void resize(int width, int height); void requestRedraw() { diff --git a/linux/cc/WindowX11MotifHints.hh b/x11/cc/WindowX11MotifHints.hh similarity index 100% rename from linux/cc/WindowX11MotifHints.hh rename to x11/cc/WindowX11MotifHints.hh diff --git a/linux/java/WindowX11.java b/x11/java/WindowX11.java similarity index 96% rename from linux/java/WindowX11.java rename to x11/java/WindowX11.java index d60e0502..89d584c9 100644 --- a/linux/java/WindowX11.java +++ b/x11/java/WindowX11.java @@ -211,6 +211,12 @@ public boolean isFullScreen() { return _nIsFullScreen(); } + @Override + public float getScale() { + assert _onUIThread() : "Should be run on UI thread"; + return _nGetScale(); + } + @ApiStatus.Internal public static native long _nMake(); @ApiStatus.Internal public native void _nSetVisible(boolean isVisible); @ApiStatus.Internal public native IRect _nGetWindowRect(); @@ -229,4 +235,5 @@ public boolean isFullScreen() { @ApiStatus.Internal public native void _nSetTitlebarVisible(boolean isVisible); @ApiStatus.Internal public native void _nSetFullScreen(boolean isFullScreen); @ApiStatus.Internal public native boolean _nIsFullScreen(); + @ApiStatus.Internal public native float _nGetScale(); }