App.kt 6.2 KB

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