From d7a2a2292cd22d496a35b46fababacca6b0daae2 Mon Sep 17 00:00:00 2001 From: Jacob Chvatal Date: Sat, 31 Dec 2022 21:12:52 -0500 Subject: [PATCH 1/7] add jwm flake --- .envrc | 5 +++++ flake.lock | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ shell.nix | 12 +++++++++++ 4 files changed, 138 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..24e67d2e --- /dev/null +++ b/.envrc @@ -0,0 +1,5 @@ +# If we are a computer with nix-shell available, then use that to setup +# the build environment with exactly what we need. +if has nix; then + use nix +fi diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..8b9cb1e4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,60 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1668681692, + "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "009399224d5e398d03b22badca40a37ac85412a1", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1670980281, + "narHash": "sha256-g0t/SmQca/JBEd+3Ry1qFgDfDK8ME9AM6EP4YUl8/lo=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "5cb48ea3c19ce2e5746a44d6b91847396bd28c1f", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "release-22.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..057e6857 --- /dev/null +++ b/flake.nix @@ -0,0 +1,61 @@ +let + name = "JWM"; + description = "Cross-platform window management and OS integration library for Java"; +in +{ + inherit name description; + + inputs = { + nixpkgs.url = github:nixos/nixpkgs/release-22.05; + flake-utils.url = github:numtide/flake-utils; + + # Used for shell.nix + flake-compat = { + url = github:edolstra/flake-compat; + flake = false; + }; + }; + + outputs = {self, nixpkgs, flake-utils, ...} @ inputs: + flake-utils.lib.eachDefaultSystem ( + system: let + pkgs = import nixpkgs {inherit system;}; + xDependencies = with pkgs; with xorg; [ + xorgserver + + # development lib + libX11 + + # xorg input modules + xf86inputevdev + xf86inputsynaptics + xf86inputlibinput + + # dyn libs + libXrandr + libXcursor + libXi + ]; + in rec { + devShells.default = pkgs.mkShell { + inherit name description; + nativeBuildInputs = with pkgs; [ + jdk17 + ] ++ xDependencies; + + buildInputs = with pkgs; [ + python3 + libGL + ninja + cmake + gcc + ]; + + LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (with pkgs; [ libGL ] ++ xDependencies)}:$LD_LIBRARY_PATH"; + }; + + # For compatibility with older versions of the `nix` binary + devShell = self.devShells.${system}.default; + } + ); +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..a15057aa --- /dev/null +++ b/shell.nix @@ -0,0 +1,12 @@ +(import + ( + let + flake-compat = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.flake-compat; + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${flake-compat.locked.rev}.tar.gz"; + sha256 = flake-compat.locked.narHash; + } + ) + {src = ./.;}) +.shellNix From f331d78c3248a7bf5610f02f48570502de0ec353 Mon Sep 17 00:00:00 2001 From: Jacob Chvatal Date: Sat, 31 Dec 2022 21:13:48 -0500 Subject: [PATCH 2/7] declare ninja as cmake program --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index 057e6857..a5aea34b 100644 --- a/flake.nix +++ b/flake.nix @@ -51,6 +51,7 @@ in gcc ]; + CMAKE_MAKE_PROGRAM = pkgs.ninja; LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (with pkgs; [ libGL ] ++ xDependencies)}:$LD_LIBRARY_PATH"; }; From 16ec5c25d9a85d02406a8e35a1d4f1443c413527 Mon Sep 17 00:00:00 2001 From: Jacob Chvatal Date: Sat, 31 Dec 2022 21:35:27 -0500 Subject: [PATCH 3/7] reordered to add wayland/ --- CMakeLists.txt | 5 +- script/build.py | 19 +- shared/java/App.java | 2 + shared/java/Platform.java | 14 +- wayland/CMakeLists.txt | 41 + {linux => wayland}/cc/AppX11.cc | 0 {linux => wayland}/cc/AppX11.hh | 0 {linux => wayland}/cc/ClipboardX11.cc | 0 {linux => wayland}/cc/ILayer.cc | 0 {linux => wayland}/cc/ILayer.hh | 0 {linux => wayland}/cc/KeyX11.cc | 0 {linux => wayland}/cc/KeyX11.hh | 0 {linux => wayland}/cc/LayerGL.cc | 0 {linux => wayland}/cc/LayerRaster.cc | 0 {linux => wayland}/cc/MouseButtonX11.cc | 0 {linux => wayland}/cc/MouseButtonX11.hh | 0 {linux => wayland}/cc/ScreenInfo.cc | 0 {linux => wayland}/cc/ScreenInfo.hh | 0 {linux => wayland}/cc/WindowManagerX11.cc | 0 {linux => wayland}/cc/WindowManagerX11.hh | 0 {linux => wayland}/cc/WindowX11.cc | 0 {linux => wayland}/cc/WindowX11.hh | 0 {linux => wayland}/cc/WindowX11MotifHints.hh | 0 wayland/java/WindowWayland.java | 218 +++++ {linux => x11}/CMakeLists.txt | 0 x11/cc/AppX11.cc | 126 +++ x11/cc/AppX11.hh | 35 + x11/cc/ClipboardX11.cc | 125 +++ x11/cc/ILayer.cc | 13 + x11/cc/ILayer.hh | 23 + x11/cc/KeyX11.cc | 168 ++++ x11/cc/KeyX11.hh | 13 + x11/cc/LayerGL.cc | 123 +++ x11/cc/LayerRaster.cc | 123 +++ x11/cc/MouseButtonX11.cc | 25 + x11/cc/MouseButtonX11.hh | 12 + x11/cc/ScreenInfo.cc | 7 + x11/cc/ScreenInfo.hh | 13 + x11/cc/WindowManagerX11.cc | 895 +++++++++++++++++++ x11/cc/WindowManagerX11.hh | 140 +++ x11/cc/WindowX11.cc | 587 ++++++++++++ x11/cc/WindowX11.hh | 86 ++ x11/cc/WindowX11MotifHints.hh | 12 + {linux => x11}/java/WindowX11.java | 0 44 files changed, 2816 insertions(+), 9 deletions(-) create mode 100644 wayland/CMakeLists.txt rename {linux => wayland}/cc/AppX11.cc (100%) rename {linux => wayland}/cc/AppX11.hh (100%) rename {linux => wayland}/cc/ClipboardX11.cc (100%) rename {linux => wayland}/cc/ILayer.cc (100%) rename {linux => wayland}/cc/ILayer.hh (100%) rename {linux => wayland}/cc/KeyX11.cc (100%) rename {linux => wayland}/cc/KeyX11.hh (100%) rename {linux => wayland}/cc/LayerGL.cc (100%) rename {linux => wayland}/cc/LayerRaster.cc (100%) rename {linux => wayland}/cc/MouseButtonX11.cc (100%) rename {linux => wayland}/cc/MouseButtonX11.hh (100%) rename {linux => wayland}/cc/ScreenInfo.cc (100%) rename {linux => wayland}/cc/ScreenInfo.hh (100%) rename {linux => wayland}/cc/WindowManagerX11.cc (100%) rename {linux => wayland}/cc/WindowManagerX11.hh (100%) rename {linux => wayland}/cc/WindowX11.cc (100%) rename {linux => wayland}/cc/WindowX11.hh (100%) rename {linux => wayland}/cc/WindowX11MotifHints.hh (100%) create mode 100644 wayland/java/WindowWayland.java rename {linux => x11}/CMakeLists.txt (100%) create mode 100644 x11/cc/AppX11.cc create mode 100644 x11/cc/AppX11.hh create mode 100644 x11/cc/ClipboardX11.cc create mode 100644 x11/cc/ILayer.cc create mode 100644 x11/cc/ILayer.hh create mode 100644 x11/cc/KeyX11.cc create mode 100644 x11/cc/KeyX11.hh create mode 100644 x11/cc/LayerGL.cc create mode 100644 x11/cc/LayerRaster.cc create mode 100644 x11/cc/MouseButtonX11.cc create mode 100644 x11/cc/MouseButtonX11.hh create mode 100644 x11/cc/ScreenInfo.cc create mode 100644 x11/cc/ScreenInfo.hh create mode 100644 x11/cc/WindowManagerX11.cc create mode 100644 x11/cc/WindowManagerX11.hh create mode 100644 x11/cc/WindowX11.cc create mode 100644 x11/cc/WindowX11.hh create mode 100644 x11/cc/WindowX11MotifHints.hh rename {linux => x11}/java/WindowX11.java (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b64230f..8f73e99a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ if(APPLE) endif() if (UNIX AND NOT APPLE) - add_subdirectory(linux) + add_subdirectory(x11) + add_subdirectory(wayland) message(STATUS "Configure linux platform module") -endif() \ No newline at end of file +endif() diff --git a/script/build.py b/script/build.py index 56e4cc3b..d4cbeda7 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", @@ -25,9 +25,22 @@ def build_native(): return 0 +def build_native(): + # TODO: There is likely a better folder hierarchy than this. + 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",) + sources = build_utils.files("x11/java/**/*.java", "wayland/java/**/*.java", "macos/java/**/*.java", "shared/java/**/*.java", "windows/java/**/*.java",) build_utils.javac(sources, "target/classes", classpath=common.deps_compile()) return 0 diff --git a/shared/java/App.java b/shared/java/App.java index 7e86f665..7b3a066d 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); diff --git a/shared/java/Platform.java b/shared/java/Platform.java index 737fcaa8..6d91bbb1 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 { @@ -12,9 +13,14 @@ public enum Platform { CURRENT = MACOS; else if (os.contains("windows")) CURRENT = WINDOWS; - else if (os.contains("nux") || os.contains("nix")) - CURRENT = X11; + else if (os.contains("nux") || os.contains("nix")) { + String sessionType = System.getenv("XDG_SESSION_TYPE"); + if (sessionType.contains("wayland")) + CURRENT = WAYLAND; + else if (sessionType.contains("x11")) + CURRENT = X11; + } else throw new RuntimeException("Unsupported platform: " + os); } -} \ No newline at end of file +} diff --git a/wayland/CMakeLists.txt b/wayland/CMakeLists.txt new file mode 100644 index 00000000..35706043 --- /dev/null +++ b/wayland/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.9) + +# prefer the newer GL library (GLVND) +cmake_policy(SET CMP0072 NEW) + +project(jwm LANGUAGES CXX) +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() + +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_IMPL ${CMAKE_CURRENT_LIST_DIR}/../shared/cc/impl/*.cc) +add_library(jwm SHARED ${SOURCES_OBJC} ${SOURCES_CXX} ${SOURCES_CXX_IMPL}) + +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 ${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) diff --git a/linux/cc/AppX11.cc b/wayland/cc/AppX11.cc similarity index 100% rename from linux/cc/AppX11.cc rename to wayland/cc/AppX11.cc diff --git a/linux/cc/AppX11.hh b/wayland/cc/AppX11.hh similarity index 100% rename from linux/cc/AppX11.hh rename to wayland/cc/AppX11.hh diff --git a/linux/cc/ClipboardX11.cc b/wayland/cc/ClipboardX11.cc similarity index 100% rename from linux/cc/ClipboardX11.cc rename to wayland/cc/ClipboardX11.cc diff --git a/linux/cc/ILayer.cc b/wayland/cc/ILayer.cc similarity index 100% rename from linux/cc/ILayer.cc rename to wayland/cc/ILayer.cc diff --git a/linux/cc/ILayer.hh b/wayland/cc/ILayer.hh similarity index 100% rename from linux/cc/ILayer.hh rename to wayland/cc/ILayer.hh diff --git a/linux/cc/KeyX11.cc b/wayland/cc/KeyX11.cc similarity index 100% rename from linux/cc/KeyX11.cc rename to wayland/cc/KeyX11.cc diff --git a/linux/cc/KeyX11.hh b/wayland/cc/KeyX11.hh similarity index 100% rename from linux/cc/KeyX11.hh rename to wayland/cc/KeyX11.hh diff --git a/linux/cc/LayerGL.cc b/wayland/cc/LayerGL.cc similarity index 100% rename from linux/cc/LayerGL.cc rename to wayland/cc/LayerGL.cc diff --git a/linux/cc/LayerRaster.cc b/wayland/cc/LayerRaster.cc similarity index 100% rename from linux/cc/LayerRaster.cc rename to wayland/cc/LayerRaster.cc diff --git a/linux/cc/MouseButtonX11.cc b/wayland/cc/MouseButtonX11.cc similarity index 100% rename from linux/cc/MouseButtonX11.cc rename to wayland/cc/MouseButtonX11.cc diff --git a/linux/cc/MouseButtonX11.hh b/wayland/cc/MouseButtonX11.hh similarity index 100% rename from linux/cc/MouseButtonX11.hh rename to wayland/cc/MouseButtonX11.hh diff --git a/linux/cc/ScreenInfo.cc b/wayland/cc/ScreenInfo.cc similarity index 100% rename from linux/cc/ScreenInfo.cc rename to wayland/cc/ScreenInfo.cc diff --git a/linux/cc/ScreenInfo.hh b/wayland/cc/ScreenInfo.hh similarity index 100% rename from linux/cc/ScreenInfo.hh rename to wayland/cc/ScreenInfo.hh diff --git a/linux/cc/WindowManagerX11.cc b/wayland/cc/WindowManagerX11.cc similarity index 100% rename from linux/cc/WindowManagerX11.cc rename to wayland/cc/WindowManagerX11.cc diff --git a/linux/cc/WindowManagerX11.hh b/wayland/cc/WindowManagerX11.hh similarity index 100% rename from linux/cc/WindowManagerX11.hh rename to wayland/cc/WindowManagerX11.hh diff --git a/linux/cc/WindowX11.cc b/wayland/cc/WindowX11.cc similarity index 100% rename from linux/cc/WindowX11.cc rename to wayland/cc/WindowX11.cc diff --git a/linux/cc/WindowX11.hh b/wayland/cc/WindowX11.hh similarity index 100% rename from linux/cc/WindowX11.hh rename to wayland/cc/WindowX11.hh diff --git a/linux/cc/WindowX11MotifHints.hh b/wayland/cc/WindowX11MotifHints.hh similarity index 100% rename from linux/cc/WindowX11MotifHints.hh rename to wayland/cc/WindowX11MotifHints.hh diff --git a/wayland/java/WindowWayland.java b/wayland/java/WindowWayland.java new file mode 100644 index 00000000..e51fa1f3 --- /dev/null +++ b/wayland/java/WindowWayland.java @@ -0,0 +1,218 @@ +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(); + // TODO: impl me + return this; + } + + @Override + public void unmarkText() { + assert _onUIThread(); + // TODO: impl me! + } + + @Override + public IRect getWindowRect() { + assert _onUIThread(); + return _nGetWindowRect(); + } + + @Override + public IRect getContentRect() { + assert _onUIThread(); + return _nGetContentRect(); + } + + @Override + public Window setWindowPosition(int left, int top) { + assert _onUIThread(); + _nSetWindowPosition(left, top); + return this; + } + + @Override + public Window setWindowSize(int width, int height) { + assert _onUIThread(); + _nSetWindowSize(width, height); + return this; + } + + @Override + public Window setContentSize(int width, int height) { + assert _onUIThread(); + _nSetContentSize(width, height); + return this; + } + + @Override + public Window setTitle(String title) { + assert _onUIThread(); + try { + _nSetTitle(title.getBytes("UTF-8")); + } catch (UnsupportedEncodingException ignored) {} + return this; + } + + @Override + public Window setIcon(File icon) { + // TODO #95 + return this; + } + + @Override + public Window setTitlebarVisible(boolean value) { + _nSetTitlebarVisible(value); + return this; + } + + @Override + public Window setVisible(boolean isVisible) { + assert _onUIThread(); + _nSetVisible(isVisible); + return super.setVisible(true); + } + + @Override + public Window hideMouseCursorUntilMoved(boolean value) { + // TODO impl me! + return this; + } + + @Override + public Window lockMouseCursor(boolean value) { + // TODO impl me! + 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(); + 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(); + // TODO implement + return this; + } + + @Override + public ZOrder getZOrder() { + assert _onUIThread(); + return ZOrder.NORMAL; + } + + @Override + public Window setZOrder(ZOrder order) { + assert _onUIThread(); + // 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(); + _nSetFullScreen(value); + return this; + } + + @Override + public boolean isFullScreen() { + assert _onUIThread(); + return _nIsFullScreen(); + } + + @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(byte[] title); + @ApiStatus.Internal public native void _nSetTitlebarVisible(boolean isVisible); + @ApiStatus.Internal public native void _nSetFullScreen(boolean isFullScreen); + @ApiStatus.Internal public native boolean _nIsFullScreen(); +} diff --git a/linux/CMakeLists.txt b/x11/CMakeLists.txt similarity index 100% rename from linux/CMakeLists.txt rename to x11/CMakeLists.txt diff --git a/x11/cc/AppX11.cc b/x11/cc/AppX11.cc new file mode 100644 index 00000000..9a1f03ec --- /dev/null +++ b/x11/cc/AppX11.cc @@ -0,0 +1,126 @@ +#include +#include "AppX11.hh" +#include +#include +#include +#include +#include +#include + +jwm::AppX11 jwm::app; + + +float jwm::AppX11::getScale() { + char *resourceString = XResourceManagerString(wm.display); + XrmDatabase db; + XrmValue value; + char *type = NULL; + + static struct once { + once() { + XrmInitialize(); + } + } once; + + if (resourceString) { + db = XrmGetStringDatabase(resourceString); + if (XrmGetResource(db, "Xft.dpi", "String", &type, &value)) { + if (value.addr) { + return atof(value.addr) / 96.f; + } + } + } + + return 1.f; +} + +void jwm::AppX11::init(JNIEnv* jniEnv) { + _jniEnv = jniEnv; +} + +void jwm::AppX11::start() { + wm.runLoop(); +} + +void jwm::AppX11::terminate() { + wm.terminate(); +} + +const std::vector& jwm::AppX11::getScreens() { + if (_screens.empty()) { + Display* display = getWindowManager().getDisplay(); + XRRScreenResources* resources = XRRGetScreenResources(display, getWindowManager().getRootWindow()); + RROutput primaryOutput = XRRGetOutputPrimary(display, getWindowManager().getRootWindow()); + int count = resources->ncrtc; + int primaryIdx = 0; + + float dpi = jwm::app.getScale(); + + for (int i = 0; i < count; ++i) { + XRRCrtcInfo* info = XRRGetCrtcInfo(display, resources, resources->crtcs[i]); + // skip empty monitors + if (info->width != 0) { + for (int j = 0; j < info->noutput; ++j) { + RROutput output = info->outputs[j]; + if (output == primaryOutput) { + primaryIdx = _screens.size(); + break; + } + } + + auto bounds = jwm::IRect::makeXYWH(info->x, info->y, info->width, info->height); + + ScreenInfo myScreenInfo = { + long(info->outputs[0]), + bounds, + false + }; + _screens.push_back(myScreenInfo); + } + + XRRFreeCrtcInfo(info); + } + XRRFreeScreenResources(resources); + _screens[primaryIdx].isPrimary = true; + } + return _screens; +} + +// 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); + }); +} + + +extern "C" JNIEXPORT jobjectArray JNICALL Java_io_github_humbleui_jwm_App__1nGetScreens(JNIEnv* env, jobject cls) noexcept { + auto& screens = jwm::app.getScreens(); + jobjectArray array = env->NewObjectArray(screens.size(), jwm::classes::Screen::kCls, 0); + float scale = jwm::app.getScale(); + size_t index = 0; + for (auto& screen : screens) { + env->SetObjectArrayElement(array, index++, screen.asJavaObject(env)); + } + + return array; +} diff --git a/x11/cc/AppX11.hh b/x11/cc/AppX11.hh new file mode 100644 index 00000000..13276503 --- /dev/null +++ b/x11/cc/AppX11.hh @@ -0,0 +1,35 @@ +#pragma once + +#include "WindowManagerX11.hh" +#include +#include "Types.hh" +#include +#include "impl/Library.hh" +#include "ScreenInfo.hh" + +namespace jwm { + extern class AppX11 { + public: + + void init(JNIEnv* jniEnv); + void start(); + void terminate(); + + WindowManagerX11& getWindowManager() { + return wm; + } + + JNIEnv* getJniEnv() { + return _jniEnv; + } + + const std::vector& getScreens(); + + float getScale(); + + JNIEnv* _jniEnv; + WindowManagerX11 wm; + std::vector _screens; + + } app; +} \ No newline at end of file diff --git a/x11/cc/ClipboardX11.cc b/x11/cc/ClipboardX11.cc new file mode 100644 index 00000000..fd8c23d7 --- /dev/null +++ b/x11/cc/ClipboardX11.cc @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include "AppX11.hh" +#include + +namespace jwm { + class ClipboardX11 { + public: + static ClipboardX11& inst() { + static ClipboardX11 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; + // HACK: prefer UTF8_STRING over text/plain and convert it to utf16 + if (formatId == "text/plain") { + contents = app.getWindowManager().getClipboardContents("UTF8_STRING"); + } + // TODO add another formats + 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::ClipboardX11::inst().set(env, entries); +} + +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_Clipboard__1nGet + (JNIEnv* env, jclass jclass, jobjectArray formats) { + return jwm::ClipboardX11::inst().get(env, formats); +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_io_github_humbleui_jwm_Clipboard__1nGetFormats + (JNIEnv* env, jclass jclass) { + return jwm::ClipboardX11::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; +} \ No newline at end of file diff --git a/x11/cc/ILayer.cc b/x11/cc/ILayer.cc new file mode 100644 index 00000000..4f7d2d1e --- /dev/null +++ b/x11/cc/ILayer.cc @@ -0,0 +1,13 @@ +#include "ILayer.hh" + +jwm::ILayer* jwm::ILayer::_ourCurrentLayer = nullptr; + + +void jwm::ILayer::makeCurrent() { + if (_ourCurrentLayer != this) { + makeCurrentForced(); + } +} +void jwm::ILayer::makeCurrentForced() { + _ourCurrentLayer = this; +} \ No newline at end of file diff --git a/x11/cc/ILayer.hh b/x11/cc/ILayer.hh new file mode 100644 index 00000000..5a22d209 --- /dev/null +++ b/x11/cc/ILayer.hh @@ -0,0 +1,23 @@ +#pragma once + +namespace jwm { + +class ILayer { +public: + + enum VSync { + VSYNC_ADAPTIVE = -1, + VSYNC_DISABLED = 0, + VSYNC_ENABLED = 1, + }; + + void makeCurrent(); + virtual void makeCurrentForced(); + virtual void setVsyncMode(VSync v) = 0; + virtual void close() = 0; + + + static ILayer* _ourCurrentLayer; +}; + +} // namespace jwm \ No newline at end of file diff --git a/x11/cc/KeyX11.cc b/x11/cc/KeyX11.cc new file mode 100644 index 00000000..e5cb0b76 --- /dev/null +++ b/x11/cc/KeyX11.cc @@ -0,0 +1,168 @@ +#include "KeyX11.hh" +#include +#include +#include +#include +#include "KeyModifier.hh" + + +bool gKeyStates[(size_t) jwm::Key::_KEY_COUNT] = {0}; + +bool jwm::KeyX11::getKeyState(jwm::Key key) { + return gKeyStates[(size_t) key]; +} + +void jwm::KeyX11::setKeyState(jwm::Key key, bool isDown) { + gKeyStates[(size_t) key] = isDown; +} + +int jwm::KeyX11::getModifiers() { + int m = 0; + + if (getKeyState(jwm::Key::SHIFT )) m |= (int)jwm::KeyModifier::SHIFT; + if (getKeyState(jwm::Key::CONTROL )) m |= (int)jwm::KeyModifier::CONTROL; + if (getKeyState(jwm::Key::ALT )) m |= (int)jwm::KeyModifier::ALT; + if (getKeyState(jwm::Key::LINUX_META )) m |= (int)jwm::KeyModifier::LINUX_META; + if (getKeyState(jwm::Key::LINUX_SUPER)) m |= (int)jwm::KeyModifier::LINUX_SUPER; + + return m; +} + +jwm::Key jwm::KeyX11::fromNative(uint32_t v) { + switch (v) { + // Modifiers + case XK_Caps_Lock: return Key::CAPS_LOCK; + case XK_Shift_R: + case XK_Shift_L: return Key::SHIFT; + case XK_Control_R: + case XK_Control_L: return Key::CONTROL; + case XK_Alt_R: + case XK_Alt_L: return Key::ALT; + // Key::WIN_LOGO + case XK_Super_L: + case XK_Super_R: return Key::LINUX_SUPER; + case XK_Meta_L: + case XK_Meta_R: return Key::LINUX_META; + // Key::MAC_COMMAND + // Key::MAC_OPTION + // Key::MAC_FN + + // Rest of the keys + case XK_Return: return Key::ENTER; + case XK_BackSpace: return Key::BACKSPACE; + case XK_Tab: return Key::TAB; + case XK_Cancel: return Key::CANCEL; + case XK_Clear: return Key::CLEAR; + case XK_Pause: return Key::PAUSE; + case XK_Escape: return Key::ESCAPE; + case XK_space: return Key::SPACE; + case XK_Page_Up: return Key::PAGE_UP; + case XK_Page_Down: return Key::PAGE_DOWN; + case XK_End: return Key::END; + case XK_Home: return Key::HOME; + case XK_Left: return Key::LEFT; + case XK_Up: return Key::UP; + case XK_Right: return Key::RIGHT; + case XK_Down: return Key::DOWN; + case XK_comma: return Key::COMMA; + case XK_minus: return Key::MINUS; + case XK_period: return Key::PERIOD; + case XK_slash: return Key::SLASH; + case XK_0: return Key::DIGIT0; + case XK_1: return Key::DIGIT1; + case XK_2: return Key::DIGIT2; + case XK_3: return Key::DIGIT3; + case XK_4: return Key::DIGIT4; + case XK_5: return Key::DIGIT5; + case XK_6: return Key::DIGIT6; + case XK_7: return Key::DIGIT7; + case XK_8: return Key::DIGIT8; + case XK_9: return Key::DIGIT9; + case XK_semicolon: return Key::SEMICOLON; + case XK_equal: return Key::EQUALS; + case XK_a: return Key::A; + case XK_b: return Key::B; + case XK_c: return Key::C; + case XK_d: return Key::D; + case XK_e: return Key::E; + case XK_f: return Key::F; + case XK_g: return Key::G; + case XK_h: return Key::H; + case XK_i: return Key::I; + case XK_j: return Key::J; + case XK_k: return Key::K; + case XK_l: return Key::L; + case XK_m: return Key::M; + case XK_n: return Key::N; + case XK_o: return Key::O; + case XK_p: return Key::P; + case XK_q: return Key::Q; + case XK_r: return Key::R; + case XK_s: return Key::S; + case XK_t: return Key::T; + case XK_u: return Key::U; + case XK_v: return Key::V; + case XK_w: return Key::W; + case XK_x: return Key::X; + case XK_y: return Key::Y; + case XK_z: return Key::Z; + case XK_bracketleft: return Key::OPEN_BRACKET; + case XK_backslash: return Key::BACK_SLASH; + case XK_bracketright: return Key::CLOSE_BRACKET; + case XK_KP_0: return Key::DIGIT0; + case XK_KP_1: return Key::DIGIT1; + case XK_KP_2: return Key::DIGIT2; + case XK_KP_3: return Key::DIGIT3; + case XK_KP_4: return Key::DIGIT4; + case XK_KP_5: return Key::DIGIT5; + case XK_KP_6: return Key::DIGIT6; + case XK_KP_7: return Key::DIGIT7; + case XK_KP_8: return Key::DIGIT8; + case XK_KP_9: return Key::DIGIT9; + case XK_multiply: return Key::MULTIPLY; + case XK_KP_Add: return Key::ADD; + case XK_KP_Separator: return Key::SEPARATOR; + case XK_KP_Subtract: return Key::MINUS; + case XK_KP_Decimal: return Key::PERIOD; + case XK_KP_Divide: return Key::SLASH; + case XK_KP_Delete: return Key::DEL; + case XK_Delete: return Key::DEL; + case XK_Num_Lock: return Key::NUM_LOCK; + case XK_Scroll_Lock: return Key::SCROLL_LOCK; + case XK_F1: return Key::F1; + case XK_F2: return Key::F2; + case XK_F3: return Key::F3; + case XK_F4: return Key::F4; + case XK_F5: return Key::F5; + case XK_F6: return Key::F6; + case XK_F7: return Key::F7; + case XK_F8: return Key::F8; + case XK_F9: return Key::F9; + case XK_F10: return Key::F10; + case XK_F11: return Key::F11; + case XK_F12: return Key::F12; + case XK_F13: return Key::F13; + case XK_F14: return Key::F14; + case XK_F15: return Key::F15; + case XK_F16: return Key::F16; + case XK_F17: return Key::F17; + case XK_F18: return Key::F18; + case XK_F19: return Key::F19; + case XK_F20: return Key::F20; + case XK_F21: return Key::F21; + case XK_F22: return Key::F22; + case XK_F23: return Key::F23; + case XK_F24: return Key::F24; + case XK_Print: return Key::PRINTSCREEN; + case XK_Insert: return Key::INSERT; + case XK_Help: return Key::HELP; + case XK_grave: return Key::BACK_QUOTE; + case XK_quoteright: return Key::QUOTE; + case XK_Menu: return Key::MENU; + // Key::KANA + // Key::VOLUME_UP + // Key::VOLUME_DOWN + // Key::MUTE + default: return Key::UNDEFINED; + } +} \ No newline at end of file diff --git a/x11/cc/KeyX11.hh b/x11/cc/KeyX11.hh new file mode 100644 index 00000000..9deb5397 --- /dev/null +++ b/x11/cc/KeyX11.hh @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "Key.hh" + +namespace jwm { + namespace KeyX11 { + jwm::Key fromNative(uint32_t v); + bool getKeyState(jwm::Key key); + void setKeyState(jwm::Key key, bool isDown); + int getModifiers(); + } +} \ No newline at end of file diff --git a/x11/cc/LayerGL.cc b/x11/cc/LayerGL.cc new file mode 100644 index 00000000..4beb64eb --- /dev/null +++ b/x11/cc/LayerGL.cc @@ -0,0 +1,123 @@ +#include +#include +#include +#include "impl/Library.hh" +#include "impl/RefCounted.hh" +#include "WindowX11.hh" +#include + +namespace jwm { + + class LayerGL: public RefCounted, public ILayer { + public: + WindowX11* fWindow; + GLXContext _context = nullptr; + using glXSwapIntervalEXT_t = void (*)(Display*, GLXDrawable, int); + glXSwapIntervalEXT_t _glXSwapIntervalEXT; + + LayerGL() = default; + virtual ~LayerGL() = default; + + void attach(WindowX11* window) { + if (window->_windowManager.getVisualInfo() == nullptr) { + throw std::runtime_error("layer not supported"); + } + + fWindow = jwm::ref(window); + fWindow->setLayer(this); + + if (_context == nullptr) { + _context = glXCreateContext(window->_windowManager.getDisplay(), + window->_windowManager.getVisualInfo(), + nullptr, + true); + + } + + makeCurrentForced(); + + _glXSwapIntervalEXT = reinterpret_cast(glXGetProcAddress(reinterpret_cast("glXSwapIntervalEXT"))); + setVsyncMode(VSYNC_ADAPTIVE); + } + + void setVsyncMode(VSync v) override { + + if (_glXSwapIntervalEXT) { + _glXSwapIntervalEXT(fWindow->_windowManager.getDisplay(), + fWindow->_x11Window, + v); + } + } + + void resize(int width, int height) { + glClearStencil(0); + glClearColor(0, 0, 0, 255); + glStencilMask(0xffffffff); + glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, width, height); + } + + void swapBuffers() { + glXSwapBuffers(fWindow->_windowManager.getDisplay(), fWindow->_x11Window); + } + + void close() override { + glXDestroyContext(fWindow->_windowManager.getDisplay(), _context); + jwm::unref(&fWindow); + } + + void makeCurrentForced() override { + ILayer::makeCurrentForced(); + glXMakeCurrent(fWindow->_windowManager.getDisplay(), + fWindow->_x11Window, + _context); + } + }; + +} // 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::WindowX11* window = reinterpret_cast(jwm::classes::Native::fromJava(env, windowObj)); + instance->attach(window); + } catch (const std::exception& e) { + 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, jint width, jint height) { +} + +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) { +} + +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(); +} \ No newline at end of file diff --git a/x11/cc/LayerRaster.cc b/x11/cc/LayerRaster.cc new file mode 100644 index 00000000..2ec70c85 --- /dev/null +++ b/x11/cc/LayerRaster.cc @@ -0,0 +1,123 @@ +// JNI + +#include +#include "impl/Library.hh" +#include "impl/RefCounted.hh" +#include "WindowX11.hh" + +namespace jwm { + class LayerRaster: public RefCounted, public ILayer { + public: + WindowX11* fWindow; + size_t _width = 0, _height = 0; + XImage* _xImage = nullptr; + GC _graphicsContext; + VSync _vsync = VSYNC_ENABLED; + + /** + * Using raw pointer here because XImage frees this buffer on XDestroyImage. + */ + uint8_t* _imageData = nullptr; + + LayerRaster() = default; + virtual ~LayerRaster() = default; + + void attach(WindowX11* window) { + fWindow = jwm::ref(window); + fWindow->setLayer(this); + + Display* d = fWindow->_windowManager.getDisplay(); + _graphicsContext = DefaultGC(d, DefaultScreen(d)); + } + + void resize(int width, int height) { + Display* d = fWindow->_windowManager.getDisplay(); + _width = width; + _height = height; + if (_xImage) { + XDestroyImage(_xImage); + } + _imageData = new uint8_t[width * height * sizeof(uint32_t)]; + _xImage = XCreateImage(d, CopyFromParent, DefaultDepth(d, DefaultScreen(d)), ZPixmap, 0, (char*)_imageData, width, height, 32, 0); + XInitImage(_xImage); + _xImage->byte_order = _xImage->bitmap_bit_order = LSBFirst; + } + + const void* getPixelsPtr() const { + return _imageData; + } + + int getRowBytes() const { + return _width * sizeof(uint32_t); + } + + void swapBuffers() { + XPutImage(fWindow->_windowManager.getDisplay(), fWindow->_x11Window, _graphicsContext, _xImage, 0, 0, 0, 0, _width, _height); + } + + void close() override { + if (_xImage) { + XDestroyImage(_xImage); + _xImage = nullptr; + } + jwm::unref(&fWindow); + } + + void makeCurrentForced() override { + ILayer::makeCurrentForced(); + } + + void setVsyncMode(VSync v) override { + _vsync = v; + } + }; +} + + +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::WindowX11* 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()); +} \ No newline at end of file diff --git a/x11/cc/MouseButtonX11.cc b/x11/cc/MouseButtonX11.cc new file mode 100644 index 00000000..c1aa2c36 --- /dev/null +++ b/x11/cc/MouseButtonX11.cc @@ -0,0 +1,25 @@ +#include "MouseButtonX11.hh" + + +jwm::MouseButton jwm::MouseButtonX11::fromNative(uint32_t v) { + switch (v) { + case 1: return jwm::MouseButton::PRIMARY; + case 2: return jwm::MouseButton::MIDDLE; + case 3: return jwm::MouseButton::SECONDARY; + case 8: return jwm::MouseButton::BACK; + case 9: return jwm::MouseButton::FORWARD; + } + return jwm::MouseButton::PRIMARY; +} + +bool jwm::MouseButtonX11::isButton(uint32_t v) { + return v < 4 || v > 7; // mouse wheel buttons +} + +int jwm::MouseButtonX11::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; +} \ No newline at end of file diff --git a/x11/cc/MouseButtonX11.hh b/x11/cc/MouseButtonX11.hh new file mode 100644 index 00000000..898edda7 --- /dev/null +++ b/x11/cc/MouseButtonX11.hh @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "MouseButton.hh" + +namespace jwm { + namespace MouseButtonX11 { + MouseButton fromNative(uint32_t v); + int fromNativeMask(unsigned v); + bool isButton(uint32_t v); + } +} \ No newline at end of file diff --git a/x11/cc/ScreenInfo.cc b/x11/cc/ScreenInfo.cc new file mode 100644 index 00000000..ddcab4c6 --- /dev/null +++ b/x11/cc/ScreenInfo.cc @@ -0,0 +1,7 @@ +#include "ScreenInfo.hh" +#include "AppX11.hh" + + +jobject jwm::ScreenInfo::asJavaObject(JNIEnv* env) const { + return jwm::classes::Screen::make(env, id, isPrimary, bounds, bounds, jwm::app.getScale()); +} \ No newline at end of file diff --git a/x11/cc/ScreenInfo.hh b/x11/cc/ScreenInfo.hh new file mode 100644 index 00000000..947890a3 --- /dev/null +++ b/x11/cc/ScreenInfo.hh @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace jwm +{ + struct ScreenInfo { + long id; + IRect bounds; + bool isPrimary; + jobject asJavaObject(JNIEnv* env) const; + }; +} // namespace jwm diff --git a/x11/cc/WindowManagerX11.cc b/x11/cc/WindowManagerX11.cc new file mode 100644 index 00000000..02fb83ef --- /dev/null +++ b/x11/cc/WindowManagerX11.cc @@ -0,0 +1,895 @@ +#include "WindowManagerX11.hh" +#include "WindowX11.hh" +#include +#include +#include +#include +#include "AppX11.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include "KeyX11.hh" +#include "MouseButtonX11.hh" +#include "StringUTF16.hh" +#include +#include "Log.hh" + +using namespace jwm; + +int WindowManagerX11::_xerrorhandler(Display* dsp, XErrorEvent* error) { + char errorstring[0x100]; + XGetErrorText(dsp, error->error_code, errorstring, sizeof(errorstring)); + fprintf(stderr, "X Error: %s\n", errorstring); + fflush(stderr); + return 0; +} + +WindowManagerX11::WindowManagerX11(): + display(XOpenDisplay(nullptr)), + _atoms(display) { + + XSetErrorHandler(_xerrorhandler); + screen = DefaultScreenOfDisplay(display); + + // for utf8 input + if (XSupportsLocale()) { + XSetLocaleModifiers("@im=none"); + + _im = XOpenIM(display, NULL, NULL, NULL); + if (_im != NULL) { + XIMStyles* styles; + if (XGetIMValues(_im, XNQueryInputStyle, &styles, NULL)) { + // could not init IM + throw std::runtime_error("failed to init IM"); + } + } + } + + // pick visual info + { + GLint att[] = { + GLX_X_RENDERABLE, True, // 1 + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, // 3 + GLX_RENDER_TYPE, GLX_RGBA_BIT, // 5 + GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, // 7 + GLX_RED_SIZE, 8, // 9 + GLX_GREEN_SIZE, 8, // 11 + GLX_BLUE_SIZE, 8, // 13 + GLX_ALPHA_SIZE, 8, // 15 + GLX_DEPTH_SIZE, 24, + GLX_STENCIL_SIZE, 8, + GLX_DOUBLEBUFFER, true, + GLX_STENCIL_SIZE, 8, + None + }; + + int fbcount; + GLXFBConfig* fbc = glXChooseFBConfig(display, DefaultScreen(display), att, &fbcount); + + if (fbc == nullptr || fbcount <= 0) { + // try to reduce system requirements + if (fbc == nullptr || fbcount <= 0) { + // try to disable rgba. + att[5] = 0; + fbc = glXChooseFBConfig(display, DefaultScreen(display), att, &fbcount); + + if (fbc == nullptr || fbcount <= 0) { + // use default attribs + glXChooseFBConfig(display, DefaultScreen(display), nullptr, &fbcount); + if (fbc == nullptr || fbcount <= 0) { + // giving up. + x11VisualInfo = nullptr; + JWM_LOG("Failed to pick OpenGL-capable X11 VisualInfo; OpenGL is not available"); + } + } + } + } + + if (fbc) { + // pick the FB config/visual with the most samples per pixel + int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = std::numeric_limits::max(); + + XVisualInfo* vi; + int i; + for (i = 0; i < fbcount; ++i) { + vi = glXGetVisualFromFBConfig(display, fbc[i]); + if (vi) { + int samp_buf, samples; + glXGetFBConfigAttrib(display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); + glXGetFBConfigAttrib(display, fbc[i], GLX_SAMPLES, &samples); + + if (best_fbc < 0 || samp_buf && samples > best_num_samp) + best_fbc = i, best_num_samp = samples; + if (worst_fbc < 0 || !samp_buf || samples < worst_num_samp) + worst_fbc = i, worst_num_samp = samples; + } + XFree(vi); + } + + GLXFBConfig bestFbc = fbc[best_fbc]; + XFree(fbc); + + // get a visual + x11VisualInfo = glXGetVisualFromFBConfig(display, bestFbc); + } + } + + // create swa + { + x11SWA.colormap = XCreateColormap(display, screen->root, getX11Visual(), AllocNone); + x11SWA.event_mask = ExposureMask + | KeyPressMask + | KeyReleaseMask + | ButtonPressMask + | ButtonReleaseMask + | StructureNotifyMask + | PointerMotionMask + | PropertyChangeMask + | FocusChangeMask + | StructureNotifyMask; + x11SWA.override_redirect = true; + } + + // XInput2 + { + int opcode, firstevent, firsterror; + if (XQueryExtension(display, "XInputExtension", &opcode, &firstevent, &firsterror)) { + int major = 2, minor = 3; + if (XIQueryVersion(display, &major, &minor) != BadRequest) { + _xi2 = std::make_unique(XInput2{ opcode }); + _xi2IterateDevices(); + } + } + } + + // load system cursors + { + const char* themeName = XcursorGetTheme(display); + int defaultSize = themeName != nullptr ? XcursorGetDefaultSize(display) : 0; + auto loadCursor = [&](const char* name, unsigned alternativeId) { + if (themeName) { + auto image = XcursorLibraryLoadImage(name, themeName, defaultSize); + if (image) { + auto cursor = XcursorImageLoadCursor(display, image); + XcursorImageDestroy(image); + return cursor; + } + } + + // fallback to non-theme cursor + return XCreateFontCursor(display, alternativeId); + }; + + _cursors[static_cast(jwm::MouseCursor::ARROW )] = loadCursor("default" , XC_left_ptr ); + _cursors[static_cast(jwm::MouseCursor::CROSSHAIR )] = loadCursor("crosshair" , XC_crosshair ); + _cursors[static_cast(jwm::MouseCursor::HELP )] = loadCursor("help" , XC_question_arrow ); + _cursors[static_cast(jwm::MouseCursor::POINTING_HAND )] = loadCursor("pointer" , XC_hand2 ); + _cursors[static_cast(jwm::MouseCursor::IBEAM )] = loadCursor("text" , XC_xterm ); + _cursors[static_cast(jwm::MouseCursor::NOT_ALLOWED )] = loadCursor("not-allowed" , XC_pirate ); + _cursors[static_cast(jwm::MouseCursor::WAIT )] = loadCursor("watch" , XC_watch ); + _cursors[static_cast(jwm::MouseCursor::RESIZE_NS )] = loadCursor("ns-resize" , XC_sb_v_double_arrow ); + _cursors[static_cast(jwm::MouseCursor::RESIZE_WE )] = loadCursor("ew-resize" , XC_sb_h_double_arrow ); + _cursors[static_cast(jwm::MouseCursor::RESIZE_NESW )] = loadCursor("nesw-resize" , XC_hand2 ); + _cursors[static_cast(jwm::MouseCursor::RESIZE_NWSE )] = loadCursor("nwse-resize" , XC_hand2 ); + } +} + +void WindowManagerX11::_xi2IterateDevices() { + int deviceCount; + XIDeviceInfo* devices = XIQueryDevice(display, XIAllDevices, &deviceCount); + for (int i = 0; i < deviceCount; ++i) { + XIDeviceInfo& device = devices[i]; + if (device.use != XIMasterPointer) { + continue; + } + for (int classId = 0; classId < device.num_classes; ++classId) { + XIAnyClassInfo* classInfo = device.classes[classId]; + XInput2::Device& myDevice = _xi2->deviceById[classInfo->sourceid]; + + switch (classInfo->type) + { + case XIScrollClass: { + XIScrollClassInfo* scroll = reinterpret_cast(classInfo); + myDevice.scroll.push_back(XInput2::Device::ScrollValuator{ + scroll->scroll_type == XIScrollTypeHorizontal, + scroll->number, + scroll->increment + }); + break; + } + } + } + } + XIFreeDeviceInfo(devices); +} + +::Window WindowManagerX11::getRootWindow() const { + return XDefaultRootWindow(display); +} + + +void WindowManagerX11::runLoop() { + _runLoop = true; + XEvent ev; + + // buffer to read into; really only needs to be two characters long due to the notifyBool fast path, but longer doesn't hurt + char buf[100]; + // initialize a pipe to write to whenever this loop needs to process something new + int pipes[2]; + if (pipe(pipes)) { + printf("Failed to open pipe\n"); + return; + } + + notifyFD = pipes[1]; + fcntl(pipes[1], F_SETFL, O_NONBLOCK); // make sure notifyLoop doesn't block + // two polled items - the X11 event queue, and our event queue + struct pollfd ps[] = {{.fd=XConnectionNumber(display), .events=POLLIN}, {.fd=pipes[0], .events=POLLIN}}; + + while (_runLoop) { + while (XPending(display)) { + XNextEvent(display, &ev); + _processXEvent(ev); + if (jwm::classes::Throwable::exceptionThrown(app.getJniEnv())) + _runLoop = false; + } + _processCallbacks(); + + // block until the next X11 or our event + if (poll(&ps[0], 2, -1) < 0) { + printf("Error during poll\n"); + break; + } + + // clear pipe + if (ps[1].revents & POLLIN) { + while (read(pipes[0], buf, sizeof(buf)) == sizeof(buf)) { } + } + // clear fast path boolean; done after clearing the pipe so that, during event execution, new notifyLoop calls can still function + notifyBool.store(false); + // The events causing a notifyLoop anywhere between poll() end and here will be processed in all cases, as that's the next thing that happens + } + + notifyFD = -1; + close(pipes[0]); + close(pipes[1]); +} + +void WindowManagerX11::notifyLoop() { + 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) + } +} + +void WindowManagerX11::_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(); + } + } + { + // copy window list in case one closes any other, invalidating some iterator in _nativeWindowToMy + std::vector copy; + for (auto& p : _nativeWindowToMy) { + copy.push_back(p.second); + } + // process redraw requests + for (auto p : copy) { + if (p->isRedrawRequested()) { + p->unsetRedrawRequest(); + if (p->_layer) { + p->_layer->makeCurrent(); + } + p->dispatch(classes::EventFrame::kInstance); + } + } + } +} + +void WindowManagerX11::_processXEvent(XEvent& ev) { + using namespace classes; + + WindowX11* myWindow = nullptr; + auto it = _nativeWindowToMy.find(ev.xkey.window); + if (it != _nativeWindowToMy.end()) { + myWindow = it->second; + } + if (myWindow == nullptr) { + // probably an XI2 event + if (ev.type == GenericEvent && _xi2 && _xi2->opcode == ev.xcookie.extension) { + if (XGetEventData(display, &ev.xcookie)) { + XIEvent* xiEvent = reinterpret_cast(ev.xcookie.data); + switch (xiEvent->evtype) { + case XI_DeviceChanged: + _xi2->deviceById.clear(); + _xi2IterateDevices(); + break; + + case XI_Enter: { + XIEnterEvent* deviceEvent = reinterpret_cast(xiEvent); + + it = _nativeWindowToMy.find(deviceEvent->event); + + if (it != _nativeWindowToMy.end()) { + myWindow = it->second; + } else { + break; + } + + for (auto& device : _xi2->deviceById) { + for (auto& valuator : device.second.scroll) { + valuator.previousValue = 0; + } + } + break; + } + + case XI_Motion: { + XIDeviceEvent* deviceEvent = reinterpret_cast(xiEvent); + + it = _nativeWindowToMy.find(deviceEvent->event); + + if (it != _nativeWindowToMy.end()) { + myWindow = it->second; + } else { + break; + } + + if (myWindow->_layer) { + myWindow->_layer->makeCurrent(); + } + + auto itMyDevice = _xi2->deviceById.find(deviceEvent->deviceid); + if (itMyDevice == _xi2->deviceById.end()) { + break; + } + + XInput2::Device& myDevice = itMyDevice->second; + double dX = 0, dY = 0; + for (int i = 0, valuatorIndex = 0; i < deviceEvent->valuators.mask_len * 8; ++i) { + if (!XIMaskIsSet(deviceEvent->valuators.mask, i)) { + continue; + } + + for (auto& valuator : myDevice.scroll) { + if (valuator.number == i) { + double value = reinterpret_cast(deviceEvent->valuators.values)[valuatorIndex]; + if (valuator.previousValue == 0) { + valuator.previousValue = value; + value = 0; + } else { + double delta = value - valuator.previousValue; + valuator.previousValue = value; + value = delta; + } + const float kPixelsPerScroll = 100; // #236 + value = value * kPixelsPerScroll / valuator.increment; + if (valuator.isHorizontal) { + dX = value; + } else { + dY = value; + } + break; + } + } + ++valuatorIndex; + } + + if (dX != 0 || dY != 0) { + jwm::JNILocal eventMouseScroll( + app.getJniEnv(), + EventMouseScroll::make( + app.getJniEnv(), + -dX, + -dY, + 0.0f, + 0.0f, + 0.0f, + lastMousePosX, + lastMousePosY, + jwm::KeyX11::getModifiers() + ) + ); + myWindow->dispatch(eventMouseScroll.get()); + } else { + unsigned mask; + ::Window unused1; + int unused2; + XQueryPointer(display, myWindow->_x11Window, &unused1, &unused1, &unused2, &unused2, &unused2, &unused2, &mask); + lastMousePosX = deviceEvent->event_x; + lastMousePosY = deviceEvent->event_y; + int movementX = 0, movementY = 0; // TODO: impl me! + jwm::JNILocal eventMove( + app.getJniEnv(), + EventMouseMove::make(app.getJniEnv(), + deviceEvent->event_x, + deviceEvent->event_y, + movementX, + movementY, + jwm::MouseButtonX11::fromNativeMask(mask), + jwm::KeyX11::getModifiers() + ) + ); + myWindow->dispatch(eventMove.get()); + } + + break; + } + } + XFreeEventData(display, &ev.xcookie); + } + } + return; + } + if (myWindow->_layer) { + myWindow->_layer->makeCurrent(); + } + + switch (ev.type) { + case ClientMessage: { + if (ev.xclient.message_type == _atoms.WM_PROTOCOLS) { + if (ev.xclient.data.l[0] == _atoms._NET_WM_SYNC_REQUEST) { + // flicker-fix sync on resize + myWindow->_xsyncRequestCounter.lo = ev.xclient.data.l[2]; + myWindow->_xsyncRequestCounter.hi = ev.xclient.data.l[3]; + } else if (ev.xclient.data.l[0] == _atoms.WM_DELETE_WINDOW) { + // close button clicked + myWindow->dispatch(EventWindowCloseRequest::kInstance); + } + } + break; + } + case ConfigureNotify: { // resize and move + WindowX11* except = nullptr; + int posX, posY; + myWindow->getContentPosition(posX, posY); + + if (posX != myWindow->_posX || posY != myWindow->_posY) { + myWindow->_posX = posX; + myWindow->_posY = posY; + + jwm::JNILocal eventMove( + app.getJniEnv(), + EventWindowMove::make(app.getJniEnv(), posX, posY) + ); + myWindow->dispatch(eventMove.get()); + } + + if (ev.xconfigure.width != myWindow->_width || ev.xconfigure.height != myWindow->_height) + { + except = myWindow; + myWindow->_width = ev.xconfigure.width; + myWindow->_height = ev.xconfigure.height; + jwm::JNILocal eventWindowResize(app.getJniEnv(), EventWindowResize::make(app.getJniEnv(), ev.xconfigure.width, ev.xconfigure.height, ev.xconfigure.width, ev.xconfigure.height)); + myWindow->dispatch(eventWindowResize.get()); + + // force redraw + if (myWindow->_layer) { + myWindow->_layer->makeCurrent(); + myWindow->_layer->setVsyncMode(ILayer::VSYNC_DISABLED); + } + myWindow->unsetRedrawRequest(); + myWindow->dispatch(EventFrame::kInstance); + + if (myWindow->_layer) { + myWindow->_layer->setVsyncMode(ILayer::VSYNC_ADAPTIVE); + } + + XSyncValue syncValue; + XSyncIntsToValue(&syncValue, + myWindow->_xsyncRequestCounter.lo, + myWindow->_xsyncRequestCounter.hi); + XSyncSetCounter(display, myWindow->_xsyncRequestCounter.counter, syncValue); + + + // force repaint all windows otherwise they will freeze on GTK-based WMs + for (auto& p : _nativeWindowToMy) { + if (except != p.second && p.second->isRedrawRequested()) { + p.second->unsetRedrawRequest(); + if (p.second->_layer) { + p.second->_layer->makeCurrent(); + } + p.second->dispatch(EventFrame::kInstance); + } + } + } + + break; + } + + case MotionNotify: { // mouse move + unsigned mask; + ::Window unused1; + int unused2; + XQueryPointer(display, myWindow->_x11Window, &unused1, &unused1, &unused2, &unused2, &unused2, &unused2, &mask); + lastMousePosX = ev.xmotion.x; + lastMousePosY = ev.xmotion.y; + jwm::JNILocal eventMove( + app.getJniEnv(), + EventMouseMove::make(app.getJniEnv(), + ev.xmotion.x, + ev.xmotion.y, + jwm::MouseButtonX11::fromNativeMask(mask), + jwm::KeyX11::getModifiers() + ) + ); + myWindow->dispatch(eventMove.get()); + break; + } + + case ButtonPress: { // mouse down + uint32_t button = ev.xbutton.button; + if (MouseButtonX11::isButton(button)) { + jwm::JNILocal eventButton( + app.getJniEnv(), + EventMouseButton::make( + app.getJniEnv(), + MouseButtonX11::fromNative(button), + true, + lastMousePosX, + lastMousePosY, + jwm::KeyX11::getModifiers() + ) + ); + myWindow->dispatch(eventButton.get()); + } + break; + } + + case ButtonRelease: { // mouse up + uint32_t button = ev.xbutton.button; + if (MouseButtonX11::isButton(button)) { + jwm::JNILocal eventButton( + app.getJniEnv(), + EventMouseButton::make( + app.getJniEnv(), + MouseButtonX11::fromNative(button), + false, + lastMousePosX, + lastMousePosY, + jwm::KeyX11::getModifiers() + ) + ); + myWindow->dispatch(eventButton.get()); + } + break; + } + + case FocusIn: { // focused + XSetICFocus(myWindow->_ic); + myWindow->dispatch(EventWindowFocusIn::kInstance); + break; + } + + case FocusOut: { // unfocused + for (size_t i = 0; i < (size_t) jwm::Key::_KEY_COUNT; i++) { + jwm::Key key = (jwm::Key) i; + if (jwm::KeyX11::getKeyState(key)) { + jwm::KeyX11::setKeyState(key, false); + jwm::JNILocal eventKey(app.getJniEnv(), EventKey::make(app.getJniEnv(), key, false, 0)); + myWindow->dispatch(eventKey.get()); + } + } + myWindow->dispatch(EventWindowFocusOut::kInstance); + break; + } + + case KeyPress: { // keyboard down + + KeySym s = XLookupKeysym(&ev.xkey, 0); + if (s != NoSymbol) { + jwm::Key key = KeyX11::fromNative(s); + jwm::KeyX11::setKeyState(key, true); + jwm::JNILocal eventKey(app.getJniEnv(), + EventKey::make(app.getJniEnv(), + key, + true, + jwm::KeyX11::getModifiers())); + myWindow->dispatch(eventKey.get()); + } + + // allow input methods (incl. XCompose) to interpret the event; but not before dispatching EventKey + if (XFilterEvent(&ev, myWindow->_x11Window)) break; + + uint8_t textBuffer[0x40]; + Status status; + int count = Xutf8LookupString(myWindow->_ic, + (XKeyPressedEvent*)&ev, + reinterpret_cast(textBuffer), + sizeof(textBuffer)-1, + &s, + &status); + if (status==XBufferOverflow) break; + + textBuffer[count] = 0; + if (count > 0) { + // ignore delete key and other control symbols + if (textBuffer[0] != 127 && textBuffer[0] > 0x1f) { + JNIEnv* env = app.getJniEnv(); + + jwm::StringUTF16 converted = reinterpret_cast(textBuffer); + jwm::JNILocal jtext = converted.toJString(env); + + jwm::JNILocal eventTextInput(env, EventTextInput::make(env, jtext.get())); + myWindow->dispatch(eventTextInput.get()); + } + } + + break; + } + + case KeyRelease: { // keyboard up + KeySym s = XLookupKeysym(&ev.xkey, 0); + jwm::Key key = KeyX11::fromNative(s); + jwm::KeyX11::setKeyState(key, false); + jwm::JNILocal eventKey(app.getJniEnv(), + EventKey::make(app.getJniEnv(), + key, + false, + jwm::KeyX11::getModifiers())); + myWindow->dispatch(eventKey.get()); + break; + } + + case SelectionRequest: { + if (ev.xselectionrequest.property == None) { + break; + } + if (ev.xselectionrequest.target == _atoms.TARGETS) { // data type request + std::vector targetAtoms = { + XInternAtom(display, "TIMESTAMP", false), + XInternAtom(display, "TARGETS", false), + XInternAtom(display, "SAVE_TARGETS", false), + XInternAtom(display, "MULTIPLE", false) + }; + // additional 10 elements to be sure + targetAtoms.reserve(10 + _myClipboardContents.size()); + for (auto& entry : _myClipboardContents) { + targetAtoms.push_back(XInternAtom(display, entry.first.c_str(), false)); + } + + // if user stored text, we should also add UTF8_STRING and STRING + if (_myClipboardContents.find("text/plain") != _myClipboardContents.end()) { + targetAtoms.insert(targetAtoms.end(), + { + XInternAtom(display, "STRING", false), + XInternAtom(display, "UTF8_STRING", false), + XInternAtom(display, "text/plain;charset=utf-8", false), + }); + } + + XChangeProperty(display, + ev.xselectionrequest.requestor, + ev.xselectionrequest.property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*) targetAtoms.data(), + targetAtoms.size()); + } else { // data request + std::string targetName; + { + char* targetNameC = XGetAtomName(display, ev.xselectionrequest.target); + targetName = targetNameC; + XFree(targetNameC); + } + auto it = _myClipboardContents.find(targetName); + if (it == _myClipboardContents.end() && (targetName == "UTF8_STRING" || targetName == "STRING" || targetName == "text/plain;charset=utf-8")) { // check for UTF8_STRING + it = _myClipboardContents.find("text/plain"); + } + if (it != _myClipboardContents.end()) { + XChangeProperty(display, + ev.xselectionrequest.requestor, + ev.xselectionrequest.property, + ev.xselectionrequest.target, + 8, + PropModeReplace, + (unsigned char*) it->second.data(), + it->second.size()); + } else { + // we cannot supply contents + ev.xselectionrequest.property = None; + } + } + + // notify the requestor + XSelectionEvent ssev; + ssev.type = SelectionNotify; + ssev.requestor = ev.xselectionrequest.requestor; + ssev.selection = ev.xselectionrequest.selection; + ssev.target = ev.xselectionrequest.target; + ssev.property = ev.xselectionrequest.property; + ssev.time = ev.xselectionrequest.time; + + XSendEvent(display, ev.xselectionrequest.requestor, True, NoEventMask, (XEvent *)&ssev); + break; + } + } +} + + +std::vector WindowManagerX11::getClipboardFormats() { + auto owner = XGetSelectionOwner(display, _atoms.CLIPBOARD); + if (owner == None) + { + return {}; + } + + assert(("create at least one window in order to use clipboard" && !_nativeWindowToMy.empty())); + + auto nativeHandle = _nativeWindowToMy.begin()->first; + assert(nativeHandle); + + XConvertSelection(display, + _atoms.CLIPBOARD, + _atoms.TARGETS, + _atoms.JWM_CLIPBOARD, + nativeHandle, + CurrentTime); + + XEvent ev; + + // fetch mime types + std::vector result; + + // using lambda here in order to break 2 loops + [&]{ + while (_runLoop) { + while (XPending(display)) { + XNextEvent(display, &ev); + if (ev.type == SelectionNotify) { + int format; + unsigned long count, lengthInBytes; + Atom type; + Atom* properties; + XGetWindowProperty(display, nativeHandle, _atoms.JWM_CLIPBOARD, 0, 1024 * sizeof(Atom), false, XA_ATOM, &type, &format, &count, &lengthInBytes, reinterpret_cast(&properties)); + + for (unsigned long i = 0; i < count; ++i) { + char* str = XGetAtomName(display, properties[i]); + if (str) { + std::string s = str; + // include only mime types + if (s.find('/') != std::string::npos) { + result.push_back(s); + } else if (s == "UTF8_STRING") { + // HACK: treat UTF8_STRING as text/plain under the hood + // avoid duplicates + std::string textPlain = "text/plain"; + if (std::find(result.begin(), result.end(), textPlain) != result.end()) { + result.push_back(textPlain); + } + } + XFree(str); + } + } + + XFree(properties); + return; + } else { + _processXEvent(ev); + } + + } + _processCallbacks(); + } + }(); + + // fetching data + + XDeleteProperty(display, nativeHandle, _atoms.JWM_CLIPBOARD); + return result; +} + +jwm::ByteBuf WindowManagerX11::getClipboardContents(const std::string& type) { + auto nativeHandle = _nativeWindowToMy.begin()->first; + + XConvertSelection(display, + _atoms.CLIPBOARD, + XInternAtom(display, type.c_str(), false), + _atoms.JWM_CLIPBOARD, + nativeHandle, + CurrentTime); + XEvent ev; + while (_runLoop) { + while (XPending(display)) { + XNextEvent(display, &ev); + switch (ev.type) + { + case SelectionNotify: { + if (ev.xselection.property == None) { + return {}; + } + + Atom da, incr, type; + int di; + unsigned long size, length, count; + unsigned char* propRet = NULL; + + XGetWindowProperty(display, nativeHandle, _atoms.JWM_CLIPBOARD, 0, 0, False, AnyPropertyType, + &type, &di, &length, &size, &propRet); + XFree(propRet); + + // Clipboard data is too large and INCR mechanism not implemented + ByteBuf result; + if (type != _atoms.INCR) + { + XGetWindowProperty(display, nativeHandle, _atoms.JWM_CLIPBOARD, 0, size, False, AnyPropertyType, + &da, &di, &length, &count, &propRet); + + result = ByteBuf{ propRet, propRet + length }; + XFree(propRet); + return result; + } + XDeleteProperty(display, nativeHandle, _atoms.JWM_CLIPBOARD); + return result; + } + default: + _processXEvent(ev); + } + } + _processCallbacks(); + } + + XDeleteProperty(display, nativeHandle, _atoms.JWM_CLIPBOARD); + return {}; +} + +void WindowManagerX11::registerWindow(WindowX11* window) { + _nativeWindowToMy[window->_x11Window] = window; + + // XInput + if (_xi2) { + XIEventMask eventMask; + unsigned char mask[2] = { 0 }; + XISetMask(mask, XI_DeviceChanged); + XISetMask(mask, XI_Motion); + XISetMask(mask, XI_Enter); + eventMask.deviceid = XIAllDevices; + eventMask.mask_len = sizeof(mask); + eventMask.mask = mask; + + XISelectEvents(display, window->_x11Window, &eventMask, 1); + } +} + +void WindowManagerX11::unregisterWindow(WindowX11* window) { + auto it = _nativeWindowToMy.find(window->_x11Window); + if (it != _nativeWindowToMy.end()) { + _nativeWindowToMy.erase(it); + } +} + +void WindowManagerX11::terminate() { + _runLoop = false; + notifyLoop(); +} + +void WindowManagerX11::setClipboardContents(std::map&& c) { + assert(("create at least one window in order to use clipboard" && !_nativeWindowToMy.empty())); + _myClipboardContents = c; + ::Window window = _nativeWindowToMy.begin()->first; + XSetSelectionOwner(display, XA_PRIMARY, window, CurrentTime); + XSetSelectionOwner(display, _atoms.CLIPBOARD, window, CurrentTime); +} + +void WindowManagerX11::enqueueTask(const std::function& task) { + std::unique_lock lock(_taskQueueLock); + _taskQueue.push(task); + _taskQueueNotify.notify_one(); + notifyLoop(); +} diff --git a/x11/cc/WindowManagerX11.hh b/x11/cc/WindowManagerX11.hh new file mode 100644 index 00000000..e07cd93a --- /dev/null +++ b/x11/cc/WindowManagerX11.hh @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "Types.hh" +#include +#include +#include +#include +#include "MouseCursor.hh" + +namespace jwm { + class WindowX11; + class WindowManagerX11 { + public: + WindowManagerX11(); + + void runLoop(); + void notifyLoop(); + void terminate(); + + void registerWindow(WindowX11* window); + void unregisterWindow(WindowX11* window); + + XVisualInfo* pickVisual(); + static int _xerrorhandler(Display* dsp, XErrorEvent* error); + void _xi2IterateDevices(); + + Display* getDisplay() const { return display; } + ::Window getRootWindow() const; + Screen* getScreen() const { return screen; } + XVisualInfo* getVisualInfo() const { return x11VisualInfo; } + XSetWindowAttributes& getSWA() { return x11SWA; } + XIM getIM() const { return _im; } + + int getX11VisualDepth() const { + if (x11VisualInfo) { + return x11VisualInfo->depth; + } + return DefaultDepth(display, 0); + } + Visual* getX11Visual() const { + if (x11VisualInfo) { + return x11VisualInfo->visual; + } + return DefaultVisual(display, 0); + } + + void enqueueTask(const std::function& task); + + void _processXEvent(XEvent& ev); + void _processCallbacks(); + + ByteBuf getClipboardContents(const std::string& type); + std::vector getClipboardFormats(); + + Display* display = nullptr; + Screen* screen; + XVisualInfo* x11VisualInfo; + XSetWindowAttributes x11SWA; + bool _runLoop; + int notifyFD = -1; + std::atomic_bool notifyBool{false}; + int lastMousePosX = 0; + int lastMousePosY = 0; + + std::map<::Window, WindowX11*> _nativeWindowToMy; + std::map _myClipboardContents; + + + + /** + * Input Manager + */ + XIM _im; + + Cursor _cursors[static_cast(jwm::MouseCursor::COUNT)]; + + std::mutex _taskQueueLock; + std::condition_variable _taskQueueNotify; + std::queue> _taskQueue; + + struct XInput2 { + int opcode; + + struct Device { + struct ScrollValuator { + bool isHorizontal; + int number; + double increment; + double previousValue = 0; + }; + std::vector scroll; + }; + std::map deviceById; + }; + + std::unique_ptr _xi2; + + struct Atoms { + Atoms(Display* display): _display(display) {} + + // display definition here allows to reference Display* in DEFINE_ATOM + Display* _display; + + #define DEFINE_ATOM(name) Atom name = XInternAtom(_display, #name, 0) + + // protocols + // NOTE: WM_DELETE_WINDOW should be the first protocol because WindowX11 references to this variable + const static int PROTOCOL_COUNT = 2; + DEFINE_ATOM(WM_DELETE_WINDOW); + DEFINE_ATOM(_NET_WM_SYNC_REQUEST); + + // other atoms + DEFINE_ATOM(_MOTIF_WM_HINTS); + DEFINE_ATOM(_NET_WM_STATE); + DEFINE_ATOM(_NET_WM_NAME); + DEFINE_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); + DEFINE_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); + DEFINE_ATOM(_NET_FRAME_EXTENTS); + DEFINE_ATOM(_NET_WM_SYNC_REQUEST_COUNTER); + DEFINE_ATOM(_NET_WM_STATE_FULLSCREEN); + DEFINE_ATOM(WM_PROTOCOLS); + DEFINE_ATOM(UTF8_STRING); + DEFINE_ATOM(CLIPBOARD); + DEFINE_ATOM(JWM_CLIPBOARD); + DEFINE_ATOM(INCR); + DEFINE_ATOM(TARGETS); + + #undef DEFINE_ATOM + } _atoms; + Atoms& getAtoms() { return _atoms; } + void setClipboardContents(std::map&& c); + }; +} diff --git a/x11/cc/WindowX11.cc b/x11/cc/WindowX11.cc new file mode 100644 index 00000000..c73fba04 --- /dev/null +++ b/x11/cc/WindowX11.cc @@ -0,0 +1,587 @@ +#include "WindowX11.hh" +#include +#include +#include +#include "AppX11.hh" +#include "impl/Library.hh" +#include "impl/JNILocal.hh" +#include +#include + + +using namespace jwm; + + +WindowX11::WindowX11(JNIEnv* env, WindowManagerX11& windowManager): + jwm::Window(env), + _windowManager(windowManager) +{ +} + +WindowX11::~WindowX11() { + close(); +} + +void WindowX11::setTitle(const std::string& title) { + XChangeProperty(_windowManager.getDisplay(), + _x11Window, + _windowManager.getAtoms()._NET_WM_NAME, + _windowManager.getAtoms().UTF8_STRING, + 8, + PropModeReplace, + reinterpret_cast(title.c_str()), + title.length()); +} + +void WindowX11::setTitlebarVisible(bool isVisible) { + MotifHints motifHints = {0}; + + motifHints.flags = MOTIF_HINTS_DECORATIONS; + motifHints.decorations = int(isVisible); + + XChangeProperty(_windowManager.getDisplay(), + _x11Window, + _windowManager.getAtoms()._MOTIF_WM_HINTS, + _windowManager.getAtoms()._MOTIF_WM_HINTS, + 32, + PropModeReplace, + (unsigned char*) &motifHints, + 5); +} + +void WindowX11::close() { + if (_x11Window) { + _windowManager.unregisterWindow(this); + XDestroyWindow(_windowManager.display, _x11Window); + _x11Window = 0; + } +} +void WindowX11::_xSendEventToWM(Atom atom, long a, long b, long c, long d, long e) const { + XEvent event = { 0 }; + event.type = ClientMessage; + event.xclient.window = _x11Window; + event.xclient.format = 32; // data is 32-bit longs + event.xclient.message_type = atom; + event.xclient.data.l[0] = a; + event.xclient.data.l[1] = b; + event.xclient.data.l[2] = c; + event.xclient.data.l[3] = d; + event.xclient.data.l[4] = e; + + XSendEvent(_windowManager.display, + DefaultRootWindow(_windowManager.display), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &event); +} +unsigned long WindowX11::_xGetWindowProperty(Atom property, Atom type, unsigned char** value) const { + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + + XGetWindowProperty(_windowManager.display, + _x11Window, + property, + 0, + std::numeric_limits::max(), + false, + type, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + value); + + return itemCount; +} + +void WindowX11::maximize() { + XWindowAttributes wa; + XGetWindowAttributes(_windowManager.display, _x11Window, &wa); + + if (wa.map_state == IsViewable) { + _xSendEventToWM(_windowManager._atoms._NET_WM_STATE, + 1, + _windowManager._atoms._NET_WM_STATE_MAXIMIZED_HORZ, + _windowManager._atoms._NET_WM_STATE_MAXIMIZED_VERT, + 0, + 0); + } else { + Atom* states = nullptr; + unsigned long count = _xGetWindowProperty(_windowManager._atoms._NET_WM_STATE, + XA_ATOM, + reinterpret_cast(&states)); + + + Atom missing[2] = { + _windowManager._atoms._NET_WM_STATE_MAXIMIZED_VERT, + _windowManager._atoms._NET_WM_STATE_MAXIMIZED_HORZ + }; + unsigned long missingCount = 2; + + for (unsigned long i = 0; i < count; i++) + { + for (unsigned long j = 0; j < missingCount; j++) + { + if (states[i] == missing[j]) + { + missing[j] = missing[missingCount - 1]; + missingCount--; + } + } + } + + if (states) + XFree(states); + + if (!missingCount) + return; + + XChangeProperty(_windowManager.display, + _x11Window, + _windowManager._atoms._NET_WM_STATE, + XA_ATOM, + 32, + PropModeAppend, + (unsigned char*) missing, + missingCount); + } + XFlush(_windowManager.display); +} + +void WindowX11::minimize() { + XIconifyWindow(_windowManager.display, _x11Window, 0); +} + +void WindowX11::restore() { + if (_windowManager._atoms._NET_WM_STATE && + _windowManager._atoms._NET_WM_STATE_MAXIMIZED_VERT && + _windowManager._atoms._NET_WM_STATE_MAXIMIZED_HORZ) { + _xSendEventToWM(_windowManager._atoms._NET_WM_STATE, + 0, + _windowManager._atoms._NET_WM_STATE_MAXIMIZED_VERT, + _windowManager._atoms._NET_WM_STATE_MAXIMIZED_HORZ, + 1, + 0); + } +} + +void WindowX11::setFullScreen(bool isFullScreen) { + // NOTE: Largely borrowed from https://github.com/godotengine/godot/blob/f7cf9fb148140b86ee5795110373a0d55ff32860/platform/linuxbsd/x11/display_server_x11.cpp + Display* display = _windowManager.display; + + // Should the window be exclusively full screen (i.e. block out other popups). + // There isn't a HumbleUI setting for this, and my WM defaults to exclusive full-screen, + // (as does Windows, as I recall) so let's assume that we want the window to be exclusively fullscreen. + bool isExclusiveFullScreen = true; + + if (isFullScreen) { // and the window is not borderless: + // Remove window decorations to simulate full screen + MotifHints hints; + Atom property; + hints.flags = 2; + hints.decorations = 0; + property = XInternAtom(display, "_MOTIF_WM_HINTS", True); + if (property != None) { + XChangeProperty(display, _x11Window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } + } + + XEvent xev; + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = _x11Window; + xev.xclient.message_type = _windowManager._atoms._NET_WM_STATE; + xev.xclient.format = 32; + xev.xclient.data.l[0] = isFullScreen ? _WM_ADD : _WM_REMOVE; + xev.xclient.data.l[1] = _windowManager._atoms._NET_WM_STATE_FULLSCREEN; + xev.xclient.data.l[2] = 0; + + XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + + // set bypass compositor hint + Atom bypass_compositor = XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", True); + unsigned long compositing_disable_on = 0; // By default, don't allow window compositing + + if (isFullScreen) { + // NOTE: Compositor flickers. May be an issue. + if (isExclusiveFullScreen) { + compositing_disable_on = 1; // Force compositing to disable for efficiency + } else { + compositing_disable_on = 2; // Force composition on to allow pop-up windows + } + } + + if (bypass_compositor != None) { + XChangeProperty(display, + _x11Window, + bypass_compositor, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char *)&compositing_disable_on, + 1); + } + + XFlush(display); + + if (!isFullScreen) { + // Reset window decorations to their previous states + MotifHints hints; + Atom property; + hints.flags = 2; + hints.decorations = 1; // Add window borders back + property = XInternAtom(display, "_MOTIF_WM_HINTS", True); + if (property != None) { + XChangeProperty(display, + _x11Window, + property, + property, + 32, + PropModeReplace, + (unsigned char *)&hints, + 5); + } + } +} + +bool WindowX11::isFullScreen() { + // NOTE: Largely borrowed from https://github.com/godotengine/godot/blob/f7cf9fb148140b86ee5795110373a0d55ff32860/platform/linuxbsd/x11/display_server_x11.cpp + Display* display = _windowManager.display; + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + bool retval = false; + + int result = XGetWindowProperty( + display, + _x11Window, + _windowManager._atoms._NET_WM_STATE, + 0, + 1024, + False, + XA_ATOM, + &type, + &format, + &len, + &remaining, + &data); + + if (result == Success) { + Atom *atoms = (Atom *)data; + for (uint64_t i = 0; i < len; i++) { + if (atoms[i] == _windowManager._atoms._NET_WM_STATE_FULLSCREEN) { + retval = true; + break; + } + } + XFree(data); + } + + return retval; +} + +void WindowX11::getDecorations(int& left, int& top, int& right, int& bottom) { + unsigned long* data = nullptr; + _xGetWindowProperty(_windowManager.getAtoms()._NET_FRAME_EXTENTS, XA_CARDINAL, reinterpret_cast(&data)); + if (data!=nullptr) { + left = data[0]; + top = data[2]; + right = data[1]; + bottom = data[3]; + XFree(data); + } else { + XWindowAttributes xwa; + XGetWindowAttributes(_windowManager.display, _x11Window, &xwa); + left = xwa.x; + top = xwa.y; + right = 0; + bottom = 0; + } +} + +void WindowX11::getContentPosition(int& posX, int& posY) { + int x, y; + ::Window child; + XTranslateCoordinates(_windowManager.display, + _x11Window, + XRootWindow(_windowManager.display, 0), + 0, 0, + &x, &y, + &child); + posX = x; + posY = y; + +} + +int WindowX11::getLeft() { + int x, y; + getContentPosition(x, y); + return x; +} + +int WindowX11::getTop() { + int x, y; + getContentPosition(x, y); + return y; +} + +int WindowX11::getWidth() { + return _width; +} + +int WindowX11::getHeight() { + return _height; +} + +float WindowX11::getScale() { + return jwm::app.getScale(); +} + +bool WindowX11::init() +{ + _x11Window = XCreateWindow(_windowManager.getDisplay(), + _windowManager.getScreen()->root, + 0, 0, + 800, 500, + 0, + _windowManager.getX11VisualDepth(), + InputOutput, + _windowManager.getX11Visual(), + CWColormap | CWEventMask | CWCursor, + &_windowManager.getSWA() + ); + + // tell X11 we want to handle close button + XSetWMProtocols(_windowManager.getDisplay(), + _x11Window, + &_windowManager.getAtoms().WM_DELETE_WINDOW, + WindowManagerX11::Atoms::PROTOCOL_COUNT); + + // IC + { + _ic = XCreateIC(_windowManager.getIM(), + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, + _x11Window, + nullptr); + + XSetICFocus(_ic); + } + + + + // XSync + { + XSyncValue value; + XSyncIntToValue(&value, 0); + _xsyncRequestCounter.counter = XSyncCreateCounter(_windowManager.getDisplay(), value); + XChangeProperty(_windowManager.getDisplay(), + _x11Window, + _windowManager.getAtoms()._NET_WM_SYNC_REQUEST_COUNTER, + XA_CARDINAL, + 32, + PropModeReplace, + (const unsigned char*)&_xsyncRequestCounter.counter, 1); + + } + _windowManager.registerWindow(this); + return true; +} + +void WindowX11::move(int left, int top) { + _posX = left; + _posY = top; + if (_visible) + XMoveWindow(_windowManager.display, _x11Window, left, top); +} + +void WindowX11::resize(int width, int height) { + _width = width; + _height = height; + if (_visible) { + XResizeWindow(_windowManager.display, _x11Window, width, height); + jwm::JNILocal eventWindowResize(app.getJniEnv(), classes::EventWindowResize::make(app.getJniEnv(), width, height, width, height)); + dispatch(eventWindowResize.get()); + } +} + +void WindowX11::setVisible(bool isVisible) { + if (_visible != isVisible) { + _visible = isVisible; + if (_visible) { + XMapWindow(_windowManager.getDisplay(), _x11Window); + if (_posX > 0 && _posY > 0) + move(_posX, _posY); + if (_width > 0 && _height > 0) + resize(_width, _height); + } else { + XUnmapWindow(_windowManager.getDisplay(), _x11Window); + } + } +} + +const ScreenInfo& WindowX11::getScreen() { + // in X11, there's no straightforward way to get screen of window. + // instead, we should do it manually using center point of the window and calculating which monitor this point + // belongs to. + + int centerX = getLeft() + getWidth() / 2; + int centerY = getTop() + getHeight() / 2; + for (auto& screen : jwm::app.getScreens()) { + if (screen.bounds.isPointInside(centerX, centerY)) { + return screen; + } + } + return *jwm::app.getScreens().begin(); +} + +void jwm::WindowX11::setCursor(jwm::MouseCursor cursor) { + if (auto x11Cursor = _windowManager._cursors[static_cast(cursor)]) { + XDefineCursor(_windowManager.display, _x11Window, x11Cursor); + } else { + XUndefineCursor(_windowManager.display, _x11Window); + } +} + +// JNI + +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_WindowX11__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_WindowX11__1nSetVisible + (JNIEnv* env, jobject obj, jboolean isVisible) { + + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->setVisible(isVisible); +} + +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowX11__1nGetWindowRect + (JNIEnv* env, jobject obj) { + jwm::WindowX11* 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_WindowX11__1nGetContentRect + (JNIEnv* env, jobject obj) { + jwm::WindowX11* 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_WindowX11__1nSetWindowPosition + (JNIEnv* env, jobject obj, int left, int top) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->move(left, top); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetWindowSize + (JNIEnv* env, jobject obj, int width, int height) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + // TODO https://github.com/HumbleUI/JWM/issues/109 + instance->resize(width, height); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetContentSize + (JNIEnv* env, jobject obj, int width, int height) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->resize(width, height); +} + +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowX11__1nGetScreen + (JNIEnv* env, jobject obj) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + return instance->getScreen().asJavaObject(env); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nRequestFrame + (JNIEnv* env, jobject obj) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->requestRedraw(); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nMaximize + (JNIEnv* env, jobject obj) { + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->maximize(); +} + + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nMinimize + (JNIEnv* env, jobject obj) { + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->minimize(); +} + + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nRestore + (JNIEnv* env, jobject obj) { + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->restore(); +} + + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nClose + (JNIEnv* env, jobject obj) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->close(); +} +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetTitle + (JNIEnv* env, jobject obj, jbyteArray title) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + + jbyte* bytes = env->GetByteArrayElements(title, nullptr); + std::string titleS = { bytes, bytes + env->GetArrayLength(title) }; + env->ReleaseByteArrayElements(title, bytes, 0); + + instance->setTitle(titleS); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetTitlebarVisible + (JNIEnv* env, jobject obj, jboolean isVisible) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->setTitlebarVisible(isVisible); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetMouseCursor + (JNIEnv* env, jobject obj, jint idx) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + + instance->setCursor(static_cast(idx)); +} + +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetFullScreen + (JNIEnv* env, jobject obj, jboolean isFullScreen) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->setFullScreen(isFullScreen); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_io_github_humbleui_jwm_WindowX11__1nIsFullScreen + (JNIEnv* env, jobject obj) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + return instance->isFullScreen(); +} diff --git a/x11/cc/WindowX11.hh b/x11/cc/WindowX11.hh new file mode 100644 index 00000000..55f5befe --- /dev/null +++ b/x11/cc/WindowX11.hh @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include "Window.hh" +#include "WindowManagerX11.hh" +#include "WindowX11MotifHints.hh" +#include "ILayer.hh" +#include "ScreenInfo.hh" + +namespace jwm { + class WindowX11: public jwm::Window { + public: + WindowX11(JNIEnv* env, WindowManagerX11& windowManager); + ~WindowX11() override; + + void getDecorations(int& left, int& top, int& right, int& bottom); + void getContentPosition(int& posX, int& posY); + void setVisible(bool isVisible); + void close(); + bool init(); + int getLeft(); + int getTop(); + int getWidth(); + int getHeight(); + float getScale(); + void move(int left, int top); + void resize(int width, int height); + 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(); + + XIC getIC() const { + return _ic; + } + void setCursor(jwm::MouseCursor cursor); + void setLayer(ILayer* layer) { + _layer = layer; + } + void _xSendEventToWM(Atom atom, long a, long b, long c, long d, long e) const; + unsigned long _xGetWindowProperty(Atom property, Atom type, unsigned char** value) const; + + const ScreenInfo& getScreen(); + + /** + * _NET_WM_SYNC_REQUEST (resize flicker fix) update request counter + */ + struct { + uint32_t lo = 0; + uint32_t hi = 0; + XID counter; + } _xsyncRequestCounter; + + int _posX = -1; + int _posY = -1; + int _width = -1; + int _height = -1; + int _WM_ADD = 1L; + int _WM_REMOVE = 0L; + bool _visible = false; + + bool _isRedrawRequested = false; + + WindowManagerX11& _windowManager; + ILayer* _layer = nullptr; + ::Window _x11Window = 0; + XIC _ic; + }; +} diff --git a/x11/cc/WindowX11MotifHints.hh b/x11/cc/WindowX11MotifHints.hh new file mode 100644 index 00000000..d610178d --- /dev/null +++ b/x11/cc/WindowX11MotifHints.hh @@ -0,0 +1,12 @@ +typedef struct { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; +} MotifHints; + + +enum { + MOTIF_HINTS_DECORATIONS = (1L << 1), +}; diff --git a/linux/java/WindowX11.java b/x11/java/WindowX11.java similarity index 100% rename from linux/java/WindowX11.java rename to x11/java/WindowX11.java From a40501c6e1ade84b0931b495ea2cc19227475b27 Mon Sep 17 00:00:00 2001 From: Jacob Chvatal Date: Sat, 31 Dec 2022 21:51:38 -0500 Subject: [PATCH 4/7] update platform to wayland at start --- shared/java/App.java | 1 + shared/java/Platform.java | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/shared/java/App.java b/shared/java/App.java index 7b3a066d..0bb6bf97 100644 --- a/shared/java/App.java +++ b/shared/java/App.java @@ -32,6 +32,7 @@ public static void start(@NotNull Runnable launcher) { Log.setVerbose("true".equals(System.getenv("JWM_VERBOSE"))); long t0 = System.currentTimeMillis(); Log.setLogger((s) -> System.out.println("[ " + (System.currentTimeMillis() - t0) + " ] " + s)); + Platform.update(); launcher.run(); }); } diff --git a/shared/java/Platform.java b/shared/java/Platform.java index 6d91bbb1..2d6c05c6 100644 --- a/shared/java/Platform.java +++ b/shared/java/Platform.java @@ -2,25 +2,32 @@ public enum Platform { WINDOWS, - X11, MACOS, + X11, WAYLAND; - public static final Platform CURRENT; + public static Platform CURRENT; static { String os = System.getProperty("os.name").toLowerCase(); if (os.contains("mac") || os.contains("darwin")) CURRENT = MACOS; else if (os.contains("windows")) CURRENT = WINDOWS; - else if (os.contains("nux") || os.contains("nix")) { - String sessionType = System.getenv("XDG_SESSION_TYPE"); - if (sessionType.contains("wayland")) - CURRENT = WAYLAND; - else if (sessionType.contains("x11")) - CURRENT = X11; - } + else if (os.contains("nux") || os.contains("nix")) + CURRENT = X11; else throw new RuntimeException("Unsupported platform: " + os); } + + // Dynamically update the platform name. + // As systems can support both X11 and Wayland, + // we suppport identifying the platform at runtime here. + public static void update() { + // X11 is Linux by default, so we fall back to it. + if (Platform.CURRENT == Platform.X11) { + String sessionType = System.getenv("XDG_SESSION_TYPE"); + if (sessionType.equals("wayland")) + CURRENT = WAYLAND; + } + } } From 610e792415b61a5a1216c38a7eabed9192bde22a Mon Sep 17 00:00:00 2001 From: Jacob Chvatal Date: Sat, 31 Dec 2022 21:56:18 -0500 Subject: [PATCH 5/7] rename AppX11 to AppWayland --- wayland/cc/{AppX11.cc => AppWayland.cc} | 2 +- wayland/cc/{AppX11.hh => AppWayland.hh} | 0 wayland/cc/ClipboardX11.cc | 4 ++-- wayland/cc/ScreenInfo.cc | 4 ++-- wayland/cc/WindowManagerX11.cc | 2 +- wayland/cc/WindowX11.cc | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename wayland/cc/{AppX11.cc => AppWayland.cc} (99%) rename wayland/cc/{AppX11.hh => AppWayland.hh} (100%) diff --git a/wayland/cc/AppX11.cc b/wayland/cc/AppWayland.cc similarity index 99% rename from wayland/cc/AppX11.cc rename to wayland/cc/AppWayland.cc index 9a1f03ec..5b21fcdf 100644 --- a/wayland/cc/AppX11.cc +++ b/wayland/cc/AppWayland.cc @@ -1,5 +1,5 @@ #include -#include "AppX11.hh" +#include "AppWayland.hh" #include #include #include diff --git a/wayland/cc/AppX11.hh b/wayland/cc/AppWayland.hh similarity index 100% rename from wayland/cc/AppX11.hh rename to wayland/cc/AppWayland.hh diff --git a/wayland/cc/ClipboardX11.cc b/wayland/cc/ClipboardX11.cc index fd8c23d7..9f8f808e 100644 --- a/wayland/cc/ClipboardX11.cc +++ b/wayland/cc/ClipboardX11.cc @@ -2,7 +2,7 @@ #include #include #include -#include "AppX11.hh" +#include "AppWayland.hh" #include namespace jwm { @@ -122,4 +122,4 @@ extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_Clipboard__1nClear extern "C" JNIEXPORT jboolean JNICALL Java_io_github_humbleui_jwm_Clipboard__1nRegisterFormat (JNIEnv* env, jclass jclass, jstring formatId) { return true; -} \ No newline at end of file +} diff --git a/wayland/cc/ScreenInfo.cc b/wayland/cc/ScreenInfo.cc index ddcab4c6..c98067b7 100644 --- a/wayland/cc/ScreenInfo.cc +++ b/wayland/cc/ScreenInfo.cc @@ -1,7 +1,7 @@ #include "ScreenInfo.hh" -#include "AppX11.hh" +#include "AppWayland.hh" jobject jwm::ScreenInfo::asJavaObject(JNIEnv* env) const { return jwm::classes::Screen::make(env, id, isPrimary, bounds, bounds, jwm::app.getScale()); -} \ No newline at end of file +} diff --git a/wayland/cc/WindowManagerX11.cc b/wayland/cc/WindowManagerX11.cc index 02fb83ef..7b2acb7b 100644 --- a/wayland/cc/WindowManagerX11.cc +++ b/wayland/cc/WindowManagerX11.cc @@ -4,7 +4,7 @@ #include #include #include -#include "AppX11.hh" +#include "AppWayland.hh" #include #include #include diff --git a/wayland/cc/WindowX11.cc b/wayland/cc/WindowX11.cc index c73fba04..4c132fa6 100644 --- a/wayland/cc/WindowX11.cc +++ b/wayland/cc/WindowX11.cc @@ -2,7 +2,7 @@ #include #include #include -#include "AppX11.hh" +#include "AppWayland.hh" #include "impl/Library.hh" #include "impl/JNILocal.hh" #include From 0736522646575db05e71a323ed249fd66f56aadb Mon Sep 17 00:00:00 2001 From: Jacob Chvatal Date: Sat, 31 Dec 2022 22:07:25 -0500 Subject: [PATCH 6/7] rename most wayland to x11 - runtime error still --- wayland/cc/AppWayland.cc | 12 +- wayland/cc/AppWayland.hh | 10 +- wayland/cc/LayerGL.cc | 10 +- wayland/cc/LayerRaster.cc | 10 +- ...wManagerX11.cc => WindowManagerWayland.cc} | 40 +++--- ...wManagerX11.hh => WindowManagerWayland.hh} | 14 +- wayland/cc/{WindowX11.cc => WindowWayland.cc} | 126 +++++++++--------- wayland/cc/{WindowX11.hh => WindowWayland.hh} | 12 +- ...tifHints.hh => WindowWaylandMotifHints.hh} | 0 9 files changed, 117 insertions(+), 117 deletions(-) rename wayland/cc/{WindowManagerX11.cc => WindowManagerWayland.cc} (96%) rename wayland/cc/{WindowManagerX11.hh => WindowManagerWayland.hh} (92%) rename wayland/cc/{WindowX11.cc => WindowWayland.cc} (80%) rename wayland/cc/{WindowX11.hh => WindowWayland.hh} (88%) rename wayland/cc/{WindowX11MotifHints.hh => WindowWaylandMotifHints.hh} (100%) diff --git a/wayland/cc/AppWayland.cc b/wayland/cc/AppWayland.cc index 5b21fcdf..979af678 100644 --- a/wayland/cc/AppWayland.cc +++ b/wayland/cc/AppWayland.cc @@ -7,10 +7,10 @@ #include #include -jwm::AppX11 jwm::app; +jwm::AppWayland jwm::app; -float jwm::AppX11::getScale() { +float jwm::AppWayland::getScale() { char *resourceString = XResourceManagerString(wm.display); XrmDatabase db; XrmValue value; @@ -34,19 +34,19 @@ float jwm::AppX11::getScale() { return 1.f; } -void jwm::AppX11::init(JNIEnv* jniEnv) { +void jwm::AppWayland::init(JNIEnv* jniEnv) { _jniEnv = jniEnv; } -void jwm::AppX11::start() { +void jwm::AppWayland::start() { wm.runLoop(); } -void jwm::AppX11::terminate() { +void jwm::AppWayland::terminate() { wm.terminate(); } -const std::vector& jwm::AppX11::getScreens() { +const std::vector& jwm::AppWayland::getScreens() { if (_screens.empty()) { Display* display = getWindowManager().getDisplay(); XRRScreenResources* resources = XRRGetScreenResources(display, getWindowManager().getRootWindow()); diff --git a/wayland/cc/AppWayland.hh b/wayland/cc/AppWayland.hh index 13276503..8a159ef5 100644 --- a/wayland/cc/AppWayland.hh +++ b/wayland/cc/AppWayland.hh @@ -1,6 +1,6 @@ #pragma once -#include "WindowManagerX11.hh" +#include "WindowManagerWayland.hh" #include #include "Types.hh" #include @@ -8,14 +8,14 @@ #include "ScreenInfo.hh" namespace jwm { - extern class AppX11 { + extern class AppWayland { public: void init(JNIEnv* jniEnv); void start(); void terminate(); - WindowManagerX11& getWindowManager() { + WindowManagerWayland& getWindowManager() { return wm; } @@ -28,8 +28,8 @@ namespace jwm { float getScale(); JNIEnv* _jniEnv; - WindowManagerX11 wm; + WindowManagerWayland wm; std::vector _screens; } app; -} \ No newline at end of file +} diff --git a/wayland/cc/LayerGL.cc b/wayland/cc/LayerGL.cc index 4beb64eb..b5aafae0 100644 --- a/wayland/cc/LayerGL.cc +++ b/wayland/cc/LayerGL.cc @@ -3,14 +3,14 @@ #include #include "impl/Library.hh" #include "impl/RefCounted.hh" -#include "WindowX11.hh" +#include "WindowWayland.hh" #include namespace jwm { class LayerGL: public RefCounted, public ILayer { public: - WindowX11* fWindow; + WindowWayland* fWindow; GLXContext _context = nullptr; using glXSwapIntervalEXT_t = void (*)(Display*, GLXDrawable, int); glXSwapIntervalEXT_t _glXSwapIntervalEXT; @@ -18,7 +18,7 @@ namespace jwm { LayerGL() = default; virtual ~LayerGL() = default; - void attach(WindowX11* window) { + void attach(WindowWayland* window) { if (window->_windowManager.getVisualInfo() == nullptr) { throw std::runtime_error("layer not supported"); } @@ -89,7 +89,7 @@ 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::WindowX11* window = reinterpret_cast(jwm::classes::Native::fromJava(env, windowObj)); + jwm::WindowWayland* window = reinterpret_cast(jwm::classes::Native::fromJava(env, windowObj)); instance->attach(window); } catch (const std::exception& e) { jwm::classes::Throwable::throwLayerNotSupportedException(env, "Failed to init OpenGL"); @@ -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/wayland/cc/LayerRaster.cc b/wayland/cc/LayerRaster.cc index 2ec70c85..883bf13e 100644 --- a/wayland/cc/LayerRaster.cc +++ b/wayland/cc/LayerRaster.cc @@ -3,12 +3,12 @@ #include #include "impl/Library.hh" #include "impl/RefCounted.hh" -#include "WindowX11.hh" +#include "WindowWayland.hh" namespace jwm { class LayerRaster: public RefCounted, public ILayer { public: - WindowX11* fWindow; + WindowWayland* fWindow; size_t _width = 0, _height = 0; XImage* _xImage = nullptr; GC _graphicsContext; @@ -22,7 +22,7 @@ namespace jwm { LayerRaster() = default; virtual ~LayerRaster() = default; - void attach(WindowX11* window) { + void attach(WindowWayland* window) { fWindow = jwm::ref(window); fWindow->setLayer(this); @@ -83,7 +83,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_LayerRaster__1nMa 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::WindowX11* window = reinterpret_cast(jwm::classes::Native::fromJava(env, windowObj)); + jwm::WindowWayland* window = reinterpret_cast(jwm::classes::Native::fromJava(env, windowObj)); instance->attach(window); } @@ -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/wayland/cc/WindowManagerX11.cc b/wayland/cc/WindowManagerWayland.cc similarity index 96% rename from wayland/cc/WindowManagerX11.cc rename to wayland/cc/WindowManagerWayland.cc index 7b2acb7b..327ecf17 100644 --- a/wayland/cc/WindowManagerX11.cc +++ b/wayland/cc/WindowManagerWayland.cc @@ -1,5 +1,5 @@ -#include "WindowManagerX11.hh" -#include "WindowX11.hh" +#include "WindowManagerWayland.hh" +#include "WindowWayland.hh" #include #include #include @@ -21,7 +21,7 @@ using namespace jwm; -int WindowManagerX11::_xerrorhandler(Display* dsp, XErrorEvent* error) { +int WindowManagerWayland::_xerrorhandler(Display* dsp, XErrorEvent* error) { char errorstring[0x100]; XGetErrorText(dsp, error->error_code, errorstring, sizeof(errorstring)); fprintf(stderr, "X Error: %s\n", errorstring); @@ -29,7 +29,7 @@ int WindowManagerX11::_xerrorhandler(Display* dsp, XErrorEvent* error) { return 0; } -WindowManagerX11::WindowManagerX11(): +WindowManagerWayland::WindowManagerWayland(): display(XOpenDisplay(nullptr)), _atoms(display) { @@ -179,7 +179,7 @@ WindowManagerX11::WindowManagerX11(): } } -void WindowManagerX11::_xi2IterateDevices() { +void WindowManagerWayland::_xi2IterateDevices() { int deviceCount; XIDeviceInfo* devices = XIQueryDevice(display, XIAllDevices, &deviceCount); for (int i = 0; i < deviceCount; ++i) { @@ -208,12 +208,12 @@ void WindowManagerX11::_xi2IterateDevices() { XIFreeDeviceInfo(devices); } -::Window WindowManagerX11::getRootWindow() const { +::Window WindowManagerWayland::getRootWindow() const { return XDefaultRootWindow(display); } -void WindowManagerX11::runLoop() { +void WindowManagerWayland::runLoop() { _runLoop = true; XEvent ev; @@ -260,7 +260,7 @@ void WindowManagerX11::runLoop() { close(pipes[1]); } -void WindowManagerX11::notifyLoop() { +void WindowManagerWayland::notifyLoop() { if (notifyFD==-1) return; // fast notifyBool path to not make system calls when not necessary if (!notifyBool.exchange(true)) { @@ -269,7 +269,7 @@ void WindowManagerX11::notifyLoop() { } } -void WindowManagerX11::_processCallbacks() { +void WindowManagerWayland::_processCallbacks() { { // process ui thread callbacks std::unique_lock lock(_taskQueueLock); @@ -284,7 +284,7 @@ void WindowManagerX11::_processCallbacks() { } { // copy window list in case one closes any other, invalidating some iterator in _nativeWindowToMy - std::vector copy; + std::vector copy; for (auto& p : _nativeWindowToMy) { copy.push_back(p.second); } @@ -301,10 +301,10 @@ void WindowManagerX11::_processCallbacks() { } } -void WindowManagerX11::_processXEvent(XEvent& ev) { +void WindowManagerWayland::_processXEvent(XEvent& ev) { using namespace classes; - WindowX11* myWindow = nullptr; + WindowWayland* myWindow = nullptr; auto it = _nativeWindowToMy.find(ev.xkey.window); if (it != _nativeWindowToMy.end()) { myWindow = it->second; @@ -455,7 +455,7 @@ void WindowManagerX11::_processXEvent(XEvent& ev) { break; } case ConfigureNotify: { // resize and move - WindowX11* except = nullptr; + WindowWayland* except = nullptr; int posX, posY; myWindow->getContentPosition(posX, posY); @@ -723,7 +723,7 @@ void WindowManagerX11::_processXEvent(XEvent& ev) { } -std::vector WindowManagerX11::getClipboardFormats() { +std::vector WindowManagerWayland::getClipboardFormats() { auto owner = XGetSelectionOwner(display, _atoms.CLIPBOARD); if (owner == None) { @@ -795,7 +795,7 @@ std::vector WindowManagerX11::getClipboardFormats() { return result; } -jwm::ByteBuf WindowManagerX11::getClipboardContents(const std::string& type) { +jwm::ByteBuf WindowManagerWayland::getClipboardContents(const std::string& type) { auto nativeHandle = _nativeWindowToMy.begin()->first; XConvertSelection(display, @@ -849,7 +849,7 @@ jwm::ByteBuf WindowManagerX11::getClipboardContents(const std::string& type) { return {}; } -void WindowManagerX11::registerWindow(WindowX11* window) { +void WindowManagerWayland::registerWindow(WindowWayland* window) { _nativeWindowToMy[window->_x11Window] = window; // XInput @@ -867,19 +867,19 @@ void WindowManagerX11::registerWindow(WindowX11* window) { } } -void WindowManagerX11::unregisterWindow(WindowX11* window) { +void WindowManagerWayland::unregisterWindow(WindowWayland* window) { auto it = _nativeWindowToMy.find(window->_x11Window); if (it != _nativeWindowToMy.end()) { _nativeWindowToMy.erase(it); } } -void WindowManagerX11::terminate() { +void WindowManagerWayland::terminate() { _runLoop = false; notifyLoop(); } -void WindowManagerX11::setClipboardContents(std::map&& c) { +void WindowManagerWayland::setClipboardContents(std::map&& c) { assert(("create at least one window in order to use clipboard" && !_nativeWindowToMy.empty())); _myClipboardContents = c; ::Window window = _nativeWindowToMy.begin()->first; @@ -887,7 +887,7 @@ void WindowManagerX11::setClipboardContents(std::map&& c) XSetSelectionOwner(display, _atoms.CLIPBOARD, window, CurrentTime); } -void WindowManagerX11::enqueueTask(const std::function& task) { +void WindowManagerWayland::enqueueTask(const std::function& task) { std::unique_lock lock(_taskQueueLock); _taskQueue.push(task); _taskQueueNotify.notify_one(); diff --git a/wayland/cc/WindowManagerX11.hh b/wayland/cc/WindowManagerWayland.hh similarity index 92% rename from wayland/cc/WindowManagerX11.hh rename to wayland/cc/WindowManagerWayland.hh index e07cd93a..fe9cd788 100644 --- a/wayland/cc/WindowManagerX11.hh +++ b/wayland/cc/WindowManagerWayland.hh @@ -15,17 +15,17 @@ #include "MouseCursor.hh" namespace jwm { - class WindowX11; - class WindowManagerX11 { + class WindowWayland; + class WindowManagerWayland { public: - WindowManagerX11(); + WindowManagerWayland(); void runLoop(); void notifyLoop(); void terminate(); - void registerWindow(WindowX11* window); - void unregisterWindow(WindowX11* window); + void registerWindow(WindowWayland* window); + void unregisterWindow(WindowWayland* window); XVisualInfo* pickVisual(); static int _xerrorhandler(Display* dsp, XErrorEvent* error); @@ -69,7 +69,7 @@ namespace jwm { int lastMousePosX = 0; int lastMousePosY = 0; - std::map<::Window, WindowX11*> _nativeWindowToMy; + std::map<::Window, WindowWayland*> _nativeWindowToMy; std::map _myClipboardContents; @@ -111,7 +111,7 @@ namespace jwm { #define DEFINE_ATOM(name) Atom name = XInternAtom(_display, #name, 0) // protocols - // NOTE: WM_DELETE_WINDOW should be the first protocol because WindowX11 references to this variable + // NOTE: WM_DELETE_WINDOW should be the first protocol because WindowWayland references to this variable const static int PROTOCOL_COUNT = 2; DEFINE_ATOM(WM_DELETE_WINDOW); DEFINE_ATOM(_NET_WM_SYNC_REQUEST); diff --git a/wayland/cc/WindowX11.cc b/wayland/cc/WindowWayland.cc similarity index 80% rename from wayland/cc/WindowX11.cc rename to wayland/cc/WindowWayland.cc index 4c132fa6..ca2d869b 100644 --- a/wayland/cc/WindowX11.cc +++ b/wayland/cc/WindowWayland.cc @@ -1,4 +1,4 @@ -#include "WindowX11.hh" +#include "WindowWayland.hh" #include #include #include @@ -12,17 +12,17 @@ using namespace jwm; -WindowX11::WindowX11(JNIEnv* env, WindowManagerX11& windowManager): +WindowWayland::WindowWayland(JNIEnv* env, WindowManagerWayland& windowManager): jwm::Window(env), _windowManager(windowManager) { } -WindowX11::~WindowX11() { +WindowWayland::~WindowWayland() { close(); } -void WindowX11::setTitle(const std::string& title) { +void WindowWayland::setTitle(const std::string& title) { XChangeProperty(_windowManager.getDisplay(), _x11Window, _windowManager.getAtoms()._NET_WM_NAME, @@ -33,7 +33,7 @@ void WindowX11::setTitle(const std::string& title) { title.length()); } -void WindowX11::setTitlebarVisible(bool isVisible) { +void WindowWayland::setTitlebarVisible(bool isVisible) { MotifHints motifHints = {0}; motifHints.flags = MOTIF_HINTS_DECORATIONS; @@ -49,14 +49,14 @@ void WindowX11::setTitlebarVisible(bool isVisible) { 5); } -void WindowX11::close() { +void WindowWayland::close() { if (_x11Window) { _windowManager.unregisterWindow(this); XDestroyWindow(_windowManager.display, _x11Window); _x11Window = 0; } } -void WindowX11::_xSendEventToWM(Atom atom, long a, long b, long c, long d, long e) const { +void WindowWayland::_xSendEventToWM(Atom atom, long a, long b, long c, long d, long e) const { XEvent event = { 0 }; event.type = ClientMessage; event.xclient.window = _x11Window; @@ -74,7 +74,7 @@ void WindowX11::_xSendEventToWM(Atom atom, long a, long b, long c, long d, long SubstructureNotifyMask | SubstructureRedirectMask, &event); } -unsigned long WindowX11::_xGetWindowProperty(Atom property, Atom type, unsigned char** value) const { +unsigned long WindowWayland::_xGetWindowProperty(Atom property, Atom type, unsigned char** value) const { Atom actualType; int actualFormat; unsigned long itemCount, bytesAfter; @@ -95,7 +95,7 @@ unsigned long WindowX11::_xGetWindowProperty(Atom property, Atom type, unsigned return itemCount; } -void WindowX11::maximize() { +void WindowWayland::maximize() { XWindowAttributes wa; XGetWindowAttributes(_windowManager.display, _x11Window, &wa); @@ -149,11 +149,11 @@ void WindowX11::maximize() { XFlush(_windowManager.display); } -void WindowX11::minimize() { +void WindowWayland::minimize() { XIconifyWindow(_windowManager.display, _x11Window, 0); } -void WindowX11::restore() { +void WindowWayland::restore() { if (_windowManager._atoms._NET_WM_STATE && _windowManager._atoms._NET_WM_STATE_MAXIMIZED_VERT && _windowManager._atoms._NET_WM_STATE_MAXIMIZED_HORZ) { @@ -166,7 +166,7 @@ void WindowX11::restore() { } } -void WindowX11::setFullScreen(bool isFullScreen) { +void WindowWayland::setFullScreen(bool isFullScreen) { // NOTE: Largely borrowed from https://github.com/godotengine/godot/blob/f7cf9fb148140b86ee5795110373a0d55ff32860/platform/linuxbsd/x11/display_server_x11.cpp Display* display = _windowManager.display; @@ -246,7 +246,7 @@ void WindowX11::setFullScreen(bool isFullScreen) { } } -bool WindowX11::isFullScreen() { +bool WindowWayland::isFullScreen() { // NOTE: Largely borrowed from https://github.com/godotengine/godot/blob/f7cf9fb148140b86ee5795110373a0d55ff32860/platform/linuxbsd/x11/display_server_x11.cpp Display* display = _windowManager.display; @@ -285,7 +285,7 @@ bool WindowX11::isFullScreen() { return retval; } -void WindowX11::getDecorations(int& left, int& top, int& right, int& bottom) { +void WindowWayland::getDecorations(int& left, int& top, int& right, int& bottom) { unsigned long* data = nullptr; _xGetWindowProperty(_windowManager.getAtoms()._NET_FRAME_EXTENTS, XA_CARDINAL, reinterpret_cast(&data)); if (data!=nullptr) { @@ -304,7 +304,7 @@ void WindowX11::getDecorations(int& left, int& top, int& right, int& bottom) { } } -void WindowX11::getContentPosition(int& posX, int& posY) { +void WindowWayland::getContentPosition(int& posX, int& posY) { int x, y; ::Window child; XTranslateCoordinates(_windowManager.display, @@ -318,31 +318,31 @@ void WindowX11::getContentPosition(int& posX, int& posY) { } -int WindowX11::getLeft() { +int WindowWayland::getLeft() { int x, y; getContentPosition(x, y); return x; } -int WindowX11::getTop() { +int WindowWayland::getTop() { int x, y; getContentPosition(x, y); return y; } -int WindowX11::getWidth() { +int WindowWayland::getWidth() { return _width; } -int WindowX11::getHeight() { +int WindowWayland::getHeight() { return _height; } -float WindowX11::getScale() { +float WindowWayland::getScale() { return jwm::app.getScale(); } -bool WindowX11::init() +bool WindowWayland::init() { _x11Window = XCreateWindow(_windowManager.getDisplay(), _windowManager.getScreen()->root, @@ -360,7 +360,7 @@ bool WindowX11::init() XSetWMProtocols(_windowManager.getDisplay(), _x11Window, &_windowManager.getAtoms().WM_DELETE_WINDOW, - WindowManagerX11::Atoms::PROTOCOL_COUNT); + WindowManagerWayland::Atoms::PROTOCOL_COUNT); // IC { @@ -394,14 +394,14 @@ bool WindowX11::init() return true; } -void WindowX11::move(int left, int top) { +void WindowWayland::move(int left, int top) { _posX = left; _posY = top; if (_visible) XMoveWindow(_windowManager.display, _x11Window, left, top); } -void WindowX11::resize(int width, int height) { +void WindowWayland::resize(int width, int height) { _width = width; _height = height; if (_visible) { @@ -411,7 +411,7 @@ void WindowX11::resize(int width, int height) { } } -void WindowX11::setVisible(bool isVisible) { +void WindowWayland::setVisible(bool isVisible) { if (_visible != isVisible) { _visible = isVisible; if (_visible) { @@ -426,7 +426,7 @@ void WindowX11::setVisible(bool isVisible) { } } -const ScreenInfo& WindowX11::getScreen() { +const ScreenInfo& WindowWayland::getScreen() { // in X11, there's no straightforward way to get screen of window. // instead, we should do it manually using center point of the window and calculating which monitor this point // belongs to. @@ -441,7 +441,7 @@ const ScreenInfo& WindowX11::getScreen() { return *jwm::app.getScreens().begin(); } -void jwm::WindowX11::setCursor(jwm::MouseCursor cursor) { +void jwm::WindowWayland::setCursor(jwm::MouseCursor cursor) { if (auto x11Cursor = _windowManager._cursors[static_cast(cursor)]) { XDefineCursor(_windowManager.display, _x11Window, x11Cursor); } else { @@ -451,24 +451,24 @@ void jwm::WindowX11::setCursor(jwm::MouseCursor cursor) { // JNI -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_jwm_WindowX11__1nMake +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()); + 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_WindowX11__1nSetVisible +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); + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->setVisible(isVisible); } -extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowX11__1nGetWindowRect +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nGetWindowRect (JNIEnv* env, jobject obj) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, 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; @@ -482,9 +482,9 @@ extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowX11__1nGe ); } -extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowX11__1nGetContentRect +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nGetContentRect (JNIEnv* env, jobject obj) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, 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( @@ -496,63 +496,63 @@ extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowX11__1nGe ); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetWindowPosition +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetWindowPosition (JNIEnv* env, jobject obj, int left, int top) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); instance->move(left, top); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetWindowSize +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetWindowSize (JNIEnv* env, jobject obj, int width, int height) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); // TODO https://github.com/HumbleUI/JWM/issues/109 instance->resize(width, height); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetContentSize +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetContentSize (JNIEnv* env, jobject obj, int width, int height) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); instance->resize(width, height); } -extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowX11__1nGetScreen +extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nGetScreen (JNIEnv* env, jobject obj) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); return instance->getScreen().asJavaObject(env); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nRequestFrame +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nRequestFrame (JNIEnv* env, jobject obj) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); instance->requestRedraw(); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nMaximize +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(); + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->maximize(); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nMinimize +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(); + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->minimize(); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nRestore +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(); + reinterpret_cast(jwm::classes::Native::fromJava(env, obj))->restore(); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nClose +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nClose (JNIEnv* env, jobject obj) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); instance->close(); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetTitle +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetTitle (JNIEnv* env, jobject obj, jbyteArray title) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); jbyte* bytes = env->GetByteArrayElements(title, nullptr); std::string titleS = { bytes, bytes + env->GetArrayLength(title) }; @@ -561,27 +561,27 @@ extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetTi instance->setTitle(titleS); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetTitlebarVisible +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetTitlebarVisible (JNIEnv* env, jobject obj, jboolean isVisible) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); instance->setTitlebarVisible(isVisible); } -extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetMouseCursor +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetMouseCursor (JNIEnv* env, jobject obj, jint idx) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + 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_WindowX11__1nSetFullScreen +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nSetFullScreen (JNIEnv* env, jobject obj, jboolean isFullScreen) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); instance->setFullScreen(isFullScreen); } -extern "C" JNIEXPORT jboolean JNICALL Java_io_github_humbleui_jwm_WindowX11__1nIsFullScreen +extern "C" JNIEXPORT jboolean JNICALL Java_io_github_humbleui_jwm_WindowWayland__1nIsFullScreen (JNIEnv* env, jobject obj) { - jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + jwm::WindowWayland* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); return instance->isFullScreen(); } diff --git a/wayland/cc/WindowX11.hh b/wayland/cc/WindowWayland.hh similarity index 88% rename from wayland/cc/WindowX11.hh rename to wayland/cc/WindowWayland.hh index 55f5befe..330b3ff2 100644 --- a/wayland/cc/WindowX11.hh +++ b/wayland/cc/WindowWayland.hh @@ -4,16 +4,16 @@ #include #include #include "Window.hh" -#include "WindowManagerX11.hh" -#include "WindowX11MotifHints.hh" +#include "WindowManagerWayland.hh" +#include "WindowWaylandMotifHints.hh" #include "ILayer.hh" #include "ScreenInfo.hh" namespace jwm { - class WindowX11: public jwm::Window { + class WindowWayland: public jwm::Window { public: - WindowX11(JNIEnv* env, WindowManagerX11& windowManager); - ~WindowX11() override; + WindowWayland(JNIEnv* env, WindowManagerWayland& windowManager); + ~WindowWayland() override; void getDecorations(int& left, int& top, int& right, int& bottom); void getContentPosition(int& posX, int& posY); @@ -78,7 +78,7 @@ namespace jwm { bool _isRedrawRequested = false; - WindowManagerX11& _windowManager; + WindowManagerWayland& _windowManager; ILayer* _layer = nullptr; ::Window _x11Window = 0; XIC _ic; diff --git a/wayland/cc/WindowX11MotifHints.hh b/wayland/cc/WindowWaylandMotifHints.hh similarity index 100% rename from wayland/cc/WindowX11MotifHints.hh rename to wayland/cc/WindowWaylandMotifHints.hh From 701d9ed2cef2e62a4d9331f50b523b734db1a74b Mon Sep 17 00:00:00 2001 From: Jacob Chvatal Date: Thu, 5 Jan 2023 15:07:04 -0500 Subject: [PATCH 7/7] experiments work? --- .gitignore | 4 +- flake.nix | 8 +++- wayland/java/WindowWayland.java | 68 ++++++++++++++------------------- 3 files changed, 37 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 4d39354b..4665b099 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ docs/apidocs /macos/cmake-build-debug # jvm crash logs -hs_err_*.log \ No newline at end of file +hs_err_*.log + +experiments/ diff --git a/flake.nix b/flake.nix index a5aea34b..b06835c3 100644 --- a/flake.nix +++ b/flake.nix @@ -36,12 +36,16 @@ in libXcursor libXi ]; + + waylandDependencies = with pkgs; [ + wayland + ]; in rec { devShells.default = pkgs.mkShell { inherit name description; nativeBuildInputs = with pkgs; [ jdk17 - ] ++ xDependencies; + ] ++ xDependencies ++ waylandDependencies; buildInputs = with pkgs; [ python3 @@ -52,7 +56,7 @@ in ]; CMAKE_MAKE_PROGRAM = pkgs.ninja; - LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (with pkgs; [ libGL ] ++ xDependencies)}:$LD_LIBRARY_PATH"; + LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (with pkgs; [ libGL ] ++ xDependencies ++ waylandDependencies)}:$LD_LIBRARY_PATH"; }; # For compatibility with older versions of the `nix` binary diff --git a/wayland/java/WindowWayland.java b/wayland/java/WindowWayland.java index e51fa1f3..58becb09 100644 --- a/wayland/java/WindowWayland.java +++ b/wayland/java/WindowWayland.java @@ -78,13 +78,13 @@ public Window setIcon(File icon) { @Override public Window setTitlebarVisible(boolean value) { - _nSetTitlebarVisible(value); + throw new UnsupportedOperationException("impl me!"); return this; } @Override public Window setVisible(boolean isVisible) { - assert _onUIThread(); + throw new UnsupportedOperationException("impl me!"); _nSetVisible(isVisible); return super.setVisible(true); } @@ -131,41 +131,33 @@ public void requestFrame() { @Override public void close() { - assert _onUIThread() && !isClosed(); - _nClose(); + throw new UnsupportedOperationException("impl me!"); super.close(); } @Override public Window maximize() { - _nMaximize(); - return this; + throw new UnsupportedOperationException("impl me!"); } @Override public Window minimize() { - _nMinimize(); - return this; + throw new UnsupportedOperationException("impl me!"); } @Override public Window focus() { - assert _onUIThread(); - // TODO implement - return this; + throw new UnsupportedOperationException("impl me!"); } @Override public ZOrder getZOrder() { - assert _onUIThread(); - return ZOrder.NORMAL; + throw new UnsupportedOperationException("impl me!"); } @Override public Window setZOrder(ZOrder order) { - assert _onUIThread(); - // TODO implement - return this; + throw new UnsupportedOperationException("impl me!"); } @Override @@ -180,39 +172,35 @@ public Window setProgressBar(float progress) { @Override public Window restore() { - _nRestore(); - return this; + throw new UnsupportedOperationException("impl me!"); } @Override public Window setFullScreen(boolean value) { - assert _onUIThread(); - _nSetFullScreen(value); - return this; + throw new UnsupportedOperationException("impl me!"); } @Override public boolean isFullScreen() { - assert _onUIThread(); - return _nIsFullScreen(); + throw new UnsupportedOperationException("impl me!"); } @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(byte[] 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 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(byte[] title); + // @ApiStatus.Internal public native void _nSetTitlebarVisible(boolean isVisible); + // @ApiStatus.Internal public native void _nSetFullScreen(boolean isFullScreen); + // @ApiStatus.Internal public native boolean _nIsFullScreen(); }