DeskptopContext.kt 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. package org.mjdev.desktop.context
  2. import androidx.compose.runtime.Composable
  3. import androidx.compose.runtime.remember
  4. import androidx.compose.runtime.rememberCoroutineScope
  5. import androidx.compose.ui.unit.DpSize
  6. import androidx.compose.ui.unit.dp
  7. import coil3.Bitmap
  8. import coil3.ImageLoader
  9. import coil3.PlatformContext
  10. import coil3.compose.LocalPlatformContext
  11. import coil3.request.ImageRequest
  12. import coil3.toBitmap
  13. import kotlinx.coroutines.CoroutineScope
  14. import kotlinx.coroutines.Dispatchers
  15. import kotlinx.coroutines.launch
  16. import okio.Path
  17. import org.mjdev.desktop.data.Category
  18. import org.mjdev.desktop.data.User
  19. import org.mjdev.desktop.extensions.Compose.isDesign
  20. import org.mjdev.desktop.helpers.application.ApplicationScope
  21. import org.mjdev.desktop.helpers.image.ImageLoader.asyncImageLoader
  22. import org.mjdev.desktop.managers.palette.Palette
  23. import org.mjdev.desktop.helpers.system.shell.Shell
  24. import org.mjdev.desktop.interfaces.IApp
  25. import org.mjdev.desktop.managers.connectivity.IConnectivityManager
  26. import org.mjdev.desktop.interfaces.IDesktopContext
  27. import org.mjdev.desktop.interfaces.IPalette
  28. import org.mjdev.desktop.interfaces.ITheme
  29. import org.mjdev.desktop.interfaces.IUser
  30. import org.mjdev.desktop.managers.language.Translator
  31. import org.mjdev.desktop.log.Log
  32. import org.mjdev.desktop.managers.ai.AiManager
  33. import org.mjdev.desktop.managers.ai.base.IAiManager
  34. import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
  35. import org.mjdev.desktop.managers.ai.stt.STTPluginEmpty
  36. import org.mjdev.desktop.managers.ai.tts.TTSPluginDesktop
  37. import org.mjdev.desktop.managers.ai.tts.TTSPluginSwift
  38. import org.mjdev.desktop.managers.apps.IAppsManager
  39. import org.mjdev.desktop.managers.os.IOSManager
  40. import org.mjdev.desktop.managers.process.IProcessManager
  41. import org.mjdev.desktop.managers.theme.IThemeManager
  42. import org.mjdev.desktop.managers.translations.ITranslator
  43. import org.mjdev.desktop.managers.apps.AppsManager
  44. import org.mjdev.desktop.managers.base.IDelegate
  45. import org.mjdev.desktop.managers.connectivity.ConnectivityManager
  46. import org.mjdev.desktop.managers.keys.IKeyManager
  47. import org.mjdev.desktop.managers.os.OsManager
  48. import org.mjdev.desktop.managers.processes.ProcessManager
  49. import org.mjdev.desktop.managers.theme.ThemeManager
  50. import java.awt.Desktop
  51. import java.awt.Toolkit
  52. import java.io.File
  53. import java.net.URI
  54. import kotlin.coroutines.CoroutineContext
  55. import kotlin.reflect.KClass
  56. import kotlin.reflect.full.companionObject
  57. import org.mjdev.desktop.managers.keys.KeysManager
  58. class DesktopContext(
  59. val application: ApplicationScope? = null,
  60. override val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
  61. override val imageLoader: ImageLoader? = null,
  62. val isInDesign: Boolean = false,
  63. override val platformContext: PlatformContext? = null
  64. ) : IDesktopContext() {
  65. private val toolkit: Toolkit by lazy { Toolkit.getDefaultToolkit() }
  66. private val desktopUtils: Desktop by lazy { Desktop.getDesktop() }
  67. // val windowsManager by lazy { WindowsManager(this) }
  68. // val dbus: DBus by lazy { DBus() }
  69. override var isFirstStart: Boolean = true // todo
  70. override var isInstalled: Boolean = false // todo
  71. override val appArgs: List<String>
  72. get() = application?.args ?: emptyList()
  73. override val isDebug: Boolean
  74. get() = appArgs.contains(APP_PARAM_DEBUG) || isInDesign
  75. val allUsers
  76. get() = User.allUsers(this)
  77. override val currentLocale
  78. get() = appsManager.currentLocale
  79. override val machineName
  80. get() = osManager.machineName
  81. override val allApps: List<IApp>
  82. get() = appsManager.allApps
  83. override val appCategories: List<Category>
  84. get() = appsManager.categories
  85. override val favoriteApps: List<IApp>
  86. get() = appsManager.favoriteApps
  87. override suspend fun open(what: Any?) {
  88. open(what.toString())
  89. }
  90. // todo state
  91. override val currentUser: IUser
  92. get() = allUsers.firstOrNull { u -> u.isLoggedIn } ?: User.DEFAULT
  93. override val theme: ITheme
  94. get() = currentUser.theme
  95. override val containerSize: DpSize
  96. get() = runCatching {
  97. toolkit.screenSize.let { screen -> DpSize(screen.width.dp, screen.height.dp) }
  98. }.getOrNull() ?: DpSize.Zero
  99. override val isLandscapeMode: Boolean
  100. get() = containerSize.let { it.width > it.height }
  101. // init {
  102. // Log.i("Got app args: $args")
  103. // runCatching {
  104. // windowsManager.init()
  105. // }
  106. // runCatching {
  107. // mounts.init()
  108. // }
  109. // runCatching {
  110. // dbus.updateEnvironment()
  111. // }
  112. // }
  113. override fun dispose() {
  114. Log.i("Cleaning up resources.")
  115. processManager.dispose()
  116. palette.dispose()
  117. theme.dispose()
  118. // runCatching {
  119. // mounts.dispose()
  120. // }
  121. // runCatching {
  122. // kcefHelper.dispose()
  123. // }
  124. // runCatching {
  125. // windowsManager.dispose()
  126. // }
  127. }
  128. fun runAsync(
  129. context: CoroutineContext = Dispatchers.Default,
  130. block: suspend () -> Unit
  131. ) {
  132. scope.launch(context) { block() }
  133. }
  134. suspend fun openMail(emailAddress: String) = runCatching {
  135. desktopUtils.mail(URI.create("mailto:$emailAddress"))
  136. }
  137. suspend fun openBrowser(url: String) = runCatching {
  138. desktopUtils.browse(URI.create(url))
  139. }
  140. suspend fun openDirectoryForFile(path: String) = runCatching {
  141. desktopUtils.browseFileDirectory(File(path))
  142. }
  143. suspend fun moveToTrash(file: File) = runCatching {
  144. desktopUtils.moveToTrash(file)
  145. }
  146. suspend fun moveToTrash(filePath: String) = runCatching {
  147. moveToTrash(File(filePath))
  148. }
  149. // todo, may be need another function
  150. suspend fun open(what: String) {
  151. Shell.executeAndRead("xdg-open", what)
  152. }
  153. suspend fun openFileInAssociated(file: File) = runCatching {
  154. desktopUtils.open(file)
  155. }
  156. suspend fun openFileInAssociated(filePath: String) = runCatching {
  157. openFileInAssociated(File(filePath))
  158. }
  159. // suspend fun beep() = runCatching {
  160. // toolkit.beep()
  161. // }
  162. // suspend fun sync() = runCatching {
  163. // toolkit.sync()
  164. // }
  165. // suspend fun createCustomCursor(cursor: Image, hotSpot: Point, name: String): Cursor? = runCatching {
  166. // toolkit.createCustomCursor(cursor, hotSpot, name)
  167. // }.getOrNull()
  168. // suspend fun createCustomCursor(cursor: Bitmap, hotSpot: Point, name: String): Cursor? = runCatching {
  169. // toolkit.createCustomCursor(cursor.image, hotSpot, name)
  170. // }.getOrNull()
  171. // suspend fun createCustomCursor(cursor: ImageBitmap, hotSpot: Point, name: String): Cursor? = runCatching {
  172. // toolkit.createCustomCursor(cursor.toAwtImage(), hotSpot, name)
  173. // }.getOrNull()
  174. // suspend fun createCustomCursor(cursor: ImageVector, hotSpot: Point, name: String) =
  175. // toolkit.createCustomCursor(cursor, hotSpot, name)
  176. // @Suppress("UNUSED_PARAMETER")
  177. // override suspend fun login(
  178. // uname: String,
  179. // upasswd: String
  180. // ): Boolean = true // todo
  181. // override suspend fun logout() : Boolean = true // todo
  182. fun lock() = runAsync {
  183. Shell.executeAndRead("/usr/bin/loginctl", "lock-sessions")
  184. }
  185. override suspend fun logOut(): Unit = runAsync {
  186. application?.exitApplication()
  187. }
  188. override suspend fun suspend() {
  189. Shell.executeAndRead("/usr/bin/systemctl", "suspend")
  190. }
  191. override suspend fun shutdown() {
  192. Shell.executeAndRead("/usr/sbin/halt", "--poweroff")
  193. }
  194. override suspend fun login(uName: String, uPasswd: String): Boolean {
  195. return false // todo
  196. }
  197. override suspend fun logout(): Boolean {
  198. return false // todo
  199. }
  200. override fun createManager(cls: KClass<*>): IDelegate? = when (cls) {
  201. IOSManager::class -> OsManager(this)
  202. IPalette::class -> Palette(this)
  203. ITranslator::class -> Translator(this)
  204. IConnectivityManager::class -> ConnectivityManager(this)
  205. IAiManager::class -> AiManager(
  206. context = this,
  207. // todo user can configure
  208. pluginAI = AiPluginGemini(this@DesktopContext),
  209. pluginTTS = TTSPluginSwift(this@DesktopContext),
  210. pluginSTT = STTPluginEmpty(this@DesktopContext)
  211. )
  212. IAppsManager::class -> AppsManager(this)
  213. IThemeManager::class -> ThemeManager(this)
  214. IProcessManager::class -> ProcessManager(this)
  215. IKeyManager::class -> KeysManager(this)
  216. else -> cls.companionObject?.members?.first { it.name == "EMPTY" }?.call() as IDelegate
  217. }
  218. override suspend fun restart() {
  219. Shell.executeAndRead("/usr/sbin/halt", "--reboot")
  220. }
  221. // suspend fun Desktop.startApp(app: App) {
  222. // startApp(app.desktopFile)
  223. // }
  224. // suspend fun Desktop.startApp(
  225. // desktopFile: DesktopFile?
  226. // ) = withContext(Dispatchers.Default) {
  227. // open(desktopFile?.file)
  228. // }
  229. companion object {
  230. // todo application
  231. @Composable
  232. fun rememberDesktopContext(
  233. application: ApplicationScope?,
  234. imageLoader: ImageLoader = asyncImageLoader(),
  235. platformContext: PlatformContext = LocalPlatformContext.current,
  236. scope: CoroutineScope = rememberCoroutineScope(),
  237. isInDesign: Boolean = isDesign,
  238. ) = remember {
  239. Log.i("default initialized desktop provider created")
  240. DesktopContext(
  241. application = application,
  242. scope = scope,
  243. imageLoader = imageLoader,
  244. isInDesign = isInDesign,
  245. platformContext = platformContext
  246. )
  247. }
  248. suspend fun IDesktopContext.loadPicture(
  249. src: Any?
  250. ): Bitmap? = runCatching {
  251. ImageRequest.Builder(platformContext!!)
  252. .data(
  253. when (src) {
  254. is Path -> src.toFile()
  255. else -> src.toString()
  256. }
  257. )
  258. .build().let { req ->
  259. imageLoader?.execute(req)?.image?.toBitmap()
  260. }
  261. }.getOrNull()
  262. }
  263. }