ProcessManager.kt 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /*
  2. * Copyright (c) Milan Jurkulák 2024.
  3. * Contact:
  4. * e: mimoccc@gmail.com
  5. * e: mj@mjdev.org
  6. * w: https://mjdev.org
  7. */
  8. package eu.mjdev.desktop.managers
  9. import androidx.compose.runtime.Composable
  10. import androidx.compose.runtime.DisposableEffect
  11. import eu.mjdev.desktop.data.App
  12. import eu.mjdev.desktop.extensions.Custom.orElse
  13. import eu.mjdev.desktop.provider.DesktopProvider
  14. import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
  15. import kotlinx.coroutines.*
  16. import kotlin.jvm.optionals.getOrNull
  17. @Suppress("unused")
  18. class ProcessManager(
  19. val delay: Long = 1000L
  20. ) : ArrayList<ProcessManager.ProcessWrapper>(), AutoCloseable {
  21. private val scope = CoroutineScope(Dispatchers.IO)
  22. private val listeners = mutableListOf<ProcessListener>()
  23. private var job: Job? = null
  24. init {
  25. job = scope.launch {
  26. while (isActive) {
  27. ProcessHandle.allProcesses().toList().filterNotNull().let { phList ->
  28. phList.forEach { ph ->
  29. if (!containsProcess(ph)) {
  30. ProcessWrapper(ph).also { pw ->
  31. add(pw)
  32. }
  33. listeners.filterIsInstance<ProcessAddListener>().forEach { l ->
  34. l.onProcessAdded(ph)
  35. }
  36. }
  37. }
  38. cleanup()
  39. }
  40. delay(delay)
  41. }
  42. }
  43. }
  44. private fun cleanup() {
  45. iterator().apply {
  46. while (hasNext()) {
  47. val pw = next()
  48. if (pw.processHandle?.isAlive != true) {
  49. remove()
  50. listeners.filterIsInstance<ProcessRemoveListener>().forEach { l ->
  51. l.onProcessRemoved(pw.processHandle)
  52. }
  53. }
  54. }
  55. }
  56. }
  57. fun addOnAddListener(listener: ProcessAddListener) {
  58. listeners.add(listener)
  59. }
  60. fun addOnRemoveListener(listener: ProcessRemoveListener) {
  61. listeners.add(listener)
  62. }
  63. fun removeOnAddListener(listener: ProcessAddListener) {
  64. listeners.remove(listener)
  65. }
  66. fun removeOnRemoveListener(listener: ProcessRemoveListener) {
  67. listeners.remove(listener)
  68. }
  69. override fun close() {
  70. job?.cancel()
  71. }
  72. // todo : something raises error
  73. fun hasAppProcess(app: App?): Boolean = runCatching {
  74. val appCmd = app?.cmd
  75. val appName = app?.name
  76. val appFullName = app?.fullAppName
  77. any { pw ->
  78. (appName != null && pw.command.contains(appName)) ||
  79. (appCmd != null && pw.command.contains(appCmd)) ||
  80. (appFullName != null && pw.command.contains(appFullName)) ||
  81. (appName != null && pw.commandLine.contains(appName)) ||
  82. (appCmd != null && pw.commandLine.contains(appCmd)) ||
  83. (appFullName != null && pw.commandLine.contains(appFullName))
  84. }
  85. }.getOrNull() ?: false
  86. companion object {
  87. fun ArrayList<ProcessWrapper>.containsProcess(ph: ProcessHandle) = ph.pid().let { pid ->
  88. any { p -> p.pid == pid }
  89. }
  90. fun ArrayList<ProcessWrapper>.containsProcess(ph: ProcessWrapper) = any { p -> p.pid == ph.pid }
  91. @Composable
  92. fun processManagerListener(
  93. onChanged: ProcessManager.(processHandle: ProcessHandle?) -> Unit,
  94. ) = processManagerListener(onChanged, onChanged)
  95. @Composable
  96. fun processManagerListener(
  97. onAdd: ProcessManager.(processHandle: ProcessHandle?) -> Unit,
  98. onRemove: ProcessManager.(processHandle: ProcessHandle?) -> Unit,
  99. api: DesktopProvider = LocalDesktop.current,
  100. processManger: ProcessManager = api.processManager
  101. ) {
  102. val addListener = object : ProcessAddListener {
  103. override fun onProcessAdded(processHandle: ProcessHandle?) {
  104. onAdd.invoke(processManger, processHandle)
  105. }
  106. }
  107. val removeListener = object : ProcessRemoveListener {
  108. override fun onProcessRemoved(processHandle: ProcessHandle?) {
  109. onRemove.invoke(processManger, processHandle)
  110. }
  111. }
  112. DisposableEffect(Unit) {
  113. processManger.addOnAddListener(addListener)
  114. processManger.addOnRemoveListener(removeListener)
  115. onDispose {
  116. processManger.removeOnAddListener(addListener)
  117. processManger.removeOnRemoveListener(removeListener)
  118. }
  119. }
  120. }
  121. }
  122. interface ProcessListener
  123. interface ProcessAddListener : ProcessListener {
  124. fun onProcessAdded(processHandle: ProcessHandle?)
  125. }
  126. interface ProcessRemoveListener : ProcessListener {
  127. fun onProcessRemoved(processHandle: ProcessHandle?)
  128. }
  129. class ProcessWrapper(
  130. val processHandle: ProcessHandle?,
  131. val pid: Long = processHandle?.pid().orElse { -1L },
  132. val info: ProcessHandle.Info? = processHandle?.info(),
  133. val command: String = info?.command()?.getOrNull().orEmpty(),
  134. val commandLine: String = info?.commandLine()?.getOrNull().orEmpty()
  135. ) {
  136. override fun toString(): String {
  137. return "$pid | $command | $commandLine"
  138. }
  139. }
  140. }