Skip to content

Commit 0f5c2d0

Browse files
authored
Merge pull request #2471 from digma-ai/patch/fix-for-switch-screen-corruption
trying to fix JCEF corruption
2 parents d875dab + 0a08770 commit 0f5c2d0

File tree

8 files changed

+331
-7
lines changed

8 files changed

+331
-7
lines changed

ide-common/src/main/java/org/digma/intellij/plugin/ui/ToolWindowShower.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,27 @@ public void showToolWindow() {
6161
}
6262
}
6363
});
64+
}
65+
66+
public void hideToolWindow() {
67+
68+
EDT.ensureEDT(() -> {
69+
Log.log(LOGGER::debug, "hideToolWindow invoked");
70+
71+
if (toolWindow != null) {
72+
Log.log(LOGGER::debug, "Got reference to tool window, hiding..");
73+
hide(toolWindow);
74+
} else {
75+
Log.log(LOGGER::debug, "Don't have reference to tool window, hiding with ToolWindowManager..");
76+
ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(PluginId.TOOL_WINDOW_ID);
77+
if (tw != null) {
78+
Log.log(LOGGER::debug, "Got tool window from ToolWindowManager");
79+
hide(tw);
80+
} else {
81+
Log.log(LOGGER::debug, "Could not find tool window");
82+
}
83+
}
84+
});
6485

6586
}
6687

@@ -74,4 +95,13 @@ private void show(ToolWindow toolWindow) {
7495
toolWindow.show();
7596
}
7697
}
98+
99+
private void hide(ToolWindow toolWindow) {
100+
if (!toolWindow.isVisible()) {
101+
Log.log(LOGGER::debug, "Tool window is already hidden");
102+
} else {
103+
Log.log(LOGGER::debug, "Calling toolWindow.show");
104+
toolWindow.hide();
105+
}
106+
}
77107
}

ide-common/src/main/kotlin/org/digma/intellij/plugin/recentactivity/RecentActivityToolWindowShower.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.digma.intellij.plugin.recentactivity
22

33
import com.intellij.openapi.components.Service
4+
import com.intellij.openapi.components.service
45
import com.intellij.openapi.diagnostic.Logger
56
import com.intellij.openapi.project.Project
67
import com.intellij.openapi.wm.ToolWindow
@@ -16,6 +17,23 @@ class RecentActivityToolWindowShower(val project: Project) {
1617

1718
var toolWindow: ToolWindow? = null
1819

20+
21+
companion object {
22+
@JvmStatic
23+
fun getInstance(project: Project): RecentActivityToolWindowShower {
24+
return project.service<RecentActivityToolWindowShower>()
25+
}
26+
}
27+
28+
29+
fun isToolWindowVisible(): Boolean {
30+
if (toolWindow != null) {
31+
return toolWindow!!.isVisible
32+
}
33+
return false
34+
}
35+
36+
1937
fun showToolWindow() {
2038
Log.log(logger::trace, "showToolWindow invoked")
2139

@@ -38,6 +56,28 @@ class RecentActivityToolWindowShower(val project: Project) {
3856
}
3957
}
4058

59+
fun hideToolWindow() {
60+
Log.log(logger::trace, "hideToolWindow invoked")
61+
62+
EDT.ensureEDT {
63+
if (toolWindow != null) {
64+
toolWindow?.let {
65+
Log.log(logger::trace, "Got reference to tool window, hiding..")
66+
hide(it)
67+
}
68+
69+
} else {
70+
Log.log(logger::trace, "Don't have reference to tool window, hiding with ToolWindowManager..")
71+
val tw: ToolWindow? = ToolWindowManager.getInstance(project).getToolWindow(PluginId.OBSERVABILITY_WINDOW_ID)
72+
73+
tw?.let {
74+
Log.log(logger::trace, "Got tool window from ToolWindowManager")
75+
hide(tw)
76+
} ?: Log.log(logger::trace, "Could not find tool window")
77+
}
78+
}
79+
}
80+
4181

4282
private fun show(toolWindow: ToolWindow) {
4383
if (toolWindow.isVisible) {
@@ -48,4 +88,13 @@ class RecentActivityToolWindowShower(val project: Project) {
4888
}
4989
}
5090

91+
private fun hide(toolWindow: ToolWindow) {
92+
if (!toolWindow.isVisible) {
93+
Log.log(logger::trace, "Tool window is already hidden")
94+
} else {
95+
Log.log(logger::trace, "Calling toolWindow.show")
96+
toolWindow.hide()
97+
}
98+
}
99+
51100
}

ide-common/src/main/kotlin/org/digma/intellij/plugin/reload/ReloadAction.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import org.digma.intellij.plugin.errorreporting.ErrorReporter
1010
class ReloadAction : AnAction() {
1111
override fun actionPerformed(p0: AnActionEvent) {
1212
try {
13-
service<ReloadService>().reload()
13+
service<ReloadService>().reload(0)
1414
} catch (e: Throwable) {
1515
ErrorReporter.getInstance().reportError("ReloadAction.actionPerformed", e)
1616
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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+
}

ide-common/src/main/kotlin/org/digma/intellij/plugin/reload/ReloadService.kt

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,34 @@ package org.digma.intellij.plugin.reload
22

33
import com.intellij.openapi.Disposable
44
import com.intellij.openapi.components.Service
5+
import com.intellij.openapi.diagnostic.Logger
56
import com.intellij.openapi.project.ProjectManager
67
import com.intellij.openapi.util.Disposer
8+
import com.intellij.util.Alarm
9+
import org.digma.intellij.plugin.common.DisposableAdaptor
710
import org.digma.intellij.plugin.common.EDT
811
import org.digma.intellij.plugin.common.isProjectValid
912
import org.digma.intellij.plugin.errorreporting.ErrorReporter
13+
import org.digma.intellij.plugin.log.Log
14+
import org.digma.intellij.plugin.recentactivity.RecentActivityToolWindowShower
1015
import org.digma.intellij.plugin.ui.MainToolWindowCardsController
16+
import org.digma.intellij.plugin.ui.ToolWindowShower
1117
import org.digma.intellij.plugin.ui.panels.ReloadablePanel
18+
import java.util.concurrent.locks.ReentrantLock
19+
import kotlin.concurrent.withLock
1220

1321
@Service(Service.Level.APP)
14-
class ReloadService {
22+
class ReloadService : DisposableAdaptor {
23+
24+
private val logger = Logger.getInstance(ReloadService::class.java)
1525

1626
private val reloadables = mutableListOf<ReloadablePanel>()
1727

28+
private val myReloadAlarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this)
29+
30+
private val myReloadLock = ReentrantLock(true)
31+
private val myEDTReloadLock = ReentrantLock(true)
32+
1833

1934
fun register(reloadablePanel: ReloadablePanel, parentDisposable: Disposable) {
2035
reloadables.add(reloadablePanel)
@@ -28,7 +43,19 @@ class ReloadService {
2843
}
2944

3045

31-
fun reload() {
46+
fun reload(delay: Long = 3000) {
47+
myReloadLock.withLock {
48+
//the delay is to prevent multiple reloads in case multiple events
49+
// arrive at the same time
50+
myReloadAlarm.cancelAllRequests()
51+
myReloadAlarm.addRequest({
52+
reloadImpl()
53+
}, delay)
54+
}
55+
}
56+
57+
private fun reloadImpl() {
58+
Log.log(logger::trace, "Reloading...")
3259
ProjectManager.getInstance().openProjects.forEach {
3360
try {
3461
if (isProjectValid(it)) {
@@ -43,13 +70,36 @@ class ReloadService {
4370
reloadables.forEach {
4471
EDT.ensureEDT {
4572
try {
46-
it.reload()
73+
myEDTReloadLock.withLock {
74+
Log.log(logger::trace, "Reloading {} for project {}", it::class.simpleName, it.getProject().name)
75+
it.reload()
76+
}
4777
} catch (e: Throwable) {
4878
ErrorReporter.getInstance().reportError("ReloadService.reload", e)
4979
}
5080
}
5181
}
52-
}
5382

83+
//without hiding and showing tool window the jcef doesn't always refresh on macOS
84+
ProjectManager.getInstance().openProjects.forEach {
85+
try {
86+
if (isProjectValid(it)) {
87+
EDT.ensureEDT {
88+
Log.log(logger::trace, "Reloading tool windows for project {}...", it.name)
89+
if (ToolWindowShower.getInstance(it).isToolWindowVisible()) {
90+
ToolWindowShower.getInstance(it).hideToolWindow()
91+
ToolWindowShower.getInstance(it).showToolWindow()
92+
}
93+
if (RecentActivityToolWindowShower.getInstance(it).isToolWindowVisible()) {
94+
RecentActivityToolWindowShower.getInstance(it).hideToolWindow()
95+
RecentActivityToolWindowShower.getInstance(it).showToolWindow()
96+
}
97+
}
98+
}
99+
} catch (e: Throwable) {
100+
ErrorReporter.getInstance().reportError("ReloadService.reload", e)
101+
}
102+
}
103+
}
54104

55105
}

0 commit comments

Comments
 (0)