App.kt 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package eu.mjdev.desktop.data
  2. import androidx.compose.runtime.*
  3. import androidx.compose.ui.graphics.Color
  4. import eu.mjdev.desktop.extensions.Custom.command
  5. import eu.mjdev.desktop.extensions.Custom.commandLine
  6. import eu.mjdev.desktop.helpers.exception.EmptyException.Companion.EmptyException
  7. import eu.mjdev.desktop.helpers.system.Shell
  8. import eu.mjdev.desktop.provider.DesktopProvider
  9. import java.io.File
  10. @Suppress("unused", "MemberVisibilityCanBePrivate")
  11. class App(
  12. val file: File? = null,
  13. val desktopFile: DesktopFile? = runCatching { file?.let { f -> DesktopFile(f) } }.getOrNull(),
  14. val isFavorite: Boolean = false
  15. ) {
  16. val fileName
  17. get() = desktopFile?.fileName
  18. val fullAppName
  19. get() = desktopFile?.fullAppName ?: ""
  20. val type
  21. get() = desktopFile?.desktopSection?.Type ?: ""
  22. val version
  23. get() = desktopFile?.desktopSection?.Version ?: ""
  24. val name
  25. get() = desktopFile?.desktopSection?.Name ?: ""
  26. val comment
  27. get() = desktopFile?.desktopSection?.Comment ?: ""
  28. val path
  29. get() = desktopFile?.desktopSection?.Path ?: ""
  30. val exec
  31. get() = desktopFile?.desktopSection?.Exec ?: ""
  32. val iconName
  33. get() = desktopFile?.desktopSection?.Icon ?: ""
  34. val categories
  35. get() = desktopFile?.desktopSection?.Categories.ifEmptyCategories { listOf(Category.UNCATEGORIZED) }
  36. val notifyOnStart
  37. get() = desktopFile?.desktopSection?.NotifyOnStart ?: false
  38. val runInTerminal
  39. get() = desktopFile?.desktopSection?.RunInTerminal ?: false
  40. val windowClass
  41. get() = desktopFile?.desktopSection?.StartupWMClass.orEmpty().ifEmpty { fullAppName }
  42. val cmd
  43. get() = exec.split(" ").first()
  44. val hasProcess: Boolean = ProcessHandle.allProcesses().toList().any { ph ->
  45. ph.command.contentEquals(windowClass, true) ||
  46. ph.command.contains(cmd, true) ||
  47. ph.command.contains(name, true) ||
  48. ph.commandLine.contentEquals(windowClass, true) ||
  49. ph.commandLine.contains(cmd, true) ||
  50. ph.commandLine.contains(name, true)
  51. }
  52. var iconTint: Color? = null
  53. val iconBackground: Color = Color.White
  54. val onStopHandler: MutableState<(Throwable) -> Unit> = mutableStateOf({})
  55. val onStartingHandler: MutableState<() -> Unit> = mutableStateOf({})
  56. val onStartedHandler: MutableState<() -> Unit> = mutableStateOf({})
  57. var isStartingState = mutableStateOf(false)
  58. var isRunningState = mutableStateOf(false)
  59. var isStarting: Boolean
  60. get() = isStartingState.value
  61. internal set(value) {
  62. isStartingState.value = value
  63. }
  64. var isRunning: Boolean
  65. get() = isStartingState.value
  66. internal set(value) {
  67. isStartingState.value = value
  68. }
  69. fun start() = runCatching {
  70. println("Starting app: $name [$windowClass].")
  71. println("dex -w ${desktopFile?.absolutePath}")
  72. triggerStart()
  73. Shell {
  74. startApp(
  75. app = this@App,
  76. onStarted = {
  77. println("App started app: $name [$windowClass].")
  78. triggerStarted()
  79. },
  80. onStopped = { e ->
  81. println("App stopped: $name [$windowClass].")
  82. triggerStop(e)
  83. }
  84. )
  85. }
  86. }.onFailure { e ->
  87. triggerStop(e)
  88. }
  89. fun triggerStart() {
  90. isStarting = true
  91. onStartingHandler.value.invoke()
  92. }
  93. fun triggerStarted() {
  94. isStarting = false
  95. isRunning = true
  96. onStartedHandler.value.invoke()
  97. }
  98. fun triggerStop() = triggerStop(null)
  99. fun triggerStop(result: Throwable?) {
  100. isStarting = false
  101. isRunning = false
  102. onStopHandler.value.invoke(result ?: EmptyException)
  103. }
  104. fun onStop(function: (Throwable) -> Unit): App {
  105. onStopHandler.value = function
  106. return this
  107. }
  108. fun onStarting(function: () -> Unit): App {
  109. onStartingHandler.value = function
  110. return this
  111. }
  112. fun onStarted(function: () -> Unit): App {
  113. onStartedHandler.value = function
  114. return this
  115. }
  116. override fun equals(other: Any?): Boolean {
  117. return when {
  118. other is App -> {
  119. other.file?.name?.contentEquals(file?.name) ?: false
  120. }
  121. else -> false
  122. }
  123. }
  124. fun hasWindow(api: DesktopProvider): Boolean =
  125. api.windowsManager.hasWindow(this)
  126. override fun hashCode(): Int {
  127. var result = file?.hashCode() ?: 0
  128. result = 31 * result + (desktopFile?.hashCode() ?: 0)
  129. result = 31 * result + isFavorite.hashCode()
  130. result = 31 * result + (fileName?.hashCode() ?: 0)
  131. result = 31 * result + fullAppName.hashCode()
  132. result = 31 * result + type.hashCode()
  133. result = 31 * result + version.hashCode()
  134. result = 31 * result + name.hashCode()
  135. result = 31 * result + comment.hashCode()
  136. result = 31 * result + path.hashCode()
  137. result = 31 * result + exec.hashCode()
  138. result = 31 * result + iconName.hashCode()
  139. result = 31 * result + categories.hashCode()
  140. result = 31 * result + notifyOnStart.hashCode()
  141. result = 31 * result + runInTerminal.hashCode()
  142. result = 31 * result + windowClass.hashCode()
  143. result = 31 * result + cmd.hashCode()
  144. result = 31 * result + hasProcess.hashCode()
  145. result = 31 * result + (iconTint?.hashCode() ?: 0)
  146. result = 31 * result + iconBackground.hashCode()
  147. result = 31 * result + isStarting.hashCode()
  148. result = 31 * result + isRunning.hashCode()
  149. return result
  150. }
  151. companion object {
  152. val Empty: App = App()
  153. fun List<String>?.ifEmptyCategories(block: () -> List<String>) = this?.filter { s ->
  154. s.isNotEmpty()
  155. }.let { list ->
  156. when {
  157. list == null -> block()
  158. list.isEmpty() -> block()
  159. else -> list
  160. }
  161. }
  162. @Composable
  163. fun rememberRunningIndicator(app: App?) = remember(
  164. app,
  165. app?.isFavorite,
  166. app?.isRunning,
  167. app?.isStarting
  168. ) {
  169. RunningAppIndicator(app)
  170. }
  171. // todo
  172. class RunningAppIndicator(
  173. val app: App?
  174. ) : State<Boolean> {
  175. override val value: Boolean
  176. get() = app?.isRunning == true || app?.hasProcess == true
  177. }
  178. }
  179. }