Explorar o código

window manager reimplementation

window manager reimplementation
Milan Jurkulak %!s(int64=2) %!d(string=hai) anos
pai
achega
e3e50c9600
Modificáronse 26 ficheiros con 370 adicións e 635 borrados
  1. 2 1
      build.gradle.kts
  2. 8 11
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsBottomBar.kt
  3. 16 28
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt
  4. 1 2
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt
  5. 31 35
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuCategory.kt
  6. 10 11
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt
  7. 0 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/ControlCenterPageScope.kt
  8. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/BluetoothSettingsPage.kt
  9. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/EthSettingsPage.kt
  10. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/WifiSettingsPage.kt
  11. 13 10
      src/main/kotlin/eu/mjdev/desktop/components/custom/Clock.kt
  12. 13 28
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  13. 3 5
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt
  14. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/file/FolderIcon.kt
  15. 1 3
      src/main/kotlin/eu/mjdev/desktop/data/App.kt
  16. 34 10
      src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt
  17. 9 0
      src/main/kotlin/eu/mjdev/desktop/data/DesktopWindow.kt
  18. 17 0
      src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt
  19. 3 1
      src/main/kotlin/eu/mjdev/desktop/helpers/managers/KCEFHelper.kt
  20. 0 453
      src/main/kotlin/eu/mjdev/desktop/helpers/managers/WindowsManager.kt
  21. 10 2
      src/main/kotlin/eu/mjdev/desktop/provider/AIProvider.kt
  22. 14 11
      src/main/kotlin/eu/mjdev/desktop/provider/AppsProvider.kt
  23. 30 14
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt
  24. 76 0
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopScope.kt
  25. 70 0
      src/main/kotlin/eu/mjdev/desktop/provider/WindowsManager.kt
  26. 5 5
      src/main/kotlin/eu/mjdev/desktop/theme/gtk/GtkTheme.kt

+ 2 - 1
build.gradle.kts

@@ -101,7 +101,8 @@ allprojects {
         implementation("io.github.irgaly.kfswatch:kfswatch:1.3.0")
         // java gnome
         implementation("com.github.bailuk:java-gtk:0.5.0")
-
+        // wnck
+        // todo
         // compose debugger
 //        implementation("io.github.theapache64:rebugger:1.0.0-rc03")
         // music

+ 8 - 11
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsBottomBar.kt

@@ -12,7 +12,6 @@ import androidx.compose.material.icons.filled.RestartAlt
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.icon.ShapedIcon
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
@@ -21,8 +20,6 @@ import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 @Composable
 fun AppsBottomBar(
     modifier: Modifier = Modifier,
-    iconBackgroundColor: Color = Color.White,
-    iconColor: Color = Color.Black,
     backButtonVisible: Boolean = false,
     onContextMenuClick: () -> Unit = {},
     onBackClick: () -> Unit = {}
@@ -36,8 +33,8 @@ fun AppsBottomBar(
             if (backButtonVisible) {
                 ShapedIcon(
                     imageVector = Icons.AutoMirrored.Filled.ArrowBack,
-                    iconColor = iconColor,
-                    iconBackgroundColor = iconBackgroundColor,
+                    iconColor = borderColor,
+                    iconBackgroundColor = iconsTintColor,
                     onRightClick = onContextMenuClick,
                     onClick = onBackClick
                 )
@@ -53,22 +50,22 @@ fun AppsBottomBar(
         ) {
             ShapedIcon(
                 imageVector = Icons.Filled.RestartAlt,
-                iconColor = iconColor,
-                iconBackgroundColor = iconBackgroundColor,
+                iconColor = borderColor,
+                iconBackgroundColor = iconsTintColor,
                 onRightClick = onContextMenuClick,
                 onClick = { api.restart() }
             )
             ShapedIcon(
                 imageVector = Icons.AutoMirrored.Filled.Logout,
-                iconColor = iconColor,
-                iconBackgroundColor = iconBackgroundColor,
+                iconColor = borderColor,
+                iconBackgroundColor = iconsTintColor,
                 onRightClick = onContextMenuClick,
                 onClick = { api.logOut() }
             )
             ShapedIcon(
                 imageVector = Icons.Filled.PowerOff,
-                iconColor = iconColor,
-                iconBackgroundColor = iconBackgroundColor,
+                iconColor = borderColor,
+                iconBackgroundColor = iconsTintColor,
                 onRightClick = onContextMenuClick,
                 onClick = { api.shutdown() }
             )

+ 16 - 28
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -32,7 +32,7 @@ import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.extensions.Modifier.dropShadow
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuExitAnimation
-import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopScope
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindowState
@@ -47,31 +47,24 @@ fun AppsMenu(
     enterAnimation: EnterTransition = AppsMenuEnterAnimation,
     exitAnimation: ExitTransition = AppsMenuExitAnimation,
     onFocusChange: (Boolean) -> Unit = {},
-    onAppClick: (App, DesktopProvider) -> Unit = { app, api ->
-        api.appsProvider.startApp(app)
-    },
-    onAppContextMenuClick: (App) -> Unit = {
-        // todo
-    },
-    onCategoryContextMenuClick: (Category) -> Unit = {
-        // todo
-    },
-    onUserAvatarClick: () -> Unit = {
-        // todo
-    }
+    onAppClick: DesktopScope.(App) -> Unit = { app -> startApp(app) },
+    onAppContextMenuClick: DesktopScope.(App) -> Unit = {},
+    onCategoryContextMenuClick: (Category) -> Unit = {},
+    onUserAvatarClick: () -> Unit = {}
 ) = withDesktopScope {
-    val appMenuMinWidth by remember { api.currentUser.theme.appMenuMinWidthState }
-    val appMenuMinHeight by remember { api.currentUser.theme.appMenuMinHeightState }
-    val menuPadding by remember { api.currentUser.theme.appMenuOuterPaddingState }
-    // todo
-    var items: List<Any> by remember(api.appsProvider) { mutableStateOf(api.appsProvider.appCategories) }
+    // todo remove those three
+    val appMenuMinWidth by remember { theme.appMenuMinWidthState }
+    val appMenuMinHeight by remember { theme.appMenuMinHeightState }
+    val menuPadding by remember { theme.appMenuOuterPaddingState }
+
+    var items: List<Any> by remember(appCategories) { mutableStateOf(appCategories) }
     val onCategoryClick: (Category) -> Unit = { category ->
-        items = api.appsProvider.categoriesAndApps[category.name]?.sortedBy { it.name } ?: emptyList()
+        items = appCategoriesAndApps[category.name]?.sortedBy { it.name } ?: emptyList()
     }
     val position by rememberCalculated {
         WindowPosition.Absolute(
             panelState.bounds.x,
-            api.containerSize.height - (panelState.bounds.height + appMenuMinHeight)
+            containerSize.height - (panelState.bounds.height + appMenuMinHeight)
         )
     }
     val windowState: ChromeWindowState = rememberChromeWindowState(position = position)
@@ -152,9 +145,6 @@ fun AppsMenu(
                                 items(items) { item ->
                                     AppsMenuCategory(
                                         category = item as Category,
-                                        backgroundColor = backgroundColor,
-                                        iconTint = textColor,
-                                        textColor = textColor,
                                         onClick = { onCategoryClick(item) },
                                         onContextMenuClick = { onCategoryContextMenuClick(item) }
                                     )
@@ -169,7 +159,7 @@ fun AppsMenu(
                                         iconTint = textColor,
                                         textColor = textColor,
                                         onClick = {
-                                            onAppClick(item, api)
+                                            onAppClick(item)
                                         },
                                         onContextMenuClick = {
                                             onAppContextMenuClick(item)
@@ -202,11 +192,9 @@ fun AppsMenu(
                             .fillMaxWidth()
                             .wrapContentHeight()
                             .background(backgroundColor),
-                        iconColor = borderColor,
-                        iconBackgroundColor = iconsTintColor,
                         backButtonVisible = items.first() is App,
                         onBackClick = {
-                            items = api.appsProvider.appCategories
+                            items = appCategories
                         },
                         onContextMenuClick = {
                             // todo : context menu
@@ -217,7 +205,7 @@ fun AppsMenu(
         }
         launchedEffect(menuState.isVisible) {
             if (!menuState.isVisible) {
-                items = api.appsProvider.appCategories
+                items = appCategories
             }
         }
     }

+ 1 - 2
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt

@@ -46,8 +46,7 @@ fun AppsMenuApp(
         verticalAlignment = Alignment.CenterVertically,
     ) {
         val appIconName = remember(app, icon) { app?.name ?: icon }
-        val materialIcon =
-            remember(appIconName) { api.currentUser.theme.iconSet.iconForName(appIconName) ?: "?".toInt() }
+        val materialIcon = remember(appIconName) { iconSet.iconForName(appIconName) ?: "?".toInt() }
         FontIcon(
             iconId = materialIcon,
             iconSize = iconSize,

+ 31 - 35
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuCategory.kt

@@ -11,7 +11,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.isSecondaryPressed
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.DpSize
@@ -19,8 +18,7 @@ import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.fonticon.FontIcon
 import eu.mjdev.desktop.components.text.TextAny
 import eu.mjdev.desktop.data.Category
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("DEPRECATION")
 @OptIn(ExperimentalFoundationApi::class)
@@ -29,40 +27,38 @@ fun AppsMenuCategory(
     modifier: Modifier = Modifier,
     category: Category = Category.Empty,
     iconSize: DpSize = DpSize(32.dp, 32.dp),
-    iconTint: Color = Color.Black,
-    textColor: Color = Color.White,
-    backgroundColor: Color = Color.White,
-    api: DesktopProvider = LocalDesktop.current,
     onClick: (category: Category) -> Unit = {},
     onContextMenuClick: (category: Category) -> Unit = {}
-) = Row(
-    modifier = modifier.mouseClickable {
-        if (buttons.isSecondaryPressed) {
-            onContextMenuClick(category)
-        } else {
-            onClick(category)
-        }
-    },
-    verticalAlignment = Alignment.CenterVertically
-) {
-    val categoryName = remember(category) { category.name }
-    val materialIcon = remember(categoryName) { api.currentUser.theme.iconSet.iconForName(categoryName) ?: "?".toInt() }
-    FontIcon(
-        iconId = materialIcon,
-        iconSize = iconSize,
-        iconColor = iconTint,
-        iconBackgroundColor = backgroundColor,
-        outerPadding = PaddingValues(2.dp),
-        innerPadding = PaddingValues(0.dp),
-        onClick = { onClick(category) },
-        onRightClick = { onContextMenuClick(category) }
-    )
-    TextAny(
-        modifier = Modifier.padding(start = 4.dp).fillMaxWidth(),
-        text = category.name,
-        color = textColor,
-        fontWeight = FontWeight.Bold
-    )
+) = withDesktopScope {
+    Row(
+        modifier = modifier.mouseClickable {
+            if (buttons.isSecondaryPressed) {
+                onContextMenuClick(category)
+            } else {
+                onClick(category)
+            }
+        },
+        verticalAlignment = Alignment.CenterVertically
+    ) {
+        val categoryName = remember(category) { category.name }
+        val materialIcon = remember(categoryName) { iconSet.iconForName(categoryName) ?: "?".toInt() }
+        FontIcon(
+            iconId = materialIcon,
+            iconSize = iconSize,
+            iconColor = textColor,
+            iconBackgroundColor = backgroundColor,
+            outerPadding = PaddingValues(2.dp),
+            innerPadding = PaddingValues(0.dp),
+            onClick = { onClick(category) },
+            onRightClick = { onContextMenuClick(category) }
+        )
+        TextAny(
+            modifier = Modifier.padding(start = 4.dp).fillMaxWidth(),
+            text = category.name,
+            color = textColor,
+            fontWeight = FontWeight.Bold
+        )
+    }
 }
 
 @Preview

+ 10 - 11
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt

@@ -30,8 +30,6 @@ import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterExitAnimation
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindowState
@@ -43,7 +41,6 @@ import kotlinx.coroutines.launch
 @Suppress("FunctionName", "DEPRECATION")
 @Composable
 fun ControlCenter(
-    api: DesktopProvider = LocalDesktop.current,
     pagerState: MutableState<Int> = rememberState(0),
     controlCenterState: VisibilityState = rememberVisibilityState(),
     scope: CoroutineScope = rememberCoroutineScope(),
@@ -52,18 +49,20 @@ fun ControlCenter(
     onFocusChange: (Boolean) -> Unit = {},
     onContextMenuClick: () -> Unit = {}
 ) = withDesktopScope {
-    val backgroundAlpha by remember { api.currentUser.theme.controlCenterBackgroundAlphaState }
-    val controlCenterExpandedWidth by remember { api.currentUser.theme.controlCenterExpandedWidthState }
-    val controlCenterDividerWidth by remember { api.currentUser.theme.controlCenterDividerWidthState }
-    val controlCenterIconSize by remember { api.currentUser.theme.controlCenterIconSizeState }
-    val pages by remember { api.controlCenterPagesState }
-    val pagesFiltered = rememberCalculated(pages) { pages.filter { p -> p.condition.invoke(api) } }
+    // todo remove those
+    val backgroundAlpha by remember { theme.controlCenterBackgroundAlphaState }
+    val controlCenterExpandedWidth by remember { theme.controlCenterExpandedWidthState }
+    val controlCenterDividerWidth by remember { theme.controlCenterDividerWidthState }
+    val controlCenterIconSize by remember { theme.controlCenterIconSizeState }
+    //
+    val pagesFiltered =
+        rememberCalculated(controlCenterPages) { controlCenterPages.filter { p -> p.condition.invoke(api) } }
     val position by rememberCalculated { WindowPosition.Aligned(Alignment.TopEnd) }
     val size by rememberCalculated {
         if (controlCenterState.isVisible) {
-            DpSize(controlCenterExpandedWidth, api.containerSize.height)
+            DpSize(controlCenterExpandedWidth, containerSize.height)
         } else {
-            DpSize(controlCenterDividerWidth, api.containerSize.height)
+            DpSize(controlCenterDividerWidth, containerSize.height)
         }
     }
     val windowState: ChromeWindowState = rememberChromeWindowState(position = position, size = size)

+ 0 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/ControlCenterPageScope.kt

@@ -10,7 +10,6 @@ class ControlCenterPageScope(
     api: DesktopProvider,
     val cache: PageCache
 ) : DesktopScope(api) {
-
     companion object {
         @Suppress("UnusedReceiverParameter")
         @Composable

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/BluetoothSettingsPage.kt

@@ -13,7 +13,7 @@ import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 fun BluetoothSettingsPage() = ControlCenterPage(
     icon = Icons.Filled.Bluetooth,
     name = "Bluetooth",
-    condition = { connection.isBthAdapterAvailable }
+    condition = { connectionManager.isBthAdapterAvailable }
 ) {
     Box(
         modifier = Modifier.fillMaxSize()

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/EthSettingsPage.kt

@@ -13,7 +13,7 @@ import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 fun EthSettingsPage() = ControlCenterPage(
     icon = Icons.Filled.SettingsEthernet,
     name = "Ethernet",
-    condition = { connection.isEthAdapterAvailable }
+    condition = { connectionManager.isEthAdapterAvailable }
 ) {
     Box(
         modifier = Modifier.fillMaxSize()

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/WifiSettingsPage.kt

@@ -13,7 +13,7 @@ import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 fun WifiSettingsPage() = ControlCenterPage(
     icon = Icons.Filled.Wifi,
     name = "Wifi",
-    condition = { connection.isWifiAdapterAvailable }
+    condition = { connectionManager.isWifiAdapterAvailable }
 ) {
     Box(
         modifier = Modifier.fillMaxSize()

+ 13 - 10
src/main/kotlin/eu/mjdev/desktop/components/custom/Clock.kt

@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -13,6 +14,7 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.sp
 import eu.mjdev.desktop.components.text.TextWithShadow
+import eu.mjdev.desktop.extensions.Custom.ParsedList
 import eu.mjdev.desktop.extensions.Custom.dateFlow
 import eu.mjdev.desktop.extensions.Custom.timeFlow
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
@@ -29,7 +31,7 @@ fun Clock(
     dateTextColor: Color = Color.Black,
     showTime: Boolean = true,
     showDate: Boolean = true,
-//    talkEveryHour: Boolean = true,
+    talkEveryHour: Boolean = true,
 ) = withDesktopScope {
     Box(
         modifier = modifier,
@@ -54,15 +56,16 @@ fun Clock(
                 color = dateTextColor
             )
         }
-//        LaunchedEffect(time) {
-//            if (time.isNotEmpty()) {
-//                val parsed = time.split(":")
-//                val isZeroMinutes = (parsed.getOrNull(1) ?: "-") == "00"
-//                if (talkEveryHour && isZeroMinutes) {
-//                    api.ai.talk("It is ${parsed.firstOrNull()} hour")
-//                }
-//            }
-//        }
+        LaunchedEffect(time) {
+            if (time.isNotEmpty()) {
+                val parsed = ParsedList(time, ":")
+                val isZeroSeconds = (parsed.getOrNull(2) ?: "-") == "00"
+                val isZeroMinutes = (parsed.getOrNull(1) ?: "-") == "00"
+                if (talkEveryHour && isZeroMinutes && isZeroSeconds) {
+                    ai.talk("It is ${parsed.firstOrNull()} hour")
+                }
+            }
+        }
     }
 }
 

+ 13 - 28
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -36,7 +36,7 @@ import eu.mjdev.desktop.extensions.Compose.height
 import eu.mjdev.desktop.extensions.Modifier.topShadow
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelExitAnimation
-import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopScope
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindowState
@@ -64,35 +64,20 @@ fun DesktopPanel(
         }
     },
     position: WindowPosition.Aligned = WindowPosition.Aligned(Alignment.BottomCenter),
-    onMenuIconClicked: () -> Unit = {
-        // todo
-    },
-    onMenuIconContextMenuClicked: () -> Unit = {
-        // todo
-    },
+    onMenuIconClicked: () -> Unit = { },
+    onMenuIconContextMenuClicked: () -> Unit = { },
     onFocusChange: (Boolean) -> Unit = {},
-    onAppClick: (App, DesktopProvider) -> Unit = { app, api ->
-        api.appsProvider.startApp(app)
-    },
-    onAppContextMenuClick: (App) -> Unit = {
-        // todo
-    },
-    onLanguageClick: () -> Unit = {
-        // todo
-    },
+    onAppClick: DesktopScope.(App) -> Unit = { app -> startApp(app) },
+    onAppContextMenuClick: (App) -> Unit = { },
+    onLanguageClick: () -> Unit = { },
 ) = withDesktopScope {
     val tooltipState = remember { mutableStateOf<Any?>(null) }
     val panelHeight: (visible: Boolean) -> Dp = { visible ->
-        if (visible)
-            iconSize.height + iconOuterPadding.height + tooltipHeight + panelContentPadding.height
-        else
-            dividerWidth
+        if (visible) iconSize.height + iconOuterPadding.height + tooltipHeight + panelContentPadding.height
+        else dividerWidth
     }
     val size = remember(panelState.isVisible) {
-        DpSize(
-            api.containerSize.width,
-            panelHeight(panelState.isVisible)
-        )
+        DpSize(containerSize.width, panelHeight(panelState.isVisible))
     }
     val windowState: ChromeWindowState = rememberChromeWindowState(position = position, size = size)
     ChromeWindow(
@@ -158,9 +143,9 @@ fun DesktopPanel(
                                 .background(
                                     brush = Brush.verticalGradient(
                                         colors = listOf(
-                                            backgroundColor.copy(alpha = 0.3f),
-                                            backgroundColor.copy(alpha = 0.7f),
-                                            backgroundColor.copy(alpha = 0.8f),
+                                            backgroundColor.alpha(0.3f),
+                                            backgroundColor.alpha(0.7f),
+                                            backgroundColor.alpha(0.8f),
                                         )
                                     )
                                 )
@@ -204,7 +189,7 @@ fun DesktopPanel(
                                     iconPadding = iconPadding,
                                     iconOuterPadding = iconOuterPadding,
                                     onTooltip = { item -> tooltipState.value = item },
-                                    onAppClick = { app -> onAppClick(app, api) },
+                                    onAppClick = { app -> onAppClick(app) },
                                     onContextMenuClick = onAppContextMenuClick
                                 )
                                 DesktopPanelLanguage(

+ 3 - 5
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt

@@ -21,13 +21,11 @@ import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.noElevation
 import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.extensions.Modifier.clipRect
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @OptIn(ExperimentalComposeUiApi::class)
 @Composable
 fun DesktopPanelIcon(
-    api: DesktopProvider = LocalDesktop.current,
     app: App? = null,
     isRunning: Boolean = false,
     isStarting: Boolean = false,
@@ -44,10 +42,10 @@ fun DesktopPanelIcon(
     onToolTip: (item: Any?) -> Unit = {},
     onClick: () -> Unit = {},
     onContextMenuClick: () -> Unit = {}
-) {
+) = withDesktopScope {
     val materialIcon = remember(app, icon) {
         val iconName = app?.name ?: icon // todo better guess
-        api.currentUser.theme.iconSet.iconForName(iconName) ?: "?".toInt()
+        iconSet.iconForName(iconName) ?: "?".toInt()
     }
     val background = when {
         iconState.value -> iconBackgroundHover

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/file/FolderIcon.kt

@@ -48,7 +48,7 @@ fun FolderIcon(
             }
         }
     }
-    val iconId: Int = remember(path) { api.currentUser.theme.iconSet.iconForName(iconName) ?: "?".toInt() }
+    val iconId: Int = remember(path) { iconSet.iconForName(iconName) ?: "?".toInt() }
     val computedSize = remember(path) { iconSize + 2.dp }
     DraggableView(
         modifier = Modifier.wrapContentSize(),

+ 1 - 3
src/main/kotlin/eu/mjdev/desktop/data/App.kt

@@ -8,7 +8,7 @@ import eu.mjdev.desktop.helpers.system.Shell
 import eu.mjdev.desktop.provider.DesktopProvider
 import java.io.File
 
-@Suppress("unused", "MemberVisibilityCanBePrivate", "HasPlatformType")
+@Suppress("unused", "MemberVisibilityCanBePrivate")
 class App(
     val file: File = File("nonexistent_file_path"),
     val desktopFile: DesktopFile = DesktopFile(file),
@@ -81,12 +81,10 @@ class App(
             .apply {
                 triggerStarted()
                 println("app started")
-                println("system windows : ${api.windows.allSystemWindows}")
             }
             .onExit {
                 triggerStop()
                 println("app stopped")
-                println("system windows : ${api.windows.allSystemWindows}")
             }
     }.onFailure { error ->
         triggerStop(error)

+ 34 - 10
src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt

@@ -1,5 +1,7 @@
 package eu.mjdev.desktop.data
 
+import eu.mjdev.desktop.extensions.Custom.ParsedBoolean
+import eu.mjdev.desktop.extensions.Custom.ParsedList
 import org.ini4j.Ini
 import java.io.File
 
@@ -17,38 +19,54 @@ class DesktopFile(
         get() = file.name
 
     val type
-        get() = props?.get(Prop_Type) ?: ""
+        get() = DesktopFileType(props?.get(Prop_Type))
 
     val version
-        get() = props?.get(Prop_Version) ?: ""
+        get() = props?.get(Prop_Version).orEmpty()
 
     val name
-        get() = props?.get(Prop_Name) ?: ""
+        get() = props?.get(Prop_Name).orEmpty()
 
     val comment
-        get() = props?.get(Prop_Comment) ?: ""
+        get() = props?.get(Prop_Comment).orEmpty()
 
     val path
-        get() = props?.get(Prop_Path) ?: ""
+        get() = props?.get(Prop_Path).orEmpty()
 
     val exec
-        get() = props?.get(Prop_Exec) ?: ""
+        get() = props?.get(Prop_Exec).orEmpty()
 
     val icon
-        get() = props?.get(Prop_Icon) ?: ""
+        get() = props?.get(Prop_Icon).orEmpty()
 
     val categories
-        get() = (props?.get(Prop_Categories) ?: "").split(";").toList()
+        get() = ParsedList(props?.get(Prop_Categories))
 
     val notifyOnStart
-        get() = (props?.get(Prop_StartupNotify) ?: "false").asBoolean()
+        get() = ParsedBoolean(props?.get(Prop_StartupNotify))
 
     val runInTerminal
-        get() = (props?.get(Prop_Terminal) ?: "false").asBoolean()
+        get() = ParsedBoolean(props?.get(Prop_Terminal))
 
     val absolutePath: String
         get() = file.absolutePath
 
+    enum class DesktopFileType(
+        val header: String
+    ) {
+        Unknown(""),
+        Application("Application"),
+        Theme("X-GNOME-Metatheme");
+
+        companion object {
+            operator fun invoke(
+                value: String?
+            ): DesktopFileType = value.orEmpty().trim().let { v ->
+                entries.firstOrNull { e -> e.header.contentEquals(v, true) } ?: Unknown
+            }
+        }
+    }
+
     companion object {
         const val Prop_Type = "Type" // string "Application"
         const val Prop_Version = "Version" // string
@@ -63,6 +81,12 @@ class DesktopFile(
         const val Prop_GenericName = "GenericName"
         const val Prop_MimeType = "MimeType" // string delimited ;
         const val Prop_Actions = "Actions"
+        const val Prop_DBusActivatable = "DBusActivatable"
+
+        const val Prop_X_GNOME_UsesNotifications = "X-GNOME-UsesNotifications"
+        const val Prop_X_Purism_FormFactor = "X-Purism-FormFactor"
+        const val Prop_X_Unity_IconBackgroundColor = "X-Unity-IconBackgroundColor"
+        const val Prop_X_Ubuntu_Gettext_Domain = "X-Ubuntu-Gettext-Domain"
 
         private fun String.asBoolean(): Boolean = when (this.lowercase()) {
             "true" -> true

+ 9 - 0
src/main/kotlin/eu/mjdev/desktop/data/DesktopWindow.kt

@@ -0,0 +1,9 @@
+package eu.mjdev.desktop.data
+
+data class DesktopWindow(
+    val id: Long,
+    val pid: Long,
+    val name: String,
+    val command: String,
+    val iconName: String,
+)

+ 17 - 0
src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt

@@ -15,8 +15,25 @@ import java.text.DateFormat
 import java.util.*
 import kotlin.jvm.optionals.getOrNull
 
+@Suppress("FunctionName")
 object Custom {
 
+    fun ParsedList(
+        value: String?,
+        delimiter: String = ";"
+    ): List<String> = value?.split(delimiter)?.toList() ?: emptyList()
+
+    fun ParsedBoolean(
+        value: String?,
+        defaultValue: Boolean = false
+    ) = value.orEmpty().trim().lowercase().let { b ->
+        when (b) {
+            "true" -> true
+            "1" -> true
+            else -> defaultValue
+        }
+    }
+
     fun NativePaint.setMaskFilter(
         blurRadius: Float
     ) {

+ 3 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/managers/KCEFHelper.kt

@@ -3,6 +3,7 @@ package eu.mjdev.desktop.helpers.managers
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import dev.datlag.kcef.KCEF
+import eu.mjdev.desktop.provider.DesktopProvider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -10,7 +11,8 @@ import java.io.File
 import kotlin.math.max
 
 class KCEFHelper(
-    private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
+    private val api: DesktopProvider,
+    private val scope: CoroutineScope = api.scope,
     val initialized: MutableState<Boolean> = mutableStateOf(false),
     val restartRequired: MutableState<Boolean> = mutableStateOf(false),
     val downloading: MutableState<Int> = mutableStateOf(0)

+ 0 - 453
src/main/kotlin/eu/mjdev/desktop/helpers/managers/WindowsManager.kt

@@ -1,453 +0,0 @@
-package eu.mjdev.desktop.helpers.managers
-
-import com.sun.jna.Library
-import com.sun.jna.Native
-import com.sun.jna.NativeLong
-import com.sun.jna.Pointer
-import com.sun.jna.platform.unix.X11
-import com.sun.jna.platform.unix.X11.*
-import com.sun.jna.ptr.IntByReference
-import com.sun.jna.ptr.NativeLongByReference
-import com.sun.jna.ptr.PointerByReference
-import kotlin.jvm.optionals.getOrNull
-
-@Suppress("LocalVariableName", "FunctionName", "SameParameterValue", "unused")
-class WindowsManager {
-    private val x11: X11 by lazy { Native.load("X11", X11::class.java) }
-    private val x11Ext by lazy { Native.load("X11", X11Ext::class.java) }
-
-    @Suppress("unused")
-    private val xmu by lazy { Native.load("Xmu", Xmu::class.java) }
-    private val display: Display by lazy { x11.XOpenDisplay(null) }
-    private val rootWindow: Window
-        get() = x11.XDefaultRootWindow(display)
-    val allSystemWindows: List<SystemWindow>
-        get() = getAllWindows(rootWindow, 0)
-
-    @Suppress("DEPRECATED_IDENTITY_EQUALS")
-    private fun getAllWindows(root: Window, depth: Int): List<SystemWindow> {
-        val windowList = mutableListOf<SystemWindow>()
-        val windowRef = WindowByReference()
-        val parentRef = WindowByReference()
-        val childrenRef = PointerByReference()
-        val childCountRef = IntByReference()
-        x11.XQueryTree(display, root, windowRef, parentRef, childrenRef, childCountRef)
-        if (childrenRef.value == null) return emptyList()
-        val ids: LongArray
-        if (Native.LONG_SIZE === java.lang.Long.BYTES) {
-            ids = childrenRef.value.getLongArray(0, childCountRef.value)
-        } else if (Native.LONG_SIZE === Integer.BYTES) {
-            val intIds: IntArray = childrenRef.value.getIntArray(0, childCountRef.value)
-            ids = LongArray(intIds.size)
-            for (i in intIds.indices) {
-                ids[i] = intIds[i].toLong()
-            }
-        } else return emptyList()
-        for (id in ids) {
-            if (id == 0L) continue
-            Window(id).also { window ->
-                val pid = getWindowPid(window) ?: 0L
-                val name = getWindowName(window) ?: ""
-                val cls = getWindowClass(window) ?: ""
-                val desk = getWindowDesktop(window) ?: 0L
-                val cmd = getWindowCommand(pid) ?: ""
-                val icon = getWindowIconName(window) ?: ""
-                windowList.add(SystemWindow(id, name, icon, pid, cls, desk, cmd))
-                getAllWindows(window, depth + 1)
-            }
-        }
-        return windowList
-    }
-
-    private fun getWindowsByPids(
-        pids: List<Long>
-    ): List<SystemWindow> = allSystemWindows.filter { pids.contains(it.pid) }
-
-    private fun getWindowPid(
-        window: Window
-    ) = get_property_as_long(
-        window,
-        XA_CARDINAL,
-        "_NET_WM_PID"
-    )
-
-    private fun getWindowName(
-        window: Window
-    ) = get_property_as_utf8_string(
-        window,
-        x11.XInternAtom(display, "UTF8_STRING", false),
-        "_NET_WM_NAME"
-    )
-
-    private fun getWindowClass(
-        window: Window
-    ) = get_property_as_utf8_string(
-        window,
-        x11.XInternAtom(display, "UTF8_STRING", false),
-        "WM_CLASS"
-    )
-
-    private fun getWindowCommand(
-        pid: Long
-    ): String? = ProcessHandle.allProcesses().toList().firstOrNull {
-        it.pid() == pid
-    }?.info()?.command()?.getOrNull()
-
-    private fun getWindowDesktop(
-        window: Window
-    ) = get_property_as_long(
-        window,
-        XA_CARDINAL,
-        "_NET_SHOWING_DESKTOP"
-    )
-
-    // todo
-    private fun getWindowIconName(
-        @Suppress("UNUSED_PARAMETER") window: Window
-    ): String? {
-//        val icon_name_return = PointerByReference()
-//        if (x11Ext.XGetIconName(display, window, icon_name_return) == 0) {
-//            g_free(icon_name_return.pointer)
-//            return null
-//        }
-//        return g_strdup(icon_name_return.pointer)
-        return null
-    }
-
-    fun minimizeWindow(window: Window): Boolean =
-        x11Ext.XIconifyWindow(display, window, x11.XDefaultScreen(display)) == TRUE
-
-    @Suppress("UNUSED_PARAMETER")
-    fun maximizeWindow(
-        window: Window
-    ) {
-        // todo
-    }
-
-    fun activateWindow(
-        window: Window,
-        switchDesktop: Boolean = true
-    ) = activate_window(window, switchDesktop)
-
-    fun closeWindow(
-        window: Window
-    ) = client_msg(window, "_NET_CLOSE_WINDOW", 0, 0, 0, 0, 0)
-
-    fun isWindowActive(
-        pids: List<Long>
-    ): Boolean = get_active_window()?.let { aw ->
-        getWindowsByPids(pids).any {
-            it.id == aw.toLong()
-        }
-    } ?: false
-
-    private fun g_free(
-        pointer: Pointer?
-    ) {
-        if (pointer != null) x11.XFree(pointer)
-    }
-
-    private fun get_property_as_long(
-        window: Window,
-        xa_prop_type: Atom,
-        prop_name: String
-    ): Long? = get_property_as_long(window, xa_prop_type, prop_name, null)
-
-    private fun get_property_as_long(
-        window: Window,
-        xa_prop_type: Atom,
-        prop_name: String,
-        size: NativeLongByReference?
-    ): Long? {
-        var longProp: Long? = null
-        val prop = get_property(window, xa_prop_type, prop_name, size)
-        if (prop != null) {
-            longProp = prop.getLong(0)
-//            g_free(prop)
-        }
-        return longProp
-    }
-
-    private fun get_property_as_int(
-        win: Window,
-        xa_prop_type: Atom,
-        prop_name: String
-    ): Int? {
-        var intProp: Int? = null
-        val prop = get_property(win, xa_prop_type, prop_name, null)
-        if (prop != null) {
-            intProp = prop.getInt(0)
-//            g_free(prop)
-        }
-        return intProp
-    }
-
-    private fun g_strdup(
-        pointer: Pointer
-    ): String {
-        val value = pointer.getString(0)
-        // g_free(pointer);
-        return value
-    }
-
-    private fun g_locale_to_utf8(
-        pointer: Pointer
-    ): String = g_strdup(pointer)
-
-    private fun get_property_as_utf8_string(
-        window: Window,
-        xa_prop_type: Atom,
-        prop_name: String
-    ): String? {
-        var strProp: String? = null
-        val prop = get_property(window, xa_prop_type, prop_name, null)
-        if (prop != null) {
-            strProp = g_locale_to_utf8(prop)
-//            g_free(prop)
-        }
-        return strProp
-    }
-
-    private fun get_property_as_window(
-        window: Window,
-        xa_prop_type: Atom,
-        prop_name: String
-    ): Window? {
-        var ret: Window? = null
-        val prop = get_property(window, xa_prop_type, prop_name, null)
-        if (prop != null) {
-            ret = Window(prop.getLong(0))
-//            g_free(prop)
-        }
-        return ret
-    }
-
-    private fun get_property(
-        window: Window?,
-        xa_prop_type: Atom,
-        prop_name: String?,
-        size: NativeLongByReference?
-    ): Pointer? {
-        val xa_ret_type = AtomByReference()
-        val ret_format = IntByReference()
-        val ret_n_items = NativeLongByReference()
-        val ret_bytes_after = NativeLongByReference()
-        val ret_prop = PointerByReference()
-        val xa_prop_name = x11.XInternAtom(display, prop_name, false)
-        if (x11.XGetWindowProperty(
-                display,
-                window,
-                xa_prop_name,
-                NativeLong(0),
-                NativeLong((MAX_PROPERTY_VALUE_LEN / 4).toLong()),
-                false,
-                xa_prop_type,
-                xa_ret_type,
-                ret_format,
-                ret_n_items,
-                ret_bytes_after,
-                ret_prop
-            ) != Success
-        ) {
-            return null
-        }
-        if ((xa_ret_type.value == null) || (xa_ret_type.value.toLong() != xa_prop_type.toLong())
-        ) {
-//            g_free(ret_prop.pointer)
-            return null
-        }
-        if (size != null) {
-            var tmp_size = ((ret_format.value / 8) * ret_n_items.value.toLong())
-            if (ret_format.value == 32) {
-                tmp_size *= (NativeLong.SIZE / 4).toLong()
-            }
-            size.value = NativeLong(tmp_size)
-        }
-        return ret_prop.value
-    }
-
-    private fun client_msg(
-        window: Window,
-        msg: String,
-        data0: Long,
-        data1: Long,
-        data2: Long,
-        data3: Long,
-        data4: Long
-    ): Boolean {
-        val mask = NativeLong((SubstructureRedirectMask or SubstructureNotifyMask).toLong())
-        val xclient = XClientMessageEvent()
-        xclient.type = ClientMessage
-        xclient.serial = NativeLong(0)
-        xclient.send_event = TRUE
-        xclient.message_type = x11.XInternAtom(display, msg, false)
-        xclient.window = window
-        xclient.format = 32
-        xclient.data.setType(Array<NativeLong>::class.java)
-        xclient.data.l[0] = NativeLong(data0)
-        xclient.data.l[1] = NativeLong(data1)
-        xclient.data.l[2] = NativeLong(data2)
-        xclient.data.l[3] = NativeLong(data3)
-        xclient.data.l[4] = NativeLong(data4)
-        val event = XEvent()
-        event.setTypedValue(xclient)
-        return if (x11.XSendEvent(
-                display,
-                x11.XDefaultRootWindow(display),
-                FALSE,
-                mask,
-                event
-            ) != FALSE
-        ) EXIT_SUCCESS else EXIT_FAILURE
-    }
-
-    private fun activate_window(
-        window: Window,
-        switch_desktop: Boolean = true
-    ): Boolean {
-        var desktop: Int? = get_property_as_int(window, XA_CARDINAL, "_NET_WM_DESKTOP")
-        if (desktop == null) {
-            desktop = get_property_as_int(window, XA_CARDINAL, "_WIN_WORKSPACE")
-        }
-        if (switch_desktop && (desktop != null)) {
-            client_msg(
-                x11.XDefaultRootWindow(display),
-                "_NET_CURRENT_DESKTOP",
-                desktop.toLong(),
-                0,
-                0,
-                0,
-                0
-            )
-        }
-        client_msg(window, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0)
-        x11.XMapRaised(display, window)
-        return EXIT_SUCCESS
-    }
-
-    private fun get_active_window(): Window? = get_property_as_window(
-        x11.XDefaultRootWindow(display),
-        XA_WINDOW,
-        "_NET_ACTIVE_WINDOW"
-    )
-
-    fun dispose() {
-        x11.XCloseDisplay(display)
-    }
-
-    companion object {
-        const val MAX_PROPERTY_VALUE_LEN = 4096
-        const val TRUE: Int = 1
-        const val FALSE: Int = 0
-        const val EXIT_SUCCESS: Boolean = true
-        const val EXIT_FAILURE: Boolean = false
-    }
-
-    data class SystemWindow(
-        val id: Long,
-        val name: String,
-        val iconName: String,
-        val pid: Long,
-        val windowClass: String,
-        val desktop: Long,
-        val command: String,
-    ) {
-        companion object {
-            val SystemWindow.window: Window get() = Window(id)
-
-            fun SystemWindow.toFront(wmanager: WindowsManager) {
-                wmanager.activateWindow(window)
-            }
-
-            fun SystemWindow.minimize(wmanager: WindowsManager) {
-                wmanager.minimizeWindow(window)
-            }
-
-            fun SystemWindow.maximize(wmanager: WindowsManager) {
-                wmanager.maximizeWindow(window)
-            }
-
-            fun SystemWindow.close(wmanager: WindowsManager) {
-                wmanager.closeWindow(window)
-            }
-        }
-    }
-
-    private interface Xmu : Library {
-        fun XmuClientWindow(display: Display?, window: Window?): Window?
-    }
-
-    private interface X11Ext : Library {
-        fun XMoveWindow(
-            display: Display?,
-            window: Window?,
-            x: Long,
-            y: Long
-        )
-
-        fun XResizeWindow(
-            display: Display?,
-            window: Window?,
-            width: Long,
-            height: Long
-        )
-
-        fun XMoveResizeWindow(
-            display: Display?,
-            window: Window?,
-            x: Long,
-            y: Long,
-            width: Long,
-            height: Long
-        )
-
-        fun XCreateFontCursor(
-            display: Display?,
-            shape: Long
-        ): Cursor?
-
-        fun XGrabPointer(
-            display: Display?,
-            grab_window: Window?,
-            owner_events: Int,
-            event_mask: NativeLong?,
-            pointer_mode: Int,
-            keyboard_mode: Int,
-            confine_to: Window?,
-            cursor: Cursor?,
-            time: Int
-        ): Int
-
-        fun XAllowEvents(
-            display: Display?,
-            event_mode: Int,
-            time: Int
-        ): Int
-
-        fun XUngrabPointer(
-            display: Display?,
-            time: Int
-        ): Int
-
-        fun XmuClientWindow(
-            display: Display?,
-            window: Window?
-        ): Window?
-
-        fun XGetIconName(
-            display: Display?,
-            window: Window?,
-            icon_name_return: PointerByReference?
-        ): Int
-
-        fun XIconifyWindow(
-            display: Display?,
-            window: Window?,
-            screen: Int
-        ): Int
-
-//        companion object {
-//            const val XC_CROSS_HAIR: Int = 34
-//        }
-    }
-}

+ 10 - 2
src/main/kotlin/eu/mjdev/desktop/provider/AIProvider.kt

@@ -11,7 +11,7 @@ import kotlinx.coroutines.launch
 import nl.marc_apps.tts.TextToSpeechFactory
 import nl.marc_apps.tts.experimental.ExperimentalDesktopTarget
 
-@Suppress("MemberVisibilityCanBePrivate", "unused", "UNUSED_PARAMETER", "RemoveEmptyClassBody")
+@Suppress("MemberVisibilityCanBePrivate", "unused", "UNUSED_PARAMETER")
 class AIProvider(
     val scope: CoroutineScope,
     var pluginAI: IAIPlugin = AiPluginNull(scope),
@@ -35,7 +35,12 @@ class AIProvider(
         suspend fun ask(question: String): String
     }
 
+    interface STTListener {
+        fun onTextRecognized(text: String)
+    }
+
     interface ISTTPlugin {
+        val listeners: List<STTListener>
     }
 
     interface TTSPlugin {
@@ -43,6 +48,7 @@ class AIProvider(
     }
 
     class STTPluginNull(scope: CoroutineScope) : ISTTPlugin {
+        override val listeners: List<STTListener> = mutableListOf()
     }
 
     @OptIn(ExperimentalDesktopTarget::class)
@@ -54,6 +60,7 @@ class AIProvider(
         }
 
         override fun talk(text: String, clearQueue: Boolean) {
+            println("talking: $text")
             scope.launch {
                 textToSpeech.await()?.say(text, clearQueue)
             }
@@ -64,6 +71,7 @@ class AIProvider(
         val scope: CoroutineScope
     ) : TTSPlugin {
         override fun talk(text: String, clearQueue: Boolean) {
+            println("talking: $text")
             scope.launch(Dispatchers.IO) {
                 Shell.executeAndRead(
                     "/opt/swift/bin/swift",
@@ -94,7 +102,7 @@ class AIProvider(
             var error: Throwable? = null
             runCatching {
                 if (key.isEmpty()) {
-                    throw(Exception("Error: No gemini api key provided, pleas read manual and provide Your api key."))
+                    throw (Exception("Error: No gemini api key provided, pleas read manual and provide Your api key."))
                 } else {
                     generativeModel?.generateContent(content {
                         text(question)

+ 14 - 11
src/main/kotlin/eu/mjdev/desktop/provider/AppsProvider.kt

@@ -139,16 +139,19 @@ class AppsProvider(
 
     val favoriteApps = mutableStateListOf<App>()
 
-    fun startApp(app: App) {
+    fun startApp(
+        app: App,
+        windowsManager: WindowsManager = api.windowsManager
+    ) {
         scope.launch(Dispatchers.IO) {
-//            if (app.isRunning) {
-//                // todo menu to switch
-//                if (app.isWindowFocus(api)) {
-//                    app.minimizeWindow(api)
-//                } else {
-//                    app.requestWindowFocus(api)
-//                }
-//            } else {
+            if (app.isRunning) {
+                // todo menu to switch
+                if (windowsManager.isWindowFocus(app)) {
+                    windowsManager.minimizeWindow(app)
+                } else {
+                    windowsManager.requestWindowFocus(app)
+                }
+            } else {
                 app.onStarting {
                     if (favoriteApps.contains(app)) {
                         favoriteApps.invalidate()
@@ -167,8 +170,8 @@ class AppsProvider(
                         result.printStackTrace()
                     }
                 }.start(api)
-                favoriteApps.invalidate()
-//            }
+            }
+            favoriteApps.invalidate()
         }
     }
 

+ 30 - 14
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -9,6 +9,8 @@ import androidx.compose.ui.unit.dp
 import coil3.ImageLoader
 import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import eu.mjdev.desktop.components.controlcenter.pages.*
+import eu.mjdev.desktop.data.App
+import eu.mjdev.desktop.data.DesktopFile
 import eu.mjdev.desktop.data.User
 import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
 import eu.mjdev.desktop.helpers.adb.AdbDiscover.Companion.adbDevicesHandler
@@ -19,6 +21,7 @@ import eu.mjdev.desktop.helpers.system.DBus
 import eu.mjdev.desktop.helpers.system.OsRelease
 import eu.mjdev.desktop.helpers.system.Shell
 import eu.mjdev.desktop.provider.AIProvider.AiPluginGemini
+import eu.mjdev.desktop.theme.gtk.GtkTheme
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import java.awt.*
@@ -43,10 +46,10 @@ class DesktopProvider(
     }
     val osDetails by lazy { OsRelease() }
     val toolkit: Toolkit by lazy { Toolkit.getDefaultToolkit() }
-    val kcefHelper: KCEFHelper by lazy { KCEFHelper(scope) }
+    val kcefHelper: KCEFHelper by lazy { KCEFHelper(this) }
     val gnome: GnomeManager by lazy { GnomeManager() }
-    val connection: ConnectivityManager by lazy { ConnectivityManager() }
-    val windows: WindowsManager by lazy { WindowsManager() }
+    val connectionManager: ConnectivityManager by lazy { ConnectivityManager() }
+    val windowsManager: WindowsManager by lazy { WindowsManager(this) }
     val dbus: DBus by lazy { DBus() }
     val ai: AIProvider by lazy {
         // todo user can configure
@@ -58,6 +61,7 @@ class DesktopProvider(
             println("Mounted: ${this.targetDirectory}")
         }
     }
+    val gtkTheme: GtkTheme by lazy { GtkTheme(this) }
     val palette: Palette by lazy { Palette(scope, currentUser.theme.backgroundColor) }
     val homeDir: File
         get() = currentUser.homeDir
@@ -90,14 +94,13 @@ class DesktopProvider(
         get() = runCatching {
             toolkit.isAlwaysOnTopSupported
         }.getOrNull() ?: false
-//    val cursorSize
+
+    //    val cursorSize
 //        get() = toolkit.getBestCursorSize(32, 32).let {
 //            DpSize(it.width.dp, it.height.dp)
 //        }
-    val desktopUtils: Desktop? by lazy {
-        if (Desktop.isDesktopSupported()) {
-            Desktop.getDesktop()
-        } else null
+    val desktopUtils: Desktop by lazy {
+        Desktop.getDesktop()
     }
     val controlCenterPages
         get() = controlCenterPagesState.value
@@ -109,6 +112,9 @@ class DesktopProvider(
         get() = graphicsDevice.isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency.TRANSLUCENT)
 
     init {
+        runCatching {
+            windowsManager.init()
+        }
         runCatching {
             currentUser.theme.controlCenterExpandedWidth = containerSize.width.div(4)
         }
@@ -137,24 +143,24 @@ class DesktopProvider(
             kcefHelper.dispose()
         }
         runCatching {
-            windows.dispose()
+            windowsManager.dispose()
         }
     }
 
     fun openMail(emailAddress: String) = runCatching {
-        desktopUtils?.mail(URI.create("mailto:$emailAddress"))
+        desktopUtils.mail(URI.create("mailto:$emailAddress"))
     }
 
     fun openBrowser(url: String) = runCatching {
-        desktopUtils?.browse(URI.create(url))
+        desktopUtils.browse(URI.create(url))
     }
 
     fun openDirectoryForFile(path: String) = runCatching {
-        desktopUtils?.browseFileDirectory(File(path))
+        desktopUtils.browseFileDirectory(File(path))
     }
 
     fun moveToTrash(file: File) = runCatching {
-        desktopUtils?.moveToTrash(file)
+        desktopUtils.moveToTrash(file)
     }
 
     fun moveToTrash(filePath: String) = runCatching {
@@ -162,7 +168,7 @@ class DesktopProvider(
     }
 
     fun openFileInAssociated(file: File) = runCatching {
-        desktopUtils?.open(file)
+        desktopUtils.open(file)
     }
 
     fun openFileInAssociated(filePath: String) = runCatching {
@@ -255,5 +261,15 @@ class DesktopProvider(
                 isDebug
             )
         }
+
+        fun Desktop.startApp(app: App) {
+            startApp(app.desktopFile)
+        }
+
+        fun Desktop.startApp(
+            desktopFile: DesktopFile
+        ) {
+            this.open(desktopFile.file)
+        }
     }
 }

+ 76 - 0
src/main/kotlin/eu/mjdev/desktop/provider/DesktopScope.kt

@@ -1,6 +1,7 @@
 package eu.mjdev.desktop.provider
 
 import androidx.compose.runtime.Composable
+import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 // todo theme user support
@@ -14,9 +15,81 @@ open class DesktopScope(
     val scope
         get() = api.scope
 
+    val containerSize
+        get() = api.containerSize
+
+    val graphicsDevice
+        get() = api.graphicsDevice
+
+    val graphicsEnvironment
+        get() = api.graphicsEnvironment
+
+    val isTransparencySupported
+        get() = api.isTransparencySupported
+
+    val osDetails
+        get() = api.osDetails
+
+    val machineName
+        get() = api.machineName
+
     val currentUser
         get() = api.currentUser
 
+    val homeDir
+        get() = api.homeDir
+
+    val theme
+        get() = currentUser.theme
+
+    val iconSet
+        get() = theme.iconSet
+
+    val ai
+        get() = api.ai
+
+    val adb
+        get() = api.adbHandler
+
+    val imageLoader
+        get() = api.imageLoader
+
+    val scriptManager
+        get() = api.scriptManager
+
+    val scriptEngine
+        get() = api.scriptEngine
+
+    val connectionManager
+        get() = api.connectionManager
+
+    val windowsManager
+        get() = api.windowsManager
+
+    val dbus
+        get() = api.dbus
+
+    val gnome
+        get() = api.gnome
+
+    val appsProvider
+        get() = api.appsProvider
+
+    val appCategories
+        get() = appsProvider.appCategories
+
+    val appCategoriesAndApps
+        get() = appsProvider.categoriesAndApps
+
+    val palette
+        get() = api.palette
+
+    val desktopUtils
+        get() = api.desktopUtils
+
+    val controlCenterPages
+        get() = api.controlCenterPages
+
     val backgroundColorState
         get() = api.palette.backgroundColorState
     val backgroundColor
@@ -32,6 +105,9 @@ open class DesktopScope(
     val borderColor
         get() = api.palette.borderColor
 
+    fun startApp(app: App) =
+        api.appsProvider.startApp(app)
+
     companion object {
         @Composable
         fun withDesktopScope(

+ 70 - 0
src/main/kotlin/eu/mjdev/desktop/provider/WindowsManager.kt

@@ -0,0 +1,70 @@
+package eu.mjdev.desktop.provider
+
+import androidx.compose.runtime.mutableStateListOf
+import eu.mjdev.desktop.data.App
+import eu.mjdev.desktop.data.DesktopWindow
+import java.awt.Desktop
+import java.awt.desktop.*
+
+// todo
+@Suppress("UNUSED_PARAMETER", "unused")
+class WindowsManager(
+    val api: DesktopProvider,
+    val desktop: Desktop = api.desktopUtils
+) : AppForegroundListener, AppHiddenListener, AppReopenedListener {
+    private val windows = mutableStateListOf<DesktopWindow>()
+
+    fun init() {
+        desktop.addAppEventListener(this)
+    }
+
+    fun dispose() {
+        desktop.removeAppEventListener(this)
+    }
+
+    override fun appRaisedToForeground(
+        e: AppForegroundEvent?
+    ) {
+        println(e)
+    }
+
+    override fun appMovedToBackground(
+        e: AppForegroundEvent?
+    ) {
+        println(e)
+    }
+
+    override fun appHidden(
+        e: AppHiddenEvent?
+    ) {
+        println(e)
+    }
+
+    override fun appUnhidden(
+        e: AppHiddenEvent?
+    ) {
+        println(e)
+    }
+
+    override fun appReopened(
+        e: AppReopenedEvent?
+    ) {
+        println(e)
+    }
+
+    fun isWindowFocus(
+        app: App
+    ) = false
+
+    fun minimizeWindow(
+        app: App
+    ) = false
+
+    fun maximizeWindow(
+        app: App
+    ) = false
+
+    fun requestWindowFocus(
+        app: App
+    ) = false
+}

+ 5 - 5
src/main/kotlin/eu/mjdev/desktop/theme/gtk/GtkTheme.kt

@@ -6,12 +6,12 @@ import java.io.File
 
 @Suppress("CanBeParameter", "unused")
 class GtkTheme(
-    val themeName: String = "mjdev",
     val api: DesktopProvider,
-    val themesDir: File = File(api.homeDir, ".themes"),
-    val themeDir: File = File(themesDir, themeName),
-    val themeDesktopFile: File = File(themeDir, "index.theme"),
-    val themeCssFile: File = File(themeDir, "gtk.css")
+    private val themeName: String = "mjdev",
+    private val themesDir: File = File(api.homeDir, ".themes"),
+    private val themeDir: File = File(themesDir, themeName),
+    private val themeDesktopFile: File = File(themeDir, "index.theme"),
+    private val themeCssFile: File = File(themeDir, "gtk.css")
 ) {
     val desktopFile: DesktopFile = DesktopFile.create(themeDesktopFile) // todo