feat: get the first menu to show
This commit is contained in:
parent
184ca19652
commit
9a813fbbaf
@ -33,7 +33,8 @@ dependencies {
|
||||
}
|
||||
extraResources(project(mapOf("path" to ":native", "configuration" to "results")))
|
||||
implementation("io.gitlab.jfronny:commons-unsafe:2.0.0-SNAPSHOT")
|
||||
implementation("com.github.hypfvieh:dbus-java-core:5.0.0")
|
||||
// implementation("com.github.hypfvieh:dbus-java-core:5.0.0")
|
||||
// implementation("com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0")
|
||||
}
|
||||
|
||||
val copyExtraResources by tasks.creating(Copy::class) {
|
||||
@ -66,25 +67,26 @@ abstract class InterfaceGenerateTask : DefaultTask() {
|
||||
false,
|
||||
introspectionData,
|
||||
objectPath.get(),
|
||||
busName.get(),
|
||||
null,
|
||||
true
|
||||
busName.get()
|
||||
)
|
||||
val analyze = generator.analyze(true)!!
|
||||
if (analyze.isEmpty()) throw IllegalStateException("No interfaces found")
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
output.deleteRecursively()
|
||||
output.createDirectories()
|
||||
val regex = Regex("List<org\\.freedesktop\\.dbus\\.Struct<Integer>, ([^_\\n]+)>")
|
||||
val memory = LinkedHashMap<String, String>()
|
||||
val illegalStruct = Regex("List<org\\.freedesktop\\.dbus\\.Struct<Integer>, ([^_\\n]+)>")
|
||||
val illegalTuple = Regex("public ([A-Za-z]+Tuple) ")
|
||||
val fieldPattern = Regex("@Position\\(\\d+\\)\\r?\\n +private (.+) [a-zA-Z]+;")
|
||||
val structMemory = LinkedHashMap<String, String>()
|
||||
val tupleMemory = LinkedHashMap<String, String>()
|
||||
for (entry in analyze) {
|
||||
if (entry.key.path.equals("/.java")) continue // Skip incorrectly generated file
|
||||
if (entry.key.path.equals("/.java") || entry.key.path.endsWith("Tuple.java")) continue // Skip incorrectly generated file
|
||||
val pth = output.resolve(entry.key.path.trimStart('/'))
|
||||
pth.createParentDirs()
|
||||
// Fix the incorrect generic type
|
||||
Files.writeString(pth, entry.value.replace(regex) { match ->
|
||||
memory.computeIfAbsent(match.groups[1]!!.value) { type ->
|
||||
val name = "Struct${memory.size + 1}"
|
||||
Files.writeString(pth, entry.value.replace(illegalStruct) { match ->
|
||||
structMemory.computeIfAbsent(match.groups[1]!!.value) { type ->
|
||||
val name = "Struct${structMemory.size + 1}"
|
||||
Files.writeString(output.resolve("com/canonical").resolve("$name.java"), """
|
||||
package com.canonical;
|
||||
|
||||
@ -107,6 +109,13 @@ abstract class InterfaceGenerateTask : DefaultTask() {
|
||||
""".trimIndent())
|
||||
name
|
||||
}
|
||||
}.replace(illegalTuple) { match ->
|
||||
tupleMemory.computeIfAbsent(match.groups[1]!!.value) { type ->
|
||||
val impl = analyze[analyze.keys.first { it.path.contains(type) }]!!
|
||||
val found = fieldPattern.findAll(impl).toList()
|
||||
if (found.size != 2) throw IllegalStateException("Tuple must have exactly two fields")
|
||||
"io.gitlab.jfronny.globalmenu.DPair<${found[0].groups[1]!!.value}, ${found[1].groups[1]!!.value}>"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.github.hypfvieh:dbus-java-utils:5.0.0")
|
||||
classpath("com.github.hypfvieh:dbus-java-utils:4.3.2")
|
||||
}
|
||||
}
|
||||
|
||||
|
30
src/main/java/io/gitlab/jfronny/globalmenu/DPair.java
Normal file
30
src/main/java/io/gitlab/jfronny/globalmenu/DPair.java
Normal file
@ -0,0 +1,30 @@
|
||||
package io.gitlab.jfronny.globalmenu;
|
||||
|
||||
import org.freedesktop.dbus.Tuple;
|
||||
import org.freedesktop.dbus.annotations.Position;
|
||||
|
||||
public class DPair<A, B> extends Tuple {
|
||||
@Position(0) private A a;
|
||||
@Position(1) private B b;
|
||||
|
||||
public DPair(A a, B b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public void setA(A a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public A getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
public void setB(B b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public B getB() {
|
||||
return b;
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package io.gitlab.jfronny.globalmenu
|
||||
|
||||
import com.canonical.Dbusmenu
|
||||
import com.canonical.appmenu.Registrar
|
||||
import com.intellij.openapi.application.Application
|
||||
import com.intellij.openapi.application.ApplicationActivationListener
|
||||
@ -8,6 +7,7 @@ import com.intellij.openapi.wm.IdeFrame
|
||||
import com.intellij.openapi.wm.impl.IdeFrameImpl
|
||||
import com.intellij.openapi.wm.impl.ProjectFrameHelper
|
||||
import io.gitlab.jfronny.globalmenu.proxy.DbusmenuImpl
|
||||
import io.gitlab.jfronny.globalmenu.proxy.SwingMenuHolder
|
||||
import org.freedesktop.dbus.DBusPath
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder
|
||||
import org.freedesktop.dbus.types.UInt32
|
||||
@ -20,8 +20,6 @@ class GlobalMenuService(private val app: Application) : ApplicationActivationLis
|
||||
ideFrame.project?.let { project ->
|
||||
println(ideFrame.javaClass)
|
||||
if (ideFrame is ProjectFrameHelper) {
|
||||
// ideFrame.frame.jMenuBar = JMenuBar()
|
||||
// ideFrame.rootPane.jMenuBar
|
||||
visualize(ideFrame.rootPane.jMenuBar, ideFrame.rootPane.peer)
|
||||
} else if (ideFrame is IdeFrameImpl) {
|
||||
visualize(ideFrame.jMenuBar, ideFrame.peer)
|
||||
@ -31,29 +29,21 @@ class GlobalMenuService(private val app: Application) : ApplicationActivationLis
|
||||
}
|
||||
|
||||
fun visualize(menu: JMenuBar, peer: Peer) {
|
||||
GlobalMenu.Log.warn("Using peer: $peer")
|
||||
//TODO send to compositor
|
||||
for (i in 0 until menu.menuCount) {
|
||||
val submenu = menu.getMenu(i)
|
||||
for (j in 0 until submenu.itemCount) {
|
||||
val component = submenu.getItem(j)
|
||||
GlobalMenu.Log.warn("${submenu.text}.${component?.text}")
|
||||
}
|
||||
}
|
||||
val conn = DBusConnectionBuilder.forSessionBus().build()
|
||||
|
||||
val menu: Dbusmenu = DbusmenuImpl()
|
||||
// firefox seems to use mObjectPath(nsPrintfCString("/com/canonical/menu/%u", sID++))
|
||||
conn.exportObject("/com/canonical/dbusmenu", menu)
|
||||
val windowPtr = peer.nativePtr
|
||||
val menu = DbusmenuImpl(windowPtr, SwingMenuHolder(menu, "DBusMenuRoot"))
|
||||
conn.unExportObject(menu.objectPath)
|
||||
conn.exportObject(menu)
|
||||
|
||||
if (peer is WLPeer) {
|
||||
peer.performLocked {
|
||||
val ptr = GlobalMenu.Native.create(peer.nativePtr)
|
||||
val ptr = GlobalMenu.Native.create(windowPtr)
|
||||
GlobalMenu.Native.setAddress(ptr, conn.uniqueName, menu.objectPath)
|
||||
}
|
||||
} else {
|
||||
val registrar = conn.getRemoteObject("org.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar", Registrar::class.java)
|
||||
registrar.RegisterWindow(UInt32(peer.nativePtr), DBusPath(menu.objectPath))
|
||||
registrar.RegisterWindow(UInt32(windowPtr), DBusPath(menu.objectPath))
|
||||
}
|
||||
}
|
||||
}
|
@ -19,11 +19,14 @@ sealed interface Peer {
|
||||
val nativePtr: Long
|
||||
}
|
||||
fun Peer(peer: Any): Peer {
|
||||
if (X11Peer.componentPeerClass.isInstance(peer)) return X11Peer(peer)
|
||||
if (WLPeer.componentPeerClass.isInstance(peer)) return WLPeer(peer)
|
||||
if (peer.javaClass.name.contains("X11")) return X11Peer(peer)
|
||||
if (peer.javaClass.name.contains("WL")) return WLPeer(peer)
|
||||
throw IllegalArgumentException("Unknown peer type: ${peer.javaClass}")
|
||||
}
|
||||
class X11Peer(private val inner: Any) : Peer {
|
||||
init {
|
||||
componentPeerClass.cast(inner)
|
||||
}
|
||||
override val nativePtr: Long get() = getPtrMethod(inner)
|
||||
|
||||
companion object {
|
||||
@ -32,6 +35,9 @@ class X11Peer(private val inner: Any) : Peer {
|
||||
}
|
||||
}
|
||||
class WLPeer(private val inner: Any) : Peer {
|
||||
init {
|
||||
componentPeerClass.cast(inner)
|
||||
}
|
||||
override val nativePtr: Long get() = nativePtrField.getLong(inner)
|
||||
fun performLocked(runnable: Runnable) {
|
||||
performLockedMethod(inner, runnable)
|
||||
|
@ -1,58 +1,77 @@
|
||||
package io.gitlab.jfronny.globalmenu.proxy
|
||||
|
||||
import com.canonical.*
|
||||
import io.gitlab.jfronny.globalmenu.DPair
|
||||
import org.freedesktop.dbus.types.UInt32
|
||||
import org.freedesktop.dbus.types.Variant
|
||||
|
||||
class DbusmenuImpl : Dbusmenu {
|
||||
override fun getObjectPath(): String {
|
||||
TODO("Not yet implemented")
|
||||
class DbusmenuImpl(windowId: Long, private val menuHolder: MenuHolder) : Dbusmenu {
|
||||
private val menuPath: String = "/com/canonical/menu0x${windowId.toString(16)}"
|
||||
override fun getObjectPath(): String = menuPath
|
||||
// override fun getVersion(): UInt32 = UInt32(3)
|
||||
// override fun getTextDirection(): String = "none"
|
||||
// override fun getStatus(): String = "normal"
|
||||
//
|
||||
// override fun getIconThemePath(): Dbusmenu.PropertyIconThemePathType =
|
||||
// object : Dbusmenu.PropertyIconThemePathType, List<String> by listOf() {}
|
||||
|
||||
override fun GetLayout(parentId: Int, recursionDepth: Int, propertyNames: MutableList<String>?): DPair<UInt32, GetLayoutStruct> {
|
||||
return DPair(
|
||||
UInt32(parentId.toLong()),
|
||||
getLayout(parentId, recursionDepth, propertyNames, menuHolder.find(parentId)!!)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getVersion(): UInt32 {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
private fun getLayout(parentId: Int, recursionDepth: Int, propertyNames: MutableList<String>?, menu: Menu): GetLayoutStruct {
|
||||
val properties = readProperties(menu)
|
||||
val children = mutableListOf<Variant<*>>()
|
||||
|
||||
override fun getTextDirection(): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
menu.children?.let {
|
||||
for (sm in it) {
|
||||
children.add(Variant(getLayout(parentId, recursionDepth, propertyNames, sm)))
|
||||
}
|
||||
if (it.isNotEmpty()) properties["children-display"] = Variant("submenu")
|
||||
}
|
||||
|
||||
override fun getStatus(): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getIconThemePath(): Dbusmenu.PropertyIconThemePathType {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun GetLayout(parentId: Int, recursionDepth: Int, propertyNames: MutableList<String>?): GetLayoutTuple {
|
||||
TODO("Not yet implemented")
|
||||
return GetLayoutStruct(menu.id, properties, children)
|
||||
}
|
||||
|
||||
override fun GetGroupProperties(
|
||||
ids: MutableList<Int>?,
|
||||
ids: MutableList<Int>,
|
||||
propertyNames: MutableList<String>?
|
||||
): MutableList<GetGroupPropertiesStruct> {
|
||||
TODO("Not yet implemented")
|
||||
): MutableList<GetGroupPropertiesStruct> = mutableListOf<GetGroupPropertiesStruct>().apply {
|
||||
ids.forEach { id ->
|
||||
menuHolder.find(id)?.let { menu ->
|
||||
add(GetGroupPropertiesStruct(id, readProperties(menu)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun GetProperty(id: Int, name: String?): Variant<*> {
|
||||
TODO("Not yet implemented")
|
||||
override fun GetProperty(id: Int, name: String?): Variant<*>? = menuHolder.find(id)?.let { menu -> readProperties(menu)[name] }
|
||||
|
||||
private fun readProperties(menu: Menu): MutableMap<String, Variant<*>> {
|
||||
if (menu.isSeparator) return mutableMapOf("type" to Variant("separator"))
|
||||
val properties = mutableMapOf<String, Variant<*>>()
|
||||
properties["type"] = Variant("standard")
|
||||
properties["label"] = Variant(menu.label)
|
||||
properties["visible"] = Variant(menu.isVisible)
|
||||
properties["enabled"] = Variant(menu.isEnabled)
|
||||
if (!menu.shortcut.isNullOrEmpty()) properties["shortcut"] = Variant(menu.shortcut)
|
||||
if (!menu.toggleType.isNullOrEmpty()) {
|
||||
properties["toggle-type"] = Variant(menu.toggleType)
|
||||
properties["toggle-state"] = Variant(menu.toggleState)
|
||||
}
|
||||
if (menu.iconData?.isNotEmpty() == true) properties["icon-data"] = Variant(menu.iconData)
|
||||
return properties
|
||||
}
|
||||
|
||||
override fun Event(id: Int, eventId: String?, data: Variant<*>?, timestamp: UInt32?) {
|
||||
TODO("Not yet implemented")
|
||||
if ("clicked".endsWith(eventId!!)) { //TODO this seems off
|
||||
menuHolder.find(id)?.onEvent()
|
||||
}
|
||||
}
|
||||
|
||||
override fun EventGroup(events: MutableList<EventGroupStruct>?): MutableList<Int> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun AboutToShow(id: Int): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun AboutToShowGroup(ids: MutableList<Int>?): AboutToShowGroupTuple {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override fun EventGroup(events: MutableList<EventGroupStruct>?): MutableList<Int>? = null // not needed?
|
||||
override fun AboutToShow(id: Int): Boolean = true // not needed?
|
||||
override fun AboutToShowGroup(ids: MutableList<Int>?): DPair<MutableList<Int>, MutableList<Int>>? = null // not needed?
|
||||
}
|
||||
|
15
src/main/kotlin/io/gitlab/jfronny/globalmenu/proxy/Menu.kt
Normal file
15
src/main/kotlin/io/gitlab/jfronny/globalmenu/proxy/Menu.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package io.gitlab.jfronny.globalmenu.proxy
|
||||
|
||||
interface Menu {
|
||||
val id: Int
|
||||
val isSeparator: Boolean
|
||||
val label: String
|
||||
val isEnabled: Boolean
|
||||
val isVisible: Boolean
|
||||
val iconData: ByteArray?
|
||||
val shortcut: Array<String>?
|
||||
val toggleType: String?
|
||||
val toggleState: Int
|
||||
val children: List<Menu>?
|
||||
fun onEvent()
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package io.gitlab.jfronny.globalmenu.proxy
|
||||
|
||||
interface MenuHolder {
|
||||
fun find(menuId: Int): Menu?
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package io.gitlab.jfronny.globalmenu.proxy
|
||||
|
||||
import io.gitlab.jfronny.globalmenu.GlobalMenu
|
||||
import org.apache.commons.io.output.ByteArrayOutputStream
|
||||
import java.awt.event.ActionEvent
|
||||
import java.awt.event.InputEvent
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.image.BufferedImage
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.JCheckBoxMenuItem
|
||||
import javax.swing.JMenu
|
||||
import javax.swing.JMenuItem
|
||||
import javax.swing.JRadioButtonMenuItem
|
||||
|
||||
class SwingMenu(private val menuItem: JMenuItem?, private val holder: SwingMenuHolder) : Menu {
|
||||
override val id = holder.getId(menuItem)
|
||||
override val isSeparator: Boolean get() = menuItem == null
|
||||
override val label: String get() = menuItem?.text ?: ""
|
||||
override val isEnabled: Boolean get() = menuItem?.isEnabled ?: false
|
||||
override val isVisible: Boolean get() = menuItem?.isVisible ?: false
|
||||
override val iconData: ByteArray? get() = menuItem?.icon?.let { icon ->
|
||||
val width = icon.iconWidth
|
||||
val height = icon.iconHeight
|
||||
|
||||
val bufferedImage = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
|
||||
|
||||
bufferedImage.createGraphics().apply {
|
||||
icon.paintIcon(menuItem, this, 0, 0)
|
||||
dispose()
|
||||
}
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream().use { stream ->
|
||||
ImageIO.write(bufferedImage, "png", stream)
|
||||
stream.toByteArray()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
GlobalMenu.Log.error("Failed to convert icon to byte array", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override val shortcut: Array<String>? get() = menuItem?.accelerator?.let { ks ->
|
||||
val s = getModifiersText(ks.modifiers)
|
||||
val vk = keyEvents[ks.keyCode] ?: "UNKNOWN"
|
||||
val l = mutableListOf<String>()
|
||||
val st = StringTokenizer(s)
|
||||
while (st.hasMoreTokens()) l.add(st.nextToken())
|
||||
l.add(vk)
|
||||
l.toTypedArray()
|
||||
}
|
||||
|
||||
private fun getModifiersText(modifiers: Int): String = buildString {
|
||||
if (modifiers and InputEvent.SHIFT_DOWN_MASK != 0) append("Shift ")
|
||||
if (modifiers and InputEvent.CTRL_DOWN_MASK != 0) append("Ctrl ")
|
||||
if (modifiers and InputEvent.META_DOWN_MASK != 0) append("Meta ")
|
||||
if (modifiers and InputEvent.ALT_DOWN_MASK != 0) append("Alt ")
|
||||
if (modifiers and InputEvent.ALT_GRAPH_DOWN_MASK != 0) append("AltGraph ")
|
||||
if (modifiers and InputEvent.BUTTON1_DOWN_MASK != 0) append("Button1 ")
|
||||
if (modifiers and InputEvent.BUTTON2_DOWN_MASK != 0) append("Button2 ")
|
||||
if (modifiers and InputEvent.BUTTON3_DOWN_MASK != 0) append("Button3 ")
|
||||
}
|
||||
|
||||
override val toggleType: String? get() = when (menuItem) {
|
||||
is JRadioButtonMenuItem -> "radio"
|
||||
is JCheckBoxMenuItem -> "checkmark"
|
||||
else -> null
|
||||
}
|
||||
override val toggleState: Int get() = if (toggleType?.isNotEmpty() == true) if (menuItem!!.isSelected) 1 else 0 else -1
|
||||
override val children: List<Menu>? get() = if (menuItem is JMenu) {
|
||||
(0 until menuItem.itemCount).map { SwingMenu(menuItem.getItem(it), holder) }
|
||||
} else null
|
||||
|
||||
override fun onEvent() {
|
||||
val event = ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, menuItem!!.actionCommand)
|
||||
for (it in menuItem.actionListeners) it.actionPerformed(event)
|
||||
if (menuItem is JCheckBoxMenuItem) menuItem.isSelected = !menuItem.isSelected
|
||||
if (menuItem is JRadioButtonMenuItem) menuItem.isSelected = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
val keyEvents: Map<Int, String> = KeyEvent::class.java.fields
|
||||
.filter { it.modifiers == Modifier.PUBLIC or Modifier.STATIC or Modifier.FINAL && it.name.startsWith("VK_") }
|
||||
.associate { it.getInt(null) to it.name.substring(3) }
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.gitlab.jfronny.globalmenu.proxy
|
||||
|
||||
import javax.swing.JMenuBar
|
||||
import javax.swing.JMenuItem
|
||||
|
||||
class SwingMenuHolder(bar: JMenuBar, menuName: String): MenuHolder {
|
||||
private val root = SwingRootMenu((0 until bar.menuCount).map { bar.getMenu(it) }, menuName, this)
|
||||
|
||||
override fun find(menuId: Int): Menu? {
|
||||
if (menuId == 0) return root
|
||||
return find(root, menuId)
|
||||
}
|
||||
|
||||
private fun find(parent: Menu, menuId: Int): Menu? {
|
||||
parent.children?.forEach {
|
||||
if (it.id == menuId) return it
|
||||
val found = find(it, menuId)
|
||||
if (found != null) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getId(menuItem: JMenuItem?): Int {
|
||||
return System.identityHashCode(menuItem)
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.gitlab.jfronny.globalmenu.proxy
|
||||
|
||||
import javax.swing.JMenuItem
|
||||
|
||||
class SwingRootMenu(private val menuItems: List<JMenuItem>?, private val name: String, private val holder: SwingMenuHolder): Menu {
|
||||
override val id: Int get() = 0
|
||||
override val isSeparator: Boolean get() = menuItems == null
|
||||
override val label: String get() = name
|
||||
override val isEnabled: Boolean get() = true
|
||||
override val isVisible: Boolean get() = true
|
||||
override val iconData: ByteArray? get() = null
|
||||
override val shortcut: Array<String>? get() = null
|
||||
override val toggleType: String? get() = null
|
||||
override val toggleState: Int get() = 0
|
||||
override val children: List<Menu>? get() = menuItems?.map { SwingMenu(it, holder) }
|
||||
|
||||
override fun onEvent() {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user