Created
March 19, 2025 02:12
-
-
Save adrientetar/b039998a0752261d2ee7db9ade3e7c15 to your computer and use it in GitHub Desktop.
Custom title bar in Compose Multiplatform with JBR runtime. Updated for the new WindowDecorations API. known JBR 17 old version: b1207.30 new version: b1367.22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import sun.misc.Unsafe | |
| import java.awt.Frame | |
| import java.lang.reflect.AccessibleObject | |
| import java.lang.reflect.Method | |
| /** | |
| * Access custom window decoration functions in the JetBrains Runtime. | |
| * | |
| * Original code: <https://github.com/ButterCam/compose-jetbrains-theme/blob/main/expui/src/main/kotlin/io/kanro/compose/jetbrains/expui/util/CustomWindowDecorationAccessing.kt> | |
| * Updated for new WindowDecorations JBR API: <https://android.googlesource.com/platform/external/jetbrains/JetBrainsRuntime/+/a8a5398a738fab3daa559874e779faad9c85e746%5E%21/#F24> | |
| */ | |
| object WindowDecorations { | |
| init { | |
| UnsafeAccess.assignAccessibility( | |
| UnsafeAccess.desktopModule, | |
| listOf("java.awt") | |
| ) | |
| } | |
| private val windowDecorationsInstance: Any? = try { | |
| val windowDecorations = Class.forName(windowDecorationsClassName) | |
| val constructor = windowDecorations.declaredConstructors.first() | |
| constructor.isAccessible = true | |
| constructor.newInstance() | |
| } catch (e: Exception) { | |
| null | |
| } | |
| private val createCustomTitleBarMethod: Method? = | |
| getMethod("createCustomTitleBar") | |
| private val setHeightMethod: Method? = | |
| getCustomTitleBarMethod("setHeight", Float::class.java) | |
| private fun getMethod(name: String, vararg params: Class<*>): Method? { | |
| return try { | |
| val clazz = Class.forName(windowDecorationsClassName) | |
| val method = clazz.getDeclaredMethod(name, *params) | |
| method.isAccessible = true | |
| method | |
| } catch (e: Exception) { | |
| null | |
| } | |
| } | |
| private fun getCustomTitleBarMethod(name: String, vararg params: Class<*>): Method? { | |
| return try { | |
| val clazz = Class.forName(customTitleBarClassName) | |
| val method = clazz.getDeclaredMethod(name, *params) | |
| method.isAccessible = true | |
| method | |
| } catch (e: Exception) { | |
| null | |
| } | |
| } | |
| /** | |
| * Disable the system title bar for the provided [frame] - only retain the window controls, | |
| * centered around the provided [height]. | |
| * | |
| * @return `true` if the custom title bar was set successfully, `false` otherwise. | |
| */ | |
| fun setCustomTitleBar(frame: Frame, height: Float): Boolean { | |
| val windowDecorations = windowDecorationsInstance ?: return false | |
| // Create custom title bar | |
| val customTitleBar = run { | |
| val method = createCustomTitleBarMethod ?: return false | |
| method.invoke(windowDecorations) ?: return false | |
| } | |
| // Set the height | |
| val setHeightMethod = setHeightMethod ?: return false | |
| setHeightMethod.invoke(customTitleBar, height) | |
| // Set custom title bar | |
| val setCustomTitleBarMethod = getMethod( | |
| "setCustomTitleBar", Frame::class.java, customTitleBar::class.java | |
| ) ?: return false | |
| setCustomTitleBarMethod.invoke(windowDecorations, frame, customTitleBar) | |
| return true | |
| } | |
| } | |
| private const val windowDecorationsClassName = "java.awt.Window\$WindowDecorations" | |
| private const val customTitleBarClassName = "java.awt.Window\$CustomTitleBar" | |
| private object UnsafeAccess { | |
| private val unsafe: Any? by lazy { | |
| try { | |
| val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe") | |
| theUnsafe.isAccessible = true | |
| theUnsafe.get(null) as Unsafe | |
| } catch (e: Throwable) { | |
| null | |
| } | |
| } | |
| val desktopModule by lazy { | |
| ModuleLayer.boot().findModule("java.desktop").get() | |
| } | |
| val ownerModule by lazy { | |
| this.javaClass.module | |
| } | |
| private val isAccessibleFieldOffset: Long? by lazy { | |
| try { | |
| (unsafe as? Unsafe)?.objectFieldOffset(Parent::class.java.getDeclaredField("first")) | |
| } catch (e: Throwable) { | |
| null | |
| } | |
| } | |
| private val implAddOpens by lazy { | |
| try { | |
| Module::class.java.getDeclaredMethod( | |
| "implAddOpens", String::class.java, Module::class.java | |
| ).accessible() | |
| } catch (e: Throwable) { | |
| null | |
| } | |
| } | |
| fun assignAccessibility(obj: AccessibleObject) { | |
| try { | |
| val theUnsafe = unsafe as? Unsafe ?: return | |
| val offset = isAccessibleFieldOffset ?: return | |
| theUnsafe.putBooleanVolatile(obj, offset, true) | |
| } catch (e: Throwable) { | |
| // ignore | |
| } | |
| } | |
| fun assignAccessibility(module: Module, packages: List<String>) { | |
| try { | |
| packages.forEach { | |
| implAddOpens?.invoke(module, it, ownerModule) | |
| } | |
| } catch (e: Throwable) { | |
| // ignore | |
| } | |
| } | |
| private class Parent { | |
| var first = false | |
| @Volatile | |
| var second: Any? = null | |
| } | |
| } | |
| private fun <T : AccessibleObject> T.accessible(): T { | |
| return apply { | |
| UnsafeAccess.assignAccessibility(this) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Do you know if we can allow the OS to handle mouse events in that title bar area? For dragging and such.
I can do
WindowDraggableArea() { Box(Modifier.fillMaxWidth().height(titleBarHeight)) }but that doesn't support Aero snap or double-clicking to maximise, nor does it restore the window if dragging when maximised or have the native position constraints.I also tried looking at https://github.com/compose-fluent/compose-fluent-ui/tree/master/gallery/src/desktopMain/kotlin/io/github/composefluent/gallery but cannot see an easy way to extract its custom title bar solution since it's so integrated into the gallery project and associated library.