diff --git a/platform/platform-api/resources/messages/IdeBundle.properties b/platform/platform-api/resources/messages/IdeBundle.properties index 2211b7adba48..42220380e4e9 100644 --- a/platform/platform-api/resources/messages/IdeBundle.properties +++ b/platform/platform-api/resources/messages/IdeBundle.properties @@ -3261,3 +3261,6 @@ folder.open.processor.name=Folder code.style.cache.dump.success=Code style cache dump successful code.style.cache.dump.failed=Code style cache dump failed + +notification.group.wayland=Wayland +notification.wayland=Wayland native mode is enabled by default in the IDE. For more details see this blog post diff --git a/platform/platform-impl/src/com/intellij/ide/ui/WaylandNotification.kt b/platform/platform-impl/src/com/intellij/ide/ui/WaylandNotification.kt new file mode 100644 index 000000000000..44a780521eed --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/WaylandNotification.kt @@ -0,0 +1,115 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ide.ui + +import com.intellij.codeInsight.editorActions.TypedHandlerDelegate +import com.intellij.ide.IdeBundle +import com.intellij.ide.util.PropertiesComponent +import com.intellij.idea.AppMode +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.event.EditorMouseEvent +import com.intellij.openapi.editor.event.EditorMouseListener +import com.intellij.openapi.editor.event.EditorMouseMotionListener +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.registry.Registry +import com.intellij.psi.PsiFile +import com.intellij.util.ui.StartupUiUtil +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.TimeSource + +internal class WaylandUserActivityTracker : TypedHandlerDelegate(), EditorMouseListener, EditorMouseMotionListener { + override fun mouseClicked(event: EditorMouseEvent) { + userActivityHappened(event.editor.project) + } + + override fun mouseMoved(e: EditorMouseEvent) { + userActivityHappened(e.editor.project) + } + + override fun charTyped( + c: Char, + project: Project, + editor: Editor, + file: PsiFile, + ): Result { + userActivityHappened(project) + return Result.CONTINUE + } + + private fun userActivityHappened(project: Project?) { + if (!StartupUiUtil.isWaylandToolkit()) return + if (AppMode.isRemoteDevHost()) return // these notification are only for the frontend or the monolith + // The state is extracted into a separate service because this helper class can have several instances + // (working in different roles: a mouse listener, a typing listener...). + WaylandNotificationService.getInstance().userActivityHappened(project) + } +} + +@Service(Service.Level.APP) +internal class WaylandNotificationService { + private var firstActivityTime: TimeSource.Monotonic.ValueTimeMark? = null + private var previousActivityTime: TimeSource.Monotonic.ValueTimeMark? = null + private var notificationAlreadyShown: Boolean? = null + + companion object { + @JvmStatic fun getInstance(): WaylandNotificationService = service() + } + + fun userActivityHappened(project: Project?) { + if (isNotificationAlreadyShown()) return + + val currentActivityTime = TimeSource.Monotonic.markNow() + + var firstActivityTime = firstActivityTime + if (firstActivityTime == null) { + firstActivityTime = currentActivityTime + this.firstActivityTime = firstActivityTime + } + + val previousActivityTime = previousActivityTime + if (Registry.getInstance().isLoaded && previousActivityTime != null) { + val sinceFirstActivity = currentActivityTime - firstActivityTime + val sincePreviousActivity = currentActivityTime - previousActivityTime + if ( + sinceFirstActivity >= delaySinceFirstActivity() && + sincePreviousActivity >= delaySincePreviousActivity() + ) { + showNotification(project) + setNotificationAlreadyShown() + } + } + + this.previousActivityTime = currentActivityTime + } + + private fun isNotificationAlreadyShown(): Boolean { + var notificationAlreadyShown = notificationAlreadyShown + if (notificationAlreadyShown != null) return notificationAlreadyShown + notificationAlreadyShown = PropertiesComponent.getInstance().getBoolean(WAYLAND_NOTIFICATION_ALREADY_SHOWN, false) + this.notificationAlreadyShown = notificationAlreadyShown + return notificationAlreadyShown + } + + private fun setNotificationAlreadyShown() { + this.notificationAlreadyShown = true + PropertiesComponent.getInstance().setValue(WAYLAND_NOTIFICATION_ALREADY_SHOWN, true) + } +} + +private fun delaySinceFirstActivity(): Duration = Registry.intValue("wayland.notification.delay.after.first.activity", defaultValue = 20).minutes + +private fun delaySincePreviousActivity(): Duration = Registry.intValue("wayland.notification.delay.after.previous.activity", defaultValue = 1).minutes + +private fun showNotification(project: Project?) { + Notification( + "Wayland", + IdeBundle.message("notification.wayland", "https://jetbrains.com/TODO-blog-post"), // TODO + NotificationType.INFORMATION + ).notify(project) +} + +private const val WAYLAND_NOTIFICATION_ALREADY_SHOWN = "WAYLAND_NOTIFICATION_ALREADY_SHOWN" diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 34c480322a89..7ac9c6817a86 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -2051,6 +2051,16 @@ + + + + + + + +