AppsManager.kt 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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.apps
  9. import androidx.compose.runtime.Composable
  10. import androidx.compose.runtime.State
  11. import androidx.compose.runtime.collectAsState
  12. import eu.mjdev.desktop.data.App
  13. import eu.mjdev.desktop.data.Category
  14. import eu.mjdev.desktop.extensions.Custom.desktopFiles
  15. import eu.mjdev.desktop.extensions.Custom.dirsOnly
  16. import eu.mjdev.desktop.extensions.Custom.flowBlock
  17. import eu.mjdev.desktop.extensions.Custom.get
  18. import eu.mjdev.desktop.extensions.Custom.jsonToList
  19. import eu.mjdev.desktop.extensions.Custom.lines
  20. import eu.mjdev.desktop.extensions.Custom.text
  21. import eu.mjdev.desktop.extensions.Custom.textAsLocale
  22. import eu.mjdev.desktop.helpers.system.Shell
  23. import eu.mjdev.desktop.provider.DesktopProvider
  24. import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
  25. import kotlinx.coroutines.CoroutineScope
  26. import kotlinx.coroutines.Dispatchers
  27. import kotlinx.coroutines.flow.Flow
  28. import kotlinx.coroutines.flow.flowOn
  29. import java.io.File
  30. import java.util.*
  31. @Suppress("unused", "MemberVisibilityCanBePrivate")
  32. class AppsManager(
  33. val api: DesktopProvider,
  34. val scope: CoroutineScope = api.scope
  35. ) {
  36. companion object {
  37. const val DIR_NAME_ROOT = "/"
  38. const val DIR_NAME_USR = "/usr"
  39. const val DIR_NAME_VAR = "/var"
  40. const val DIR_NAME_DOT_CONFIG = ".config"
  41. const val DIR_NAME_DOT_ICONS = ".icons"
  42. const val DIR_NAME_DOT_THEMES = ".themes"
  43. const val DIR_NAME_DOT_LOCAL = ".local"
  44. const val DIR_NAME_SHARE = "share"
  45. const val DIR_NAME_APPLICATIONS = "applications"
  46. const val DIR_NAME_AUTOSTART = "autoStart"
  47. const val DIR_NAME_BACKGROUNDS = "backgrounds"
  48. const val DIR_NAME_MENUS = "menus"
  49. const val DIR_NAME_LIB = "lib"
  50. const val DIR_NAME_FLATPAK = "flatpak"
  51. const val DIR_NAME_EXPORTS = "exports"
  52. const val DIR_NAME_SNAPD = "snapd"
  53. const val DIR_NAME_DESKTOP = "desktop"
  54. const val FILE_NAME_GNOME_APPS_MENU = "gnome-applications.menu"
  55. const val FILE_NAME_BACKGROUND = "background"
  56. const val FILE_NAME_MIME_APPS_LIST = "mimeapps.list"
  57. const val FILE_NAME_USER_DIRS = "user-dirs.dirs"
  58. const val FILE_NAME_USER_DIRS_LOCALE = "user-dirs.locale"
  59. // todo
  60. @Composable
  61. fun rememberFavoriteApps(
  62. api: DesktopProvider = LocalDesktop.current,
  63. appsManager: AppsManager = api.appsManager,
  64. ): State<List<App>> {
  65. // var processes by mutableStateOf(0)
  66. // processManagerListener { processes = size }
  67. return appsManager.favoriteApps.collectAsState(emptyList())
  68. // todo
  69. }
  70. }
  71. private val rootDir = File(DIR_NAME_ROOT)
  72. private val usrDir = File(DIR_NAME_USR)
  73. private val varDir = File(DIR_NAME_VAR)
  74. private val configDir = api.homeDir[DIR_NAME_DOT_CONFIG]
  75. private val iconsDir = api.homeDir[DIR_NAME_DOT_ICONS]
  76. private val themesDir = api.homeDir[DIR_NAME_DOT_THEMES]
  77. private val localDir = api.homeDir[DIR_NAME_DOT_LOCAL]
  78. private val localShareDir = localDir[DIR_NAME_SHARE]
  79. private val allAppsDesktopFilesDir = localShareDir[DIR_NAME_APPLICATIONS]
  80. private val autostartDesktopFilesDir = configDir[DIR_NAME_AUTOSTART]
  81. private val backgroundFilesDir by lazy { localShareDir[DIR_NAME_BACKGROUNDS] }
  82. // private val userBackgroundFilesDir by lazy { homeDir?.resolve("pictures") } // todo
  83. private val menusDir = configDir[DIR_NAME_MENUS]
  84. private val gnomeAppsFile = menusDir[FILE_NAME_GNOME_APPS_MENU] // xml
  85. private val backgroundFile = configDir[FILE_NAME_BACKGROUND] // jpg
  86. private val mimeApps by lazy {
  87. configDir[FILE_NAME_MIME_APPS_LIST].lines
  88. } // <mime>=<desktop.file>
  89. private val userDirs by lazy {
  90. configDir[FILE_NAME_USER_DIRS].text
  91. } // <type>="<path>"
  92. private val userDirsLocale by lazy {
  93. configDir[FILE_NAME_USER_DIRS_LOCALE].textAsLocale
  94. } //SK_sk
  95. private val menusItems by lazy {
  96. menusDir[FILE_NAME_GNOME_APPS_MENU].text
  97. } // xml
  98. private val iconThemes by lazy { iconsDir.dirsOnly } // folders
  99. private val systemThemes by lazy { themesDir.dirsOnly } // folders
  100. private val autoStartDesktopFiles by lazy {
  101. autostartDesktopFilesDir.desktopFiles
  102. }
  103. // todo : probably not all here, need usr and local also
  104. private val allAppsDesktopFilesLocal by lazy {
  105. allAppsDesktopFilesDir.desktopFiles
  106. }
  107. private val allAppsDesktopFilesShared by lazy {
  108. usrDir[DIR_NAME_SHARE][DIR_NAME_APPLICATIONS].desktopFiles
  109. }
  110. private val allAppsDesktopFilesFlatPack by lazy {
  111. varDir[DIR_NAME_LIB][DIR_NAME_FLATPAK][DIR_NAME_EXPORTS][DIR_NAME_SHARE][DIR_NAME_APPLICATIONS].desktopFiles
  112. }
  113. private val allAppsDesktopFilesSnap by lazy {
  114. varDir[DIR_NAME_LIB][DIR_NAME_SNAPD][DIR_NAME_DESKTOP][DIR_NAME_APPLICATIONS].desktopFiles
  115. }
  116. private val allAppsDesktopFiles by lazy {
  117. (allAppsDesktopFilesLocal +
  118. allAppsDesktopFilesShared +
  119. allAppsDesktopFilesFlatPack +
  120. allAppsDesktopFilesSnap).distinctBy { it.file.name }
  121. }
  122. val currentLocale: Locale
  123. get() = userDirsLocale
  124. val autoStartApps
  125. get() = autoStartDesktopFiles.map { file -> App(file) }
  126. val allApps
  127. get() = allAppsDesktopFiles.map { file -> App(file) }
  128. // val backgrounds by lazy {
  129. // backgroundFilesDir.filesOnly.sortedByName()
  130. // }
  131. val appCategories = flowBlock {
  132. allApps.asSequence().flatMap { app ->
  133. app.categories.map { c -> provideCategory(c) }
  134. }.distinct().sorted().map { c ->
  135. Category(c)
  136. }.sortedByDescending { c ->
  137. c.priority
  138. }.toList()
  139. }.flowOn(Dispatchers.IO)
  140. val favoriteApps: Flow<List<App>> = flowBlock {
  141. Shell.executeAndRead(
  142. "gsettings",
  143. "get",
  144. "org.gnome.shell",
  145. "favorite-apps"
  146. ).jsonToList<String>().asSequence().flatMap { deskFileName ->
  147. allAppsDesktopFiles.filter { deskFile ->
  148. deskFile.fileName.contentEquals(deskFileName)
  149. }.map { deskFile ->
  150. App(deskFile)
  151. }
  152. }.toList()
  153. }.flowOn(Dispatchers.IO)
  154. // todo languages
  155. fun provideCategory(name: String): String {
  156. return name.trim().let { n ->
  157. if (n.isEmpty() || n.startsWith("X-", true)) Category.UNCATEGORIZED
  158. else name
  159. }
  160. }
  161. }