From 254269608c2ce2b9438574ab5748548595d93b2d Mon Sep 17 00:00:00 2001 From: dzaima Date: Fri, 27 Sep 2024 15:58:06 +0300 Subject: [PATCH 1/3] X11: add setClassHint --- linux/cc/WindowX11.cc | 28 ++++++++++++++++++++++++---- linux/cc/WindowX11.hh | 1 + linux/java/WindowX11.java | 20 ++++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/linux/cc/WindowX11.cc b/linux/cc/WindowX11.cc index 0bd16de0..00f7fc37 100644 --- a/linux/cc/WindowX11.cc +++ b/linux/cc/WindowX11.cc @@ -32,6 +32,16 @@ void WindowX11::setTitle(const std::string& title) { title.length()); } +void WindowX11::setClass(const std::string& name, const std::string& appClass) { + XClassHint *classHint = XAllocClassHint(); + if (classHint) { + classHint->res_name = const_cast(name.c_str()); + classHint->res_class = const_cast(appClass.c_str()); + XSetClassHint(_windowManager.getDisplay(), _x11Window, classHint); + XFree(classHint); + } +} + void WindowX11::setTitlebarVisible(bool isVisible) { MotifHints motifHints = {0}; @@ -555,15 +565,25 @@ extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nClose jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); instance->close(); } + +static std::string bytesToString(JNIEnv* env, jbyteArray arr) { + jbyte* bytes = env->GetByteArrayElements(arr, nullptr); + std::string res = { bytes, bytes + env->GetArrayLength(arr) }; + env->ReleaseByteArrayElements(arr, bytes, 0); + return res; +} + 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)); + instance->setTitle(bytesToString(env, title)); +} - jbyte* bytes = env->GetByteArrayElements(title, nullptr); - std::string titleS = { bytes, bytes + env->GetArrayLength(title) }; - env->ReleaseByteArrayElements(title, bytes, 0); +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetClassHint + (JNIEnv* env, jobject obj, jbyteArray name, jbyteArray appClass) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); - instance->setTitle(titleS); + instance->setClass(bytesToString(env, name), bytesToString(env, appClass)); } extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetTitlebarVisible diff --git a/linux/cc/WindowX11.hh b/linux/cc/WindowX11.hh index 55f5befe..8acf6a96 100644 --- a/linux/cc/WindowX11.hh +++ b/linux/cc/WindowX11.hh @@ -38,6 +38,7 @@ namespace jwm { return _isRedrawRequested; } void setTitle(const std::string& title); + void setClass(const std::string& name, const std::string& class_); void setTitlebarVisible(bool isVisible); void maximize(); diff --git a/linux/java/WindowX11.java b/linux/java/WindowX11.java index c11920df..1b359420 100644 --- a/linux/java/WindowX11.java +++ b/linux/java/WindowX11.java @@ -76,6 +76,25 @@ public Window setIcon(File icon) { return this; } + /** + *

Set the WM_CLASS window property.

+ * + * @param appClass application class + * @return this + */ + public Window setClassHint(String appClass) { + setClassHint(appClass, appClass); + return this; + } + + public Window setClassHint(String name, String appClass) { + assert _onUIThread() : "Should be run on UI thread"; + try { + _nSetClassHint(name.getBytes("UTF-8"), appClass.getBytes("UTF-8")); + } catch (UnsupportedEncodingException ignored) {} + return this; + } + @Override public Window setTitlebarVisible(boolean value) { _nSetTitlebarVisible(value); @@ -226,6 +245,7 @@ public boolean isFullScreen() { @ApiStatus.Internal public native void _nMinimize(); @ApiStatus.Internal public native void _nRestore(); @ApiStatus.Internal public native void _nSetTitle(byte[] title); + @ApiStatus.Internal public native void _nSetClassHint(byte[] name, byte[] appClass); @ApiStatus.Internal public native void _nSetTitlebarVisible(boolean isVisible); @ApiStatus.Internal public native void _nSetFullScreen(boolean isFullScreen); @ApiStatus.Internal public native boolean _nIsFullScreen(); From 071a5ca7dec0aab8ffccd9edc071bb2ef7885466 Mon Sep 17 00:00:00 2001 From: dzaima Date: Fri, 27 Sep 2024 15:46:06 +0300 Subject: [PATCH 2/3] X11: add setIconData --- linux/cc/WindowManagerX11.hh | 1 + linux/cc/WindowX11.cc | 32 ++++++++++++++++++++++++++++++++ linux/cc/WindowX11.hh | 1 + linux/java/WindowX11.java | 19 +++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/linux/cc/WindowManagerX11.hh b/linux/cc/WindowManagerX11.hh index aa120063..e8d5b9ee 100644 --- a/linux/cc/WindowManagerX11.hh +++ b/linux/cc/WindowManagerX11.hh @@ -121,6 +121,7 @@ namespace jwm { DEFINE_ATOM(_MOTIF_WM_HINTS); DEFINE_ATOM(_NET_WM_STATE); DEFINE_ATOM(_NET_WM_NAME); + DEFINE_ATOM(_NET_WM_ICON); DEFINE_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); DEFINE_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); DEFINE_ATOM(_NET_FRAME_EXTENTS); diff --git a/linux/cc/WindowX11.cc b/linux/cc/WindowX11.cc index 00f7fc37..e5c17f4a 100644 --- a/linux/cc/WindowX11.cc +++ b/linux/cc/WindowX11.cc @@ -42,6 +42,29 @@ void WindowX11::setClass(const std::string& name, const std::string& appClass) { } } +void WindowX11::setIconData(int width, int height, const unsigned char* argb) { + size_t size = width * height; + size_t count = size + 2; + std::unique_ptr buffer{new long[count]}; + buffer[0] = width; + buffer[1] = height; + for (size_t i = 0; i < size; i++) { + uint32_t c = static_cast(argb[i*4]) | + static_cast(argb[i*4 + 1]) << 8 | + static_cast(argb[i*4 + 2]) << 16 | + static_cast(argb[i*4 + 3]) << 24; + buffer[i+2] = c; + } + XChangeProperty(_windowManager.getDisplay(), + _x11Window, + _windowManager.getAtoms()._NET_WM_ICON, + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast(buffer.get()), + count); +} + void WindowX11::setTitlebarVisible(bool isVisible) { MotifHints motifHints = {0}; @@ -586,6 +609,15 @@ extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetCl instance->setClass(bytesToString(env, name), bytesToString(env, appClass)); } +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowX11__1nSetIconData + (JNIEnv* env, jobject obj, jint width, jint height, jbyteArray data) { + jwm::WindowX11* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + + jbyte* bytes = env->GetByteArrayElements(data, nullptr); + instance->setIconData(width, height, reinterpret_cast(bytes)); + env->ReleaseByteArrayElements(data, bytes, 0); +} + 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)); diff --git a/linux/cc/WindowX11.hh b/linux/cc/WindowX11.hh index 8acf6a96..b3507c2b 100644 --- a/linux/cc/WindowX11.hh +++ b/linux/cc/WindowX11.hh @@ -39,6 +39,7 @@ namespace jwm { } void setTitle(const std::string& title); void setClass(const std::string& name, const std::string& class_); + void setIconData(int width, int height, const unsigned char* argb); void setTitlebarVisible(bool isVisible); void maximize(); diff --git a/linux/java/WindowX11.java b/linux/java/WindowX11.java index 1b359420..70156b17 100644 --- a/linux/java/WindowX11.java +++ b/linux/java/WindowX11.java @@ -95,6 +95,24 @@ public Window setClassHint(String name, String appClass) { return this; } + /** + *

Set window icon from raw image bytes.

+ * + *

{@code data} must have a length of {@code width * height * 4}, representing per-pixel ARGB data.

+ * + * @param width icon width in pixels + * @param height icon height in pixels + * @param data icon image data + * @return this + */ + @NotNull @Contract("-> this") + public Window setIconData(int width, int height, byte[] data) { + assert _onUIThread() : "Should be run on UI thread"; + assert data.length == width*height*4 : "Incorrect icon data array length"; + _nSetIconData(width, height, data); + return this; + } + @Override public Window setTitlebarVisible(boolean value) { _nSetTitlebarVisible(value); @@ -246,6 +264,7 @@ public boolean isFullScreen() { @ApiStatus.Internal public native void _nRestore(); @ApiStatus.Internal public native void _nSetTitle(byte[] title); @ApiStatus.Internal public native void _nSetClassHint(byte[] name, byte[] appClass); + @ApiStatus.Internal public native void _nSetIconData(int width, int height, byte[] data); @ApiStatus.Internal public native void _nSetTitlebarVisible(boolean isVisible); @ApiStatus.Internal public native void _nSetFullScreen(boolean isFullScreen); @ApiStatus.Internal public native boolean _nIsFullScreen(); From a8f89e4c602ca88e157f05f9853411893202c938 Mon Sep 17 00:00:00 2001 From: dzaima Date: Fri, 27 Sep 2024 15:28:56 +0300 Subject: [PATCH 3/3] X11: use setClassHint and setIconData in dashboard example --- examples/dashboard/java/Example.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/examples/dashboard/java/Example.java b/examples/dashboard/java/Example.java index f8a2ee20..42e89470 100644 --- a/examples/dashboard/java/Example.java +++ b/examples/dashboard/java/Example.java @@ -10,6 +10,9 @@ import java.util.function.*; import java.util.stream.*; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; public class Example implements Consumer { public static int PADDING = 10; @@ -82,6 +85,17 @@ public Example() { case MACOS -> { window.setIcon(new File("examples/dashboard/resources/macos.icns")); } + case X11 -> { + ((WindowX11) window).setClassHint("jwm-dashboard-example"); // allows OS-wide identification of the window (e.g. icon themes, .desktop files) + try { + Bitmap i = Bitmap.makeFromImage(Image.makeDeferredFromEncodedBytes(Files.readAllBytes(Path.of("examples/dashboard/resources/linux/icon_48x48.png")))); + ImageInfo info = i.getImageInfo(); + + ((WindowX11) window).setIconData(info.getWidth(), info.getHeight(), i.readPixels()); + } catch (IOException e) { + e.printStackTrace(); + } + } } window.setVisible(true);