DeskptopContext.kt 9.8 KB

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