1+ package org.digma.intellij.plugin.reload
2+
3+ import com.intellij.openapi.Disposable
4+ import com.intellij.openapi.components.Service
5+ import com.intellij.openapi.components.service
6+ import com.intellij.openapi.diagnostic.Logger
7+ import com.intellij.openapi.util.Disposer
8+ import com.intellij.openapi.util.SystemInfo
9+ import kotlinx.coroutines.CancellationException
10+ import kotlinx.coroutines.CoroutineScope
11+ import kotlinx.coroutines.delay
12+ import kotlinx.coroutines.isActive
13+ import kotlinx.coroutines.launch
14+ import org.digma.intellij.plugin.errorreporting.ErrorReporter
15+ import org.digma.intellij.plugin.log.Log
16+ import java.awt.Component
17+ import java.awt.GraphicsEnvironment
18+ import java.beans.PropertyChangeEvent
19+ import java.beans.PropertyChangeListener
20+ import java.util.Queue
21+ import java.util.concurrent.ConcurrentLinkedQueue
22+ import javax.swing.JComponent
23+ import javax.swing.JPanel
24+
25+
26+ /* *
27+ * Observes property change events on jcef components and decides if to reload the jcef apps.
28+ * will do nothing if it's not macOS
29+ */
30+ @Service(Service .Level .APP )
31+ class ReloadObserver (cs : CoroutineScope ) {
32+
33+ private val logger = Logger .getInstance(ReloadObserver ::class .java)
34+
35+ private val propertyChangedEvents: Queue <Pair <ComponentDetails , PropertyChangeEvent >> = ConcurrentLinkedQueue ()
36+
37+ init {
38+
39+ if (SystemInfo .isMac) {
40+ // a long-running coroutine that processes the events in the order they arrive
41+ cs.launch {
42+ while (isActive) {
43+ try {
44+ val event = propertyChangedEvents.poll()
45+ if (event == null ) {
46+ delay(2000 )
47+ } else {
48+ checkChangesAndReload(event)
49+ }
50+ } catch (ce: CancellationException ) {
51+ throw ce
52+ } catch (e: Throwable ) {
53+ ErrorReporter .getInstance().reportError(" ReloadObserver.mainLoop" , e)
54+ }
55+ }
56+ }
57+ }
58+ }
59+
60+
61+ fun register (jcefWrapperPanel : JPanel , jcefUiComponent : JComponent , parentDisposable : Disposable ) {
62+
63+ if (GraphicsEnvironment .isHeadless()) {
64+ Log .log(logger::trace, " GraphicsEnvironment is headless, not registering components" )
65+ return
66+ }
67+
68+ if (! SystemInfo .isMac) {
69+ Log .log(logger::trace, " system is not mac, not registering components" )
70+ return
71+ }
72+
73+ val jcefPropertyChangeListener =
74+ MyPropertyChangeListener (jcefUiComponent, " ${jcefWrapperPanel.javaClass.simpleName} .jcefUiComponent" )
75+ jcefUiComponent.addPropertyChangeListener(jcefPropertyChangeListener)
76+
77+ Disposer .register(parentDisposable) {
78+ jcefUiComponent.removePropertyChangeListener(jcefPropertyChangeListener)
79+ }
80+
81+ }
82+
83+
84+ private suspend fun checkChangesAndReload (event : Pair <ComponentDetails , PropertyChangeEvent >) {
85+ try {
86+
87+ Log .log(logger::trace, " checking graphics changes for component {}" , event.first.componentName)
88+
89+ val componentDetails = event.first
90+ val component = componentDetails.component
91+
92+ val currentDisplayMode = component.graphicsConfiguration?.device?.displayMode
93+ val currentGraphicsDevice = component.graphicsConfiguration?.device?.iDstring
94+ if (currentGraphicsDevice != componentDetails.graphicDevice) {
95+ Log .log(
96+ logger::trace,
97+ " component {} moved to another graphics device, oldValue:{},newValue:{}" ,
98+ componentDetails.componentName,
99+ componentDetails.graphicDevice,
100+ currentGraphicsDevice
101+ )
102+
103+ componentDetails.graphicDevice = currentGraphicsDevice
104+ componentDetails.displayMode = currentDisplayMode
105+
106+ delay(1000 )
107+
108+ val currentGraphicsDeviceNumber = GraphicsEnvironment .getLocalGraphicsEnvironment().screenDevices.size
109+ if (currentGraphicsDeviceNumber != componentDetails.graphicsDeviceNumber) {
110+ Log .log(
111+ logger::trace,
112+ " graphics device number has changed for component {} ,oldValue:{},newValue:{}" ,
113+ componentDetails.componentName,
114+ componentDetails.graphicsDeviceNumber,
115+ currentGraphicsDeviceNumber
116+ )
117+ componentDetails.graphicsDeviceNumber = currentGraphicsDeviceNumber
118+
119+ service<ReloadService >().reload()
120+ }
121+ } else if (currentDisplayMode != componentDetails.displayMode) {
122+ Log .log(
123+ logger::trace,
124+ " component {} display mode has changed, oldValue:{},newValue:{}" ,
125+ componentDetails.componentName,
126+ componentDetails.displayMode,
127+ currentDisplayMode
128+ )
129+ componentDetails.displayMode = currentDisplayMode
130+
131+ service<ReloadService >().reload()
132+ } else {
133+ Log .log(logger::trace, " no graphics changes for component {}" , componentDetails.componentName)
134+ }
135+
136+ } catch (e: Throwable ) {
137+ ErrorReporter .getInstance().reportError(" ReloadObserver.checkChangesAndReload" , e)
138+ }
139+ }
140+
141+
142+ private class ComponentDetails (val component : Component , val componentName : String ) {
143+ var graphicDevice = component.graphicsConfiguration?.device?.iDstring
144+ var displayMode = component.graphicsConfiguration?.device?.displayMode
145+ var graphicsDeviceNumber = GraphicsEnvironment .getLocalGraphicsEnvironment().screenDevices.size
146+ }
147+
148+
149+ private inner class MyPropertyChangeListener (
150+ component : Component ,
151+ private val componentName : String
152+ ) :
153+ PropertyChangeListener {
154+
155+ private val componentDetails = ComponentDetails (component, componentName)
156+
157+ override fun propertyChange (evt : PropertyChangeEvent ) {
158+ if (evt.propertyName == " graphicsConfiguration" ) {
159+ Log .log(logger::trace, " got PropertyChangeEvent for {}, {}" , componentName, evt)
160+ // putting the events in a queue ensures we process them in the order they arrive.
161+ // just launching a coroutine here does not guarantee order and may cause wrong decisions
162+ // about reloading
163+ propertyChangedEvents.add(Pair (componentDetails, evt))
164+ }
165+ }
166+ }
167+
168+ }
0 commit comments