Parcourir la source

process indicators improvements

process indicators improvements
Milan Jurkulak il y a 2 ans
Parent
commit
dbfe4c688a

+ 1 - 1
gradle/libs.versions.toml

@@ -8,7 +8,7 @@ appMaintainer = "mj@mjdev.org<Milan Jurkulak>"
 appMenuGroup = "Desktop"
 appVendor = "mjdev"
 packageName = "org.mjdev.desktop"
-packageVersion = "1.0.1"
+packageVersion = "1.0.2"
 #
 gradle = "8.4.0"
 gradle-versions = "0.51.0"

+ 23 - 25
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsBottomBar.kt

@@ -1,16 +1,19 @@
 package eu.mjdev.desktop.components.appsmenu
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.icon.ShapedIcon
 import eu.mjdev.desktop.components.input.SearchField
+import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.icons.Icons
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
@@ -21,33 +24,28 @@ fun AppsBottomBar(
     backButtonVisible: Boolean = false,
     onContextMenuClick: () -> Unit = {},
     onHideMenu: () -> Unit = {},
+    onSearch: (String) -> Unit = {},
     onBackClick: () -> Unit = {}
 ) = withDesktopScope {
-    Box(
+    val textState = rememberState("")
+    Row(
         modifier = modifier.padding(8.dp)
     ) {
-        Row(
-            modifier = Modifier.align(Alignment.BottomStart)
-        ) {
-            if (backButtonVisible) {
-                ShapedIcon(
-                    imageVector = Icons.BackArrow,
-                    iconColor = borderColor,
-                    iconBackgroundColor = iconsTintColor,
-                    onRightClick = onContextMenuClick,
-                    onClick = onBackClick
-                )
-            }
-        }
-        Row(
-            modifier = Modifier.align(Alignment.BottomCenter)
-        ) {
-            SearchField(
-                modifier = Modifier.fillMaxWidth()
+        if (backButtonVisible && textState.value.isEmpty()) {
+            ShapedIcon(
+                imageVector = Icons.BackArrow,
+                iconColor = borderColor,
+                iconBackgroundColor = iconsTintColor,
+                onRightClick = onContextMenuClick,
+                onClick = onBackClick
             )
         }
+//        SearchField(
+//            modifier = Modifier.fillMaxWidth().background(Color.Red),
+//            textState = textState
+//        )
         Row(
-            modifier = Modifier.align(Alignment.BottomEnd)
+            modifier = Modifier.wrapContentWidth()
         ) {
             ShapedIcon(
                 imageVector = Icons.RestartComputer,

+ 3 - 0
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopPanelFavoriteApps.kt

@@ -16,6 +16,8 @@ import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanelIcon
 import eu.mjdev.desktop.data.App
+import eu.mjdev.desktop.extensions.Custom.invalidate
+import eu.mjdev.desktop.managers.ProcessManager.Companion.processManagerListener
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("FunctionName")
@@ -33,6 +35,7 @@ fun DesktopPanelFavoriteApps(
     onContextMenuClick: (app: App) -> Unit = {}
 ) = withDesktopScope {
     val apps = remember(favoriteApps.size) { favoriteApps }
+    processManagerListener { apps.invalidate() }
     Box(
         modifier = modifier
     ) {

+ 10 - 3
src/main/kotlin/eu/mjdev/desktop/components/input/SearchField.kt

@@ -9,10 +9,17 @@
 package eu.mjdev.desktop.components.input
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
 
 @Composable
 fun SearchField(
-    modifier: Modifier = Modifier
-) {
-}
+    modifier: Modifier = Modifier,
+    textState: MutableState<String> = rememberSaveable { mutableStateOf("") },
+) = SelectableOutlineEditText(
+    value = textState.value,
+    onValueChange = { v: String -> textState.value = v },
+    modifier = modifier
+)

+ 9 - 13
src/main/kotlin/eu/mjdev/desktop/data/App.kt

@@ -6,6 +6,8 @@ import eu.mjdev.desktop.extensions.Custom.command
 import eu.mjdev.desktop.extensions.Custom.commandLine
 import eu.mjdev.desktop.helpers.exception.EmptyException.Companion.EmptyException
 import eu.mjdev.desktop.helpers.system.Shell
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import java.io.File
 
 @Suppress("unused", "MemberVisibilityCanBePrivate")
@@ -44,15 +46,6 @@ class App(
     val cmd
         get() = exec.split(" ").first()
 
-    val hasProcess: Boolean = ProcessHandle.allProcesses().toList().any { ph ->
-        ph.command.contentEquals(windowClass, true) ||
-                ph.command.contains(cmd, true) ||
-                ph.command.contains(name, true) ||
-                ph.commandLine.contentEquals(windowClass, true) ||
-                ph.commandLine.contains(cmd, true) ||
-                ph.commandLine.contains(name, true)
-    }
-
     var iconTint: Color? = null
     val iconBackground: Color = Color.White
 
@@ -159,7 +152,6 @@ class App(
         result = 31 * result + runInTerminal.hashCode()
         result = 31 * result + windowClass.hashCode()
         result = 31 * result + cmd.hashCode()
-        result = 31 * result + hasProcess.hashCode()
         result = 31 * result + (iconTint?.hashCode() ?: 0)
         result = 31 * result + iconBackground.hashCode()
         result = 31 * result + isStarting.hashCode()
@@ -181,21 +173,25 @@ class App(
         }
 
         @Composable
-        fun rememberRunningIndicator(app: App?) = remember(
+        fun rememberRunningIndicator(
+            app: App?,
+            api: DesktopProvider = LocalDesktop.current
+        ) = remember(
             app,
             app?.isFavorite,
             app?.isRunning,
             app?.isStarting
         ) {
-            RunningAppIndicator(app)
+            RunningAppIndicator(api, app)
         }
 
         // todo
         class RunningAppIndicator(
+            val api: DesktopProvider,
             val app: App?
         ) : State<Boolean> {
             override val value: Boolean
-                get() = app?.isRunning == true || app?.hasProcess == true
+                get() = app?.isRunning == true || api.processManager.hasAppProcess(app)
         }
     }
 }

+ 160 - 0
src/main/kotlin/eu/mjdev/desktop/managers/ProcessManager.kt

@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.managers
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import eu.mjdev.desktop.data.App
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import kotlinx.coroutines.*
+import kotlin.jvm.optionals.getOrNull
+
+@Suppress("unused")
+class ProcessManager(
+    val delay: Long = 250L
+) : ArrayList<ProcessManager.ProcessWrapper>(), AutoCloseable {
+    private val scope = CoroutineScope(Dispatchers.IO)
+    private val listeners = mutableListOf<ProcessListener>()
+    private var job: Job? = null
+
+    init {
+        job = scope.launch {
+            while (isActive) {
+                ProcessHandle.allProcesses().toList().let { phList ->
+                    phList.forEach { ph ->
+                        if (!containsProcess(ph)) {
+                            ProcessWrapper(ph).also { pw ->
+                                add(pw)
+                            }
+                            listeners.filterIsInstance<ProcessAddListener>().forEach { l ->
+                                l.onProcessAdded(ph)
+                            }
+                        }
+                    }
+                    cleanup()
+                }
+                delay(delay)
+            }
+        }
+    }
+
+    private fun cleanup() {
+        iterator().apply {
+            while (hasNext()) {
+                val pw = next()
+                if (!pw.processHandle.isAlive) {
+                    remove()
+                    listeners.filterIsInstance<ProcessRemoveListener>().forEach { l ->
+                        l.onProcessRemoved(pw.processHandle)
+                    }
+                }
+            }
+        }
+
+    }
+
+    fun addOnAddListener(listener: ProcessAddListener) {
+        listeners.add(listener)
+    }
+
+    fun addOnRemoveListener(listener: ProcessRemoveListener) {
+        listeners.add(listener)
+    }
+
+    fun removeOnAddListener(listener: ProcessAddListener) {
+        listeners.remove(listener)
+    }
+
+    fun removeOnRemoveListener(listener: ProcessRemoveListener) {
+        listeners.remove(listener)
+    }
+
+    override fun close() {
+        job?.cancel()
+    }
+
+    fun hasAppProcess(app: App?): Boolean = if (app == null) false
+    else {
+        val appCmd = app.cmd
+        val appName = app.name
+        val appFullName = app.fullAppName
+        any { ph ->
+            ph.command.contains(appName) ||
+                    ph.command.contains(appCmd) ||
+                    ph.command.contains(appFullName) ||
+                    ph.commandLine.contains(appName) ||
+                    ph.commandLine.contains(appCmd) ||
+                    ph.commandLine.contains(appFullName)
+
+        }
+    }
+
+    companion object {
+        fun ArrayList<ProcessWrapper>.containsProcess(ph: ProcessHandle) = ph.pid().let { pid ->
+            any { p -> p.pid == pid }
+        }
+
+        fun ArrayList<ProcessWrapper>.containsProcess(ph: ProcessWrapper) = any { p -> p.pid == ph.pid }
+
+        @Composable
+        fun processManagerListener(
+            onChanged: ProcessManager.(processHandle: ProcessHandle) -> Unit,
+        ) = processManagerListener(onChanged, onChanged)
+
+        @Composable
+        fun processManagerListener(
+            onAdd: ProcessManager.(processHandle: ProcessHandle) -> Unit,
+            onRemove: ProcessManager.(processHandle: ProcessHandle) -> Unit,
+            api: DesktopProvider = LocalDesktop.current,
+            processManger: ProcessManager = api.processManager
+        ) {
+            val addListener = object : ProcessAddListener {
+                override fun onProcessAdded(processHandle: ProcessHandle) {
+                    onAdd.invoke(processManger, processHandle)
+                }
+            }
+            val removeListener = object : ProcessRemoveListener {
+                override fun onProcessRemoved(processHandle: ProcessHandle) {
+                    onRemove.invoke(processManger, processHandle)
+                }
+            }
+            DisposableEffect(Unit) {
+                processManger.addOnAddListener(addListener)
+                processManger.addOnRemoveListener(removeListener)
+                onDispose {
+                    processManger.removeOnAddListener(addListener)
+                    processManger.removeOnRemoveListener(removeListener)
+                }
+            }
+        }
+    }
+
+    interface ProcessListener
+
+    interface ProcessAddListener : ProcessListener {
+        fun onProcessAdded(processHandle: ProcessHandle)
+    }
+
+    interface ProcessRemoveListener : ProcessListener {
+        fun onProcessRemoved(processHandle: ProcessHandle)
+    }
+
+    class ProcessWrapper(
+        val processHandle: ProcessHandle,
+        val pid: Long = processHandle.pid(),
+        val info: ProcessHandle.Info? = processHandle.info(),
+        val command: String = info?.command()?.getOrNull().orEmpty(),
+        val commandLine: String = info?.commandLine()?.getOrNull().orEmpty()
+    ) {
+        override fun toString(): String {
+            return "$pid | $command | $commandLine"
+        }
+    }
+}

+ 3 - 3
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -59,9 +59,8 @@ class DesktopProvider(
 //    }
     val gtkTheme by lazy { GtkThemeHelper(this) }
     val palette by lazy { Palette(this) }
-    val desktopUtils: Desktop by lazy {
-        Desktop.getDesktop()
-    }
+    val desktopUtils: Desktop by lazy { Desktop.getDesktop() }
+    val processManager by lazy { ProcessManager() }
 
     val homeDir
         get() = currentUser.homeDir
@@ -128,6 +127,7 @@ class DesktopProvider(
 //    }
 
     override fun close() {
+        processManager.close()
 //        runCatching {
 //            mounts.dispose()
 //        }