DeskptopContext.kt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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 okio.Path
  16. import org.mjdev.desktop.data.Category
  17. import org.mjdev.desktop.data.User
  18. import org.mjdev.desktop.extensions.Compose.isDesign
  19. import org.mjdev.desktop.helpers.application.ApplicationScope
  20. import org.mjdev.desktop.helpers.image.ImageLoader.asyncImageLoader
  21. import org.mjdev.desktop.helpers.system.shell.Shell
  22. import org.mjdev.desktop.interfaces.IApp
  23. import org.mjdev.desktop.interfaces.ITheme
  24. import org.mjdev.desktop.interfaces.IUser
  25. import org.mjdev.desktop.log.Log
  26. import org.mjdev.desktop.managers.ai.AiManager
  27. import org.mjdev.desktop.managers.ai.IAiManager
  28. import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
  29. import org.mjdev.desktop.managers.ai.plugins.AiPluginOpenAi
  30. import org.mjdev.desktop.managers.ai.stt.STTPluginEmpty
  31. import org.mjdev.desktop.managers.ai.tts.TTSPluginSwift
  32. import org.mjdev.desktop.managers.apps.AppsManager
  33. import org.mjdev.desktop.managers.apps.IAppsManager
  34. import org.mjdev.desktop.managers.base.IDelegate
  35. import org.mjdev.desktop.managers.connectivity.ConnectivityManager
  36. import org.mjdev.desktop.managers.connectivity.IConnectivityManager
  37. import org.mjdev.desktop.managers.keys.IKeyManager
  38. import org.mjdev.desktop.managers.keys.KeysManager
  39. import org.mjdev.desktop.managers.language.Translator
  40. import org.mjdev.desktop.managers.os.IOSManager
  41. import org.mjdev.desktop.managers.os.OsManager
  42. import org.mjdev.desktop.managers.palette.IPalette
  43. import org.mjdev.desktop.managers.palette.Palette
  44. import org.mjdev.desktop.managers.plugins.IPluginManager
  45. import org.mjdev.desktop.managers.plugins.PluginManager
  46. import org.mjdev.desktop.managers.process.IProcessManager
  47. import org.mjdev.desktop.managers.processes.ProcessManager
  48. import org.mjdev.desktop.managers.remote.IRemoteDesktopManager
  49. import org.mjdev.desktop.managers.remote.RemoteDesktopManager
  50. import org.mjdev.desktop.managers.theme.IThemeManager
  51. import org.mjdev.desktop.managers.theme.ThemeManager
  52. import org.mjdev.desktop.managers.translations.ITranslator
  53. import org.mjdev.desktop.managers.volume.IVolumeManager
  54. import org.mjdev.desktop.managers.volume.VolumeManager
  55. import java.awt.Desktop
  56. import java.awt.Toolkit
  57. import java.io.File
  58. import java.net.URI
  59. import kotlin.reflect.KClass
  60. import kotlin.reflect.full.companionObject
  61. import kotlin.reflect.full.companionObjectInstance
  62. import kotlin.reflect.jvm.isAccessible
  63. @Suppress("RedundantSuspendModifier", "unused", "MemberVisibilityCanBePrivate")
  64. class DesktopContext(
  65. val application: ApplicationScope? = null,
  66. override val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
  67. override val imageLoader: ImageLoader? = null,
  68. val isInDesign: Boolean = false,
  69. override val platformContext: PlatformContext? = null,
  70. ) : IDesktopContext() {
  71. private val toolkit: Toolkit by lazy { Toolkit.getDefaultToolkit() }
  72. private val desktopUtils: Desktop by lazy { Desktop.getDesktop() }
  73. // val windowsManager by lazy { WindowsManager(this) }
  74. // val dbus: DBus by lazy { DBus() }
  75. override var isFirstStart: Boolean = true // todo
  76. override var isInstalled: Boolean = false // todo
  77. override val appArgs: List<String>
  78. get() = application?.args ?: emptyList()
  79. override val isDebug: Boolean
  80. get() = appArgs.contains(APP_PARAM_DEBUG) || isInDesign
  81. val allUsers
  82. get() = User.allUsers(this)
  83. override val currentLocale
  84. get() = appsManager.currentLocale
  85. override val machineName
  86. get() = osManager.machineName
  87. override val allApps: List<IApp>
  88. get() = appsManager.allApps
  89. override val appCategories: List<Category>
  90. get() = appsManager.categories
  91. override val favoriteApps: List<IApp>
  92. get() = appsManager.favoriteApps
  93. override suspend fun open(what: Any?) {
  94. open(what.toString())
  95. }
  96. // todo state
  97. override val currentUser: IUser
  98. get() = allUsers.firstOrNull { u -> u.isLoggedIn } ?: User.DEFAULT
  99. override val theme: ITheme
  100. get() = currentUser.theme
  101. override val containerSize: DpSize
  102. get() =
  103. runCatching {
  104. toolkit.screenSize.let { screen -> DpSize(screen.width.dp, screen.height.dp) }
  105. }.getOrNull() ?: DpSize.Zero
  106. init {
  107. // sudo apt-get update
  108. // sudo apt-get install \
  109. // libgl1-mesa-dev \
  110. // xorg-dev \
  111. // libgtk-3-dev \
  112. // libglib2.0-dev \
  113. // libwebkit2gtk-4.0-dev \
  114. // libjavafx-web-dev \
  115. // openjfx \
  116. // libwebkit2gtk-4.0-37 \
  117. // libwebkitgtk-6.0-4
  118. // val javaLibPath = System.getProperty("java.library.path")
  119. // System.setProperty("prism.order", "sw")
  120. // System.setProperty("glass.platform", "gtk")
  121. // System.setProperty("javafx.platform", "gtk")
  122. // System.setProperty("quantum.multithreaded", "false")
  123. // System.setProperty("java.library.path","$javaLibPath:/usr/lib/x86_64-linux-gnu/")
  124. // Log.i("Got app args: $args")
  125. // runCatching {
  126. // windowsManager.init()
  127. // }
  128. // runCatching {
  129. // mounts.init()
  130. // }
  131. // runCatching {
  132. // dbus.updateEnvironment()
  133. // }
  134. }
  135. override fun dispose() {
  136. Log.i("Cleaning up resources.")
  137. processManager.dispose()
  138. palette.dispose()
  139. theme.dispose()
  140. // runCatching {
  141. // mounts.dispose()
  142. // }
  143. // runCatching {
  144. // kcefHelper.dispose()
  145. // }
  146. // runCatching {
  147. // windowsManager.dispose()
  148. // }
  149. }
  150. suspend fun openMail(emailAddress: String) = runCatching {
  151. desktopUtils.mail(URI.create("mailto:$emailAddress"))
  152. }
  153. suspend fun openBrowser(url: String) = runCatching {
  154. desktopUtils.browse(URI.create(url))
  155. }
  156. suspend fun openDirectoryForFile(path: String) = runCatching {
  157. desktopUtils.browseFileDirectory(File(path))
  158. }
  159. suspend fun moveToTrash(file: File) = runCatching {
  160. desktopUtils.moveToTrash(file)
  161. }
  162. suspend fun moveToTrash(filePath: String) = runCatching {
  163. moveToTrash(File(filePath))
  164. }
  165. // todo, may be need another function
  166. suspend fun open(what: String) {
  167. Shell.executeAndRead("xdg-open", what)
  168. }
  169. suspend fun openFileInAssociated(file: File) = runCatching {
  170. desktopUtils.open(file)
  171. }
  172. suspend fun openFileInAssociated(filePath: String) = runCatching {
  173. openFileInAssociated(File(filePath))
  174. }
  175. // suspend fun beep() = runCatching {
  176. // toolkit.beep()
  177. // }
  178. // suspend fun sync() = runCatching {
  179. // toolkit.sync()
  180. // }
  181. // suspend fun createCustomCursor(cursor: Image, hotSpot: Point, name: String): Cursor? = runCatching {
  182. // toolkit.createCustomCursor(cursor, hotSpot, name)
  183. // }.getOrNull()
  184. // suspend fun createCustomCursor(cursor: Bitmap, hotSpot: Point, name: String): Cursor? = runCatching {
  185. // toolkit.createCustomCursor(cursor.image, hotSpot, name)
  186. // }.getOrNull()
  187. // suspend fun createCustomCursor(cursor: ImageBitmap, hotSpot: Point, name: String): Cursor? = runCatching {
  188. // toolkit.createCustomCursor(cursor.toAwtImage(), hotSpot, name)
  189. // }.getOrNull()
  190. // suspend fun createCustomCursor(cursor: ImageVector, hotSpot: Point, name: String) =
  191. // toolkit.createCustomCursor(cursor, hotSpot, name)
  192. // @Suppress("UNUSED_PARAMETER")
  193. // override suspend fun login(
  194. // uname: String,
  195. // upasswd: String
  196. // ): Boolean = true // todo
  197. // override suspend fun logout() : Boolean = true // todo
  198. fun lock() = runAsync {
  199. Shell.executeAndRead("/usr/bin/loginctl", "lock-sessions")
  200. }
  201. override suspend fun logOut() {
  202. runAsync {
  203. application?.exitApplication()
  204. }
  205. }
  206. override suspend fun suspend() {
  207. Shell.executeAndRead("/usr/bin/systemctl", "suspend")
  208. }
  209. override suspend fun shutdown() {
  210. Shell.executeAndRead("/usr/sbin/halt", "--poweroff")
  211. }
  212. override suspend fun login(
  213. uName: String,
  214. uPasswd: String,
  215. ): Boolean {
  216. return false // todo
  217. }
  218. override suspend fun logout(): Boolean {
  219. return false // todo
  220. }
  221. override fun createManager(cls: KClass<*>): IDelegate = when (cls) {
  222. IOSManager::class -> OsManager(this)
  223. IPalette::class -> Palette(this)
  224. ITranslator::class -> Translator(this)
  225. IConnectivityManager::class -> ConnectivityManager(this)
  226. IAiManager::class ->
  227. AiManager(
  228. context = this,
  229. // todo user can configure
  230. pluginAI = AiPluginOpenAi(this@DesktopContext),
  231. pluginTTS = TTSPluginSwift(this@DesktopContext),
  232. pluginSTT = STTPluginEmpty(this@DesktopContext),
  233. )
  234. IAppsManager::class -> AppsManager(this)
  235. IThemeManager::class -> ThemeManager(this)
  236. IProcessManager::class -> ProcessManager(this)
  237. IKeyManager::class -> KeysManager(this)
  238. IRemoteDesktopManager::class -> RemoteDesktopManager(this)
  239. IVolumeManager::class -> VolumeManager(this)
  240. IPluginManager::class -> PluginManager(this)
  241. else -> createEmptyManager(cls)
  242. }
  243. /**
  244. * Resolves the `EMPTY` fallback of a manager's companion. The backing field is private
  245. * static, so the getter must be made accessible before it is called reflectively.
  246. */
  247. private fun createEmptyManager(cls: KClass<*>): IDelegate = runCatching {
  248. cls.companionObject
  249. ?.members
  250. ?.firstOrNull { it.name == "EMPTY" }
  251. ?.also { it.isAccessible = true }
  252. ?.call(cls.companionObjectInstance) as? IDelegate
  253. }.onFailure { e ->
  254. Log.e(e)
  255. }.getOrNull() ?: error("No manager available for $cls")
  256. override suspend fun restart() {
  257. Shell.executeAndRead("/usr/sbin/halt", "--reboot")
  258. }
  259. // suspend fun startApp(app: App) {
  260. // startApp(app.desktopFile)
  261. // }
  262. // suspend fun Desktop.startApp(
  263. // desktopFile: DesktopFile?
  264. // ) = withContext(Dispatchers.Default) {
  265. // open(desktopFile?.file)
  266. // }
  267. companion object {
  268. // todo application
  269. @Composable
  270. fun rememberDesktopContext(
  271. application: ApplicationScope?,
  272. imageLoader: ImageLoader = asyncImageLoader(),
  273. platformContext: PlatformContext = LocalPlatformContext.current,
  274. scope: CoroutineScope = rememberCoroutineScope(),
  275. isInDesign: Boolean = isDesign,
  276. ) = remember {
  277. Log.i("default initialized desktop provider created")
  278. DesktopContext(
  279. application = application,
  280. scope = scope,
  281. imageLoader = imageLoader,
  282. isInDesign = isInDesign,
  283. platformContext = platformContext,
  284. )
  285. }
  286. suspend fun IDesktopContext.loadPicture(src: Any?): Bitmap? = runCatching {
  287. ImageRequest
  288. .Builder(platformContext!!)
  289. .data(
  290. when (src) {
  291. is Path -> src.toFile()
  292. else -> src.toString()
  293. },
  294. ).build()
  295. .let { req ->
  296. imageLoader?.execute(req)?.image?.toBitmap()
  297. }
  298. }.getOrNull()
  299. }
  300. }