feat: semi-working state

This commit is contained in:
Johannes Frohnmeyer 2024-07-20 22:54:25 +02:00
parent 9a813fbbaf
commit ab513fb0e9
Signed by: Johannes
GPG Key ID: E76429612C2929F4
6 changed files with 165 additions and 29 deletions

View File

@ -1,38 +1,77 @@
package io.gitlab.jfronny.globalmenu package io.gitlab.jfronny.globalmenu
import com.canonical.appmenu.Registrar import com.canonical.appmenu.Registrar
import com.intellij.ide.IdeEventQueue
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.impl.ActionMenu
import com.intellij.openapi.application.Application import com.intellij.openapi.application.Application
import com.intellij.openapi.application.ApplicationActivationListener import com.intellij.openapi.application.ApplicationActivationListener
import com.intellij.openapi.progress.runBlockingCancellable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.wm.IdeFrame import com.intellij.openapi.wm.IdeFrame
import com.intellij.openapi.wm.impl.IdeFrameImpl import com.intellij.openapi.wm.impl.IdeFrameImpl
import com.intellij.openapi.wm.impl.ProjectFrameHelper import com.intellij.openapi.wm.impl.ProjectFrameHelper
import com.intellij.platform.ide.menu.IdeJMenuBar
import io.gitlab.jfronny.globalmenu.proxy.DbusmenuImpl import io.gitlab.jfronny.globalmenu.proxy.DbusmenuImpl
import io.gitlab.jfronny.globalmenu.proxy.SwingMenuHolder import io.gitlab.jfronny.globalmenu.proxy.SwingMenuHolder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import org.freedesktop.dbus.DBusPath import org.freedesktop.dbus.DBusPath
import org.freedesktop.dbus.connections.impl.DBusConnection
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder
import org.freedesktop.dbus.types.UInt32 import org.freedesktop.dbus.types.UInt32
import java.awt.Window
import java.awt.event.KeyEvent
import javax.swing.JMenuBar import javax.swing.JMenuBar
import javax.swing.SwingUtilities
class GlobalMenuService(private val app: Application) : ApplicationActivationListener { class GlobalMenuService(private val app: Application) : ApplicationActivationListener {
override fun applicationActivated(ideFrame: IdeFrame) { override fun applicationActivated(ideFrame: IdeFrame) {
super.applicationActivated(ideFrame) super.applicationActivated(ideFrame)
if (!GlobalMenu.Native.isSupported) return if (!GlobalMenu.Native.isSupported) return
ideFrame.project?.let { project ->
println(ideFrame.javaClass)
if (ideFrame is ProjectFrameHelper) { if (ideFrame is ProjectFrameHelper) {
visualize(ideFrame.rootPane.jMenuBar, ideFrame.rootPane.peer) visualize(ideFrame.rootPane.jMenuBar, ideFrame.rootPane.peer)
} else if (ideFrame is IdeFrameImpl) { } else if (ideFrame is IdeFrameImpl) {
visualize(ideFrame.jMenuBar, ideFrame.peer) visualize(ideFrame.jMenuBar, ideFrame.peer)
} }
GlobalMenu.Log.warn("Activated: ${project.name}")
}
} }
fun visualize(menu: JMenuBar, peer: Peer) { override fun applicationDeactivated(ideFrame: IdeFrame) {
val conn = DBusConnectionBuilder.forSessionBus().build() lastMenu?.let { Disposer.dispose(it) }
}
private var lastMenu: Disposable? = null
private var connection: DBusConnection? = null
fun visualize(menu: JMenuBar, peer: Peer) = app.invokeLater {
lastMenu?.let { Disposer.dispose(it) }
lastMenu = Disposer.newDisposable()
val conn = connection ?: DBusConnectionBuilder.forSessionBus().build()
connection = conn
val menuHolder = SwingMenuHolder(menu, "DBusMenuRoot")
if (menu is IdeJMenuBar) {
menu.addUpdateGlobalMenuRootsListener {
menuHolder.update(menu.rootMenuItems)
}
menu.updateMenuActions(true)
}
// IdeEventQueue.getInstance().addDispatcher({ e ->
// if (e !is KeyEvent) false
// else if (!e.isAltDown) false
// else {
// val src = e.component
// val wndParent = if (src is Window) src else SwingUtilities.windowForComponent(src)
// val eventChar = e.keyChar.uppercaseChar()
//
//
// true
// }
// }, lastMenu!!)
val windowPtr = peer.nativePtr val windowPtr = peer.nativePtr
val menu = DbusmenuImpl(windowPtr, SwingMenuHolder(menu, "DBusMenuRoot")) val menu = DbusmenuImpl(windowPtr, menuHolder)
conn.unExportObject(menu.objectPath) conn.unExportObject(menu.objectPath)
conn.exportObject(menu) conn.exportObject(menu)

View File

@ -2,6 +2,7 @@ package io.gitlab.jfronny.globalmenu.proxy
import com.canonical.* import com.canonical.*
import io.gitlab.jfronny.globalmenu.DPair import io.gitlab.jfronny.globalmenu.DPair
import io.gitlab.jfronny.globalmenu.GlobalMenu
import org.freedesktop.dbus.types.UInt32 import org.freedesktop.dbus.types.UInt32
import org.freedesktop.dbus.types.Variant import org.freedesktop.dbus.types.Variant
@ -65,9 +66,16 @@ class DbusmenuImpl(windowId: Long, private val menuHolder: MenuHolder) : Dbusmen
return properties return properties
} }
override fun Event(id: Int, eventId: String?, data: Variant<*>?, timestamp: UInt32?) { override fun Event(id: Int, eventId: String, data: Variant<*>?, timestamp: UInt32?) {
if ("clicked".endsWith(eventId!!)) { //TODO this seems off GlobalMenu.Log.warn("Event $eventId for menu $id (${menuHolder.find(id)})")
menuHolder.find(id)?.onEvent() try {
when (eventId) {
"clicked" -> menuHolder.find(id)?.onEvent()
"opened" -> menuHolder.find(id)?.update()
}
} catch (e: Exception) {
GlobalMenu.Log.error("Failed to handle event $eventId for menu $id", e)
throw e
} }
} }

View File

@ -12,4 +12,5 @@ interface Menu {
val toggleState: Int val toggleState: Int
val children: List<Menu>? val children: List<Menu>?
fun onEvent() fun onEvent()
fun update()
} }

View File

@ -1,6 +1,11 @@
package io.gitlab.jfronny.globalmenu.proxy package io.gitlab.jfronny.globalmenu.proxy
import com.intellij.openapi.actionSystem.impl.ActionMenu
import com.intellij.openapi.actionSystem.impl.ActionMenuItem
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import io.gitlab.jfronny.globalmenu.GlobalMenu import io.gitlab.jfronny.globalmenu.GlobalMenu
import kotlinx.coroutines.*
import org.apache.commons.io.output.ByteArrayOutputStream import org.apache.commons.io.output.ByteArrayOutputStream
import java.awt.event.ActionEvent import java.awt.event.ActionEvent
import java.awt.event.InputEvent import java.awt.event.InputEvent
@ -13,6 +18,7 @@ import javax.swing.JCheckBoxMenuItem
import javax.swing.JMenu import javax.swing.JMenu
import javax.swing.JMenuItem import javax.swing.JMenuItem
import javax.swing.JRadioButtonMenuItem import javax.swing.JRadioButtonMenuItem
import javax.swing.JSeparator
class SwingMenu(private val menuItem: JMenuItem?, private val holder: SwingMenuHolder) : Menu { class SwingMenu(private val menuItem: JMenuItem?, private val holder: SwingMenuHolder) : Menu {
override val id = holder.getId(menuItem) override val id = holder.getId(menuItem)
@ -64,20 +70,76 @@ class SwingMenu(private val menuItem: JMenuItem?, private val holder: SwingMenuH
} }
override val toggleType: String? get() = when (menuItem) { override val toggleType: String? get() = when (menuItem) {
is ActionMenuItem -> {
//TODO handle action items
if (menuItem.isToggleable) "checkmark"
else null
}
is JRadioButtonMenuItem -> "radio" is JRadioButtonMenuItem -> "radio"
is JCheckBoxMenuItem -> "checkmark" is JCheckBoxMenuItem -> "checkmark"
else -> null else -> null
} }
override val toggleState: Int get() = if (toggleType?.isNotEmpty() == true) if (menuItem!!.isSelected) 1 else 0 else -1 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) { private var _children: List<Menu>? = null
(0 until menuItem.itemCount).map { SwingMenu(menuItem.getItem(it), holder) } override val children: List<Menu>? get() = _children
} else null
override fun onEvent() { override fun onEvent() {
val event = ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, menuItem!!.actionCommand) val event = ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, menuItem!!.actionCommand)
ApplicationManager.getApplication().invokeLater {
for (it in menuItem.actionListeners) it.actionPerformed(event) for (it in menuItem.actionListeners) it.actionPerformed(event)
if (menuItem is JCheckBoxMenuItem) menuItem.isSelected = !menuItem.isSelected }
if (menuItem is JRadioButtonMenuItem) menuItem.isSelected = true menuItem.doClick()
GlobalMenu.Log.warn("Event $event for menu $id (${menuItem.javaClass})")
when (menuItem) {
is JCheckBoxMenuItem -> menuItem.isSelected = !menuItem.isSelected
is JRadioButtonMenuItem -> menuItem.isSelected = true
}
}
override fun update() {
try {
if (menuItem is ActionMenu) {
runBlocking {
launch(Dispatchers.EDT) {
menuItem.removeAll()
menuItem.isSelected = true
menuItem.fillMenu()
syncChildren(2)
}
}
}
} catch (e: Exception) {
GlobalMenu.Log.error("Failed to update menu", e)
throw e
}
}
fun syncChildren(deepness: Int) {
GlobalMenu.Log.warn("Syncing children for $label")
_children = when (menuItem) {
is ActionMenu -> {
val ch = mutableListOf<Menu>()
for (each in menuItem.popupMenu.components) {
if (each == null) continue
if (each is JSeparator) {
ch.add(SwingMenu(null, holder))
continue
}
if (each !is JMenuItem) continue
val cmi = SwingMenu(each, holder)
if (deepness > 1 && each is ActionMenu) {
each.removeAll()
each.isSelected = true
each.fillMenu()
cmi.syncChildren(deepness - 1)
}
ch.add(cmi)
}
ch
}
is JMenu -> (0 until menuItem.itemCount).map { SwingMenu(menuItem.getItem(it), holder) }
else -> null
}
} }
companion object { companion object {

View File

@ -1,5 +1,7 @@
package io.gitlab.jfronny.globalmenu.proxy package io.gitlab.jfronny.globalmenu.proxy
import com.intellij.openapi.actionSystem.impl.ActionMenu
import com.intellij.openapi.actionSystem.impl.ActionMenuItem
import javax.swing.JMenuBar import javax.swing.JMenuBar
import javax.swing.JMenuItem import javax.swing.JMenuItem
@ -12,15 +14,20 @@ class SwingMenuHolder(bar: JMenuBar, menuName: String): MenuHolder {
} }
private fun find(parent: Menu, menuId: Int): Menu? { private fun find(parent: Menu, menuId: Int): Menu? {
parent.children?.forEach { return parent.children?.let { children ->
if (it.id == menuId) return it for (child in children) {
val found = find(it, menuId) if (child.id == menuId) return child
val found = find(child, menuId)
if (found != null) return found if (found != null) return found
} }
return null null
}
} }
fun getId(menuItem: JMenuItem?): Int { fun getId(menuItem: JMenuItem?): Int = when (menuItem) {
return System.identityHashCode(menuItem) is ActionMenu -> menuItem.anAction.toString().hashCode()
is ActionMenuItem -> menuItem.anAction.toString().hashCode()
else -> System.identityHashCode(menuItem)
} }
fun update(items: List<ActionMenu>) = root.update(items)
} }

View File

@ -1,8 +1,9 @@
package io.gitlab.jfronny.globalmenu.proxy package io.gitlab.jfronny.globalmenu.proxy
import com.intellij.openapi.actionSystem.impl.ActionMenu
import javax.swing.JMenuItem import javax.swing.JMenuItem
class SwingRootMenu(private val menuItems: List<JMenuItem>?, private val name: String, private val holder: SwingMenuHolder): Menu { class SwingRootMenu(private var menuItems: List<JMenuItem>?, private val name: String, private val holder: SwingMenuHolder): Menu {
override val id: Int get() = 0 override val id: Int get() = 0
override val isSeparator: Boolean get() = menuItems == null override val isSeparator: Boolean get() = menuItems == null
override val label: String get() = name override val label: String get() = name
@ -12,8 +13,26 @@ class SwingRootMenu(private val menuItems: List<JMenuItem>?, private val name: S
override val shortcut: Array<String>? get() = null override val shortcut: Array<String>? get() = null
override val toggleType: String? get() = null override val toggleType: String? get() = null
override val toggleState: Int get() = 0 override val toggleState: Int get() = 0
override val children: List<Menu>? get() = menuItems?.map { SwingMenu(it, holder) } private var _children: List<Menu>? = null
override val children: List<Menu>? get() = _children
override fun onEvent() { override fun onEvent() {
} }
override fun update() {
syncChildren()
}
fun update(items: List<ActionMenu>) {
menuItems = items
syncChildren()
}
init {
syncChildren()
}
private fun syncChildren() {
_children = menuItems?.map { SwingMenu(it, holder).apply { syncChildren(1) } }
}
} }