Bläddra i källkod

code improvements

Milan Jurkulak 1 år sedan
förälder
incheckning
bddbd31796
77 ändrade filer med 1355 tillägg och 876 borttagningar
  1. 2 0
      build.gradle.kts
  2. 26 12
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt
  3. 3 2
      src/main/kotlin/eu/mjdev/desktop/components/background/BackgroundImage.kt
  4. 61 28
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt
  5. 5 5
      src/main/kotlin/eu/mjdev/desktop/components/desktop/Desktop.kt
  6. 80 35
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  7. 0 2
      src/main/kotlin/eu/mjdev/desktop/components/file/FolderIcon.kt
  8. 14 13
      src/main/kotlin/eu/mjdev/desktop/components/gallery/Gallery.kt
  9. 2 1
      src/main/kotlin/eu/mjdev/desktop/components/greeter/Greeter.kt
  10. 2 1
      src/main/kotlin/eu/mjdev/desktop/components/grid/base/GridPlacedScopeImpl.kt
  11. 0 1
      src/main/kotlin/eu/mjdev/desktop/components/input/PasswordTextView.kt
  12. 32 21
      src/main/kotlin/eu/mjdev/desktop/components/main/MainWindow.kt
  13. 21 30
      src/main/kotlin/eu/mjdev/desktop/components/sliding/base/VisibilityState.kt
  14. 4 4
      src/main/kotlin/eu/mjdev/desktop/components/surface/Surface.kt
  15. 1 0
      src/main/kotlin/eu/mjdev/desktop/components/surface/base/SurfaceGlow.kt
  16. 2 1
      src/main/kotlin/eu/mjdev/desktop/components/video/VideoView.kt
  17. 11 11
      src/main/kotlin/eu/mjdev/desktop/components/video/base/KFFMpeg.kt
  18. 5 4
      src/main/kotlin/eu/mjdev/desktop/components/video/base/VideoPlayerFFMpeg.kt
  19. 6 5
      src/main/kotlin/eu/mjdev/desktop/data/App.kt
  20. 1 3
      src/main/kotlin/eu/mjdev/desktop/data/DesktopConfig.kt
  21. 5 4
      src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt
  22. 3 3
      src/main/kotlin/eu/mjdev/desktop/data/Theme.kt
  23. 8 12
      src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt
  24. 3 2
      src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt
  25. 3 2
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/AdbDiscover.kt
  26. 2 2
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/IAdb.kt
  27. 3 2
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/forwarding/TcpForwarder.kt
  28. 1 0
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbConnection.kt
  29. 2 1
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbReader.kt
  30. 1 0
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbShellResponse.kt
  31. 0 0
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbStreamImpl.kt
  32. 11 10
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbSyncStream.kt
  33. 3 2
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbWriter.kt
  34. 0 10
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/Logging.kt
  35. 0 1
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/MessageQueue.kt
  36. 2 1
      src/main/kotlin/eu/mjdev/desktop/helpers/application/Application.kt
  37. 1 1
      src/main/kotlin/eu/mjdev/desktop/helpers/backgrounds/ProviderErzvo.kt
  38. 1 1
      src/main/kotlin/eu/mjdev/desktop/helpers/backgrounds/ProviderLocal.kt
  39. 1 1
      src/main/kotlin/eu/mjdev/desktop/helpers/backgrounds/ProviderSmug.kt
  40. 9 1
      src/main/kotlin/eu/mjdev/desktop/helpers/compose/DpBounds.kt
  41. 2 1
      src/main/kotlin/eu/mjdev/desktop/helpers/compose/ImagesProvider.kt
  42. 56 0
      src/main/kotlin/eu/mjdev/desktop/helpers/generic/Queue.kt
  43. 0 31
      src/main/kotlin/eu/mjdev/desktop/helpers/internal/Queue.kt
  44. 58 0
      src/main/kotlin/eu/mjdev/desktop/helpers/keyevents/GlobalKeyListener.kt
  45. 21 54
      src/main/kotlin/eu/mjdev/desktop/helpers/keyevents/KeyEventHandler.kt
  46. 23 0
      src/main/kotlin/eu/mjdev/desktop/helpers/keyevents/KeyEventListener.kt
  47. 64 0
      src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/GlobalMouseListener.kt
  48. 61 0
      src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/MouseEventHandler.kt
  49. 26 0
      src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/MouseEventListener.kt
  50. 14 0
      src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/MouseEventType.kt
  51. 18 0
      src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/MouseRange.kt
  52. 9 1
      src/main/kotlin/eu/mjdev/desktop/helpers/palette/Palette.kt
  53. 5 7
      src/main/kotlin/eu/mjdev/desktop/helpers/system/Shell.kt
  54. 79 0
      src/main/kotlin/eu/mjdev/desktop/log/Log.kt
  55. 4 3
      src/main/kotlin/eu/mjdev/desktop/managers/artificialintelligence/actions/ActionsProvider.kt
  56. 3 2
      src/main/kotlin/eu/mjdev/desktop/managers/artificialintelligence/tts/TTSPluginMain.kt
  57. 3 2
      src/main/kotlin/eu/mjdev/desktop/managers/artificialintelligence/tts/TTSPluginSwift.kt
  58. 31 20
      src/main/kotlin/eu/mjdev/desktop/managers/connectivity/ConnectivityManager.kt
  59. 81 0
      src/main/kotlin/eu/mjdev/desktop/managers/connectivity/DeviceType.kt
  60. 1 1
      src/main/kotlin/eu/mjdev/desktop/managers/dbus/DBusManager.kt
  61. 20 15
      src/main/kotlin/eu/mjdev/desktop/managers/kcef/linux/KCEFManagerLinux.kt
  62. 3 2
      src/main/kotlin/eu/mjdev/desktop/managers/theme/linux/ThemeManagerLinux.kt
  63. 6 6
      src/main/kotlin/eu/mjdev/desktop/managers/window/WindowsManager.kt
  64. 13 9
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt
  65. 13 0
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopScope.kt
  66. 24 127
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindow.kt
  67. 0 78
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowScope.kt
  68. 136 48
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowState.kt
  69. 7 22
      src/main/kotlin/eu/mjdev/desktop/windows/DesktopWindow.kt
  70. 25 19
      src/main/kotlin/eu/mjdev/desktop/windows/FullScreenWindow.kt
  71. 110 51
      src/main/kotlin/eu/mjdev/desktop/windows/Window.kt
  72. 15 0
      src/main/kotlin/eu/mjdev/desktop/windows/WindowCloseAction.kt
  73. 0 50
      src/main/kotlin/eu/mjdev/desktop/windows/WindowFocusHelper.kt
  74. 46 0
      src/main/kotlin/eu/mjdev/desktop/windows/WindowFocusListener.kt
  75. 0 89
      src/main/kotlin/eu/mjdev/desktop/windows/WindowStateHelper.kt
  76. 38 0
      src/main/kotlin/eu/mjdev/desktop/windows/WindowStateListener.kt
  77. 3 3
      src/main/kotlin/eu/mjdev/desktop/windows/blur/base/HackedContentPane.kt

+ 2 - 0
build.gradle.kts

@@ -128,6 +128,8 @@ allprojects {
         implementation("com.composables:core:1.12.0")
         // events
         implementation("org.rationalityfrontline:kevent:2.3.1")
+        // components
+        implementation("com.composables:core:1.17.1")
         // wayland
 //        implementation("org.freedesktop:wayland-client:1.5.1")
         // paths

+ 26 - 12
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -18,7 +18,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
 import eu.mjdev.desktop.components.blur.BlurPanel
@@ -29,7 +29,6 @@ import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.ColorUtils.darker
 import eu.mjdev.desktop.extensions.Compose.clear
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
-import eu.mjdev.desktop.extensions.Compose.onMouseEnter
 import eu.mjdev.desktop.extensions.Compose.plus
 import eu.mjdev.desktop.extensions.Compose.preview
 import eu.mjdev.desktop.extensions.Compose.rememberCalculated
@@ -44,7 +43,7 @@ import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuExitAnimation
 import eu.mjdev.desktop.helpers.compose.Orientation
 import eu.mjdev.desktop.helpers.compose.rememberForeverLazyListState
-import eu.mjdev.desktop.helpers.internal.KeyEventHandler.Companion.globalKeyEventHandler
+import eu.mjdev.desktop.helpers.keyevents.GlobalKeyListener.Companion.globalKeyEventHandler
 import eu.mjdev.desktop.helpers.shape.BarShape
 import eu.mjdev.desktop.provider.DesktopScope
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
@@ -89,16 +88,31 @@ fun AppsMenu(
             else -> appCategories
         }
     }
-    val position by rememberCalculated {
+    val position by rememberCalculated(
+        containerSize.height,
+        panelState.x,
+        panelState.height,
+        appMenuMinHeight
+    ) {
         WindowPosition.Absolute(
-            panelState.bounds.x,
-            containerSize.height - (panelState.bounds.height + appMenuMinHeight)
+            panelState.x,
+            containerSize.height - (panelState.height + appMenuMinHeight - panelState.tooltipHeight)
         )
     }
-    val windowState: ChromeWindowState = rememberChromeWindowState(position = position)
+    val size by rememberComputed(
+        appMenuMinWidth,
+        appMenuMinHeight,
+        containerSize
+    ) {
+        DpSize(appMenuMinWidth, appMenuMinHeight)
+    }
     globalKeyEventHandler(
         isEnabled = { menuState.isVisible && menuState.enabled }
     ) {
+        onMenuKey {
+            panelState.show()
+            false
+        }
         onEscape {
             menuState.hide()
             true
@@ -121,10 +135,13 @@ fun AppsMenu(
         }
     }
     ChromeWindow(
+        name = "AppsMenu",
         visible = menuState.isVisible,
+        size = size,
+        position = position,
         enterAnimation = enterAnimation,
         exitAnimation = exitAnimation,
-        windowState = windowState,
+        windowState = menuState,
         onFocusChange = onFocusChange
     ) {
         Box(
@@ -142,10 +159,6 @@ fun AppsMenu(
                     4.dp,
                     0.dp
                 )
-                .onPlaced(menuState::onPlaced)
-                .onMouseEnter {
-                    windowState.requestFocus()
-                }
         ) {
             BlurPanel(
                 modifier = Modifier.fillMaxSize()
@@ -265,6 +278,7 @@ fun AppsMenu(
             if (!menuState.isVisible) {
                 category = ""
             }
+            menuState.position = position
         }
     }
 }

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/components/background/BackgroundImage.kt

@@ -14,7 +14,8 @@ import eu.mjdev.desktop.extensions.Compose.Crossfade
 import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
 import eu.mjdev.desktop.extensions.Compose.preview
-import eu.mjdev.desktop.helpers.internal.Queue
+import eu.mjdev.desktop.helpers.generic.Queue
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
@@ -26,7 +27,7 @@ fun BackgroundImage(
     modifier: Modifier = Modifier,
     fadeInDuration: Long = 3000L,
     fadeOutDuration: Long = 5000L,
-    onError: (Throwable) -> Unit = { e -> e.printStackTrace() },
+    onError: (Throwable) -> Unit = { e -> Log.e(e) },
     onChange: (bck: Any?) -> Unit = {}
 ) = withDesktopScope {
     val switchDelay = theme.backgroundRotationDelay

+ 61 - 28
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt

@@ -13,15 +13,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Divider
 import androidx.compose.material.Icon
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
-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.pointerInput
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
@@ -37,7 +35,9 @@ import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.extensions.Compose.runAsync
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterExitAnimation
-import eu.mjdev.desktop.helpers.internal.KeyEventHandler.Companion.globalKeyEventHandler
+import eu.mjdev.desktop.helpers.keyevents.GlobalKeyListener.Companion.globalKeyEventHandler
+import eu.mjdev.desktop.helpers.mouseevents.GlobalMouseListener.Companion.globalMouseEventHandler
+import eu.mjdev.desktop.helpers.mouseevents.MouseRange
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindowState
@@ -47,31 +47,45 @@ import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindow
 @Composable
 fun ControlCenter(
     pagerState: MutableState<Int> = rememberState(0),
-    controlCenterState: ChromeWindowState = rememberChromeWindowState(visible = isDebugInspectorInfoEnabled),
     enterAnimation: EnterTransition = ControlCenterEnterAnimation,
     exitAnimation: ExitTransition = ControlCenterExitAnimation,
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onContextMenuClick: () -> Unit = {}
+    onContextMenuClick: () -> Unit = {},
+    controlCenterState: ChromeWindowState = rememberChromeWindowState(),
 ) = withDesktopScope {
-    // todo remove those
-    val backgroundAlpha by remember { theme.controlCenterBackgroundAlphaState }
-    val controlCenterExpandedWidth: Dp by rememberComputed {
+    val controlCenterExpandedWidth by rememberComputed(
+        theme.controlCenterExpandedWidth,
+        containerSize.width
+    ) {
         (containerSize.width / 100f) * theme.controlCenterExpandedWidth
     }
-    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 {
+    val pagesFiltered by rememberCalculated(controlCenterPages) {
+        controlCenterPages.filter { p -> p.condition.invoke(api) }
+    }
+    val size by rememberCalculated(controlCenterState.isVisible) {
         if (controlCenterState.isVisible) {
             DpSize(controlCenterExpandedWidth, containerSize.height)
         } else {
             DpSize(controlCenterDividerWidth, containerSize.height)
         }
     }
-    val windowState: ChromeWindowState = rememberChromeWindowState(position = position, size = size)
+    val position by rememberComputed(size) {
+        WindowPosition.Absolute(
+            containerSize.width - size.width,
+            0.dp
+        )
+    }
+    val mouseRange by rememberCalculated(
+        containerSize,
+        size
+    ) {
+        MouseRange(
+            x = containerSize.width - size.width,
+            y = 0.dp,
+            width = size.width,
+            height = containerSize.height
+        )
+    }
     globalKeyEventHandler(
         isEnabled = { controlCenterState.isVisible && controlCenterState.enabled }
     ) {
@@ -80,27 +94,44 @@ fun ControlCenter(
             true
         }
     }
+    globalMouseEventHandler {
+        onPointerEnter(mouseRange) {
+            runAsync {
+                println("Pointer enter control center.")
+                controlCenterState.showOrFocus()
+            }
+        }
+    }
     ChromeWindow(
+        name = "ControlCenter",
         visible = true,
-        windowState = windowState,
+        windowState = controlCenterState,
         enterAnimation = enterAnimation,
         exitAnimation = exitAnimation,
-        onFocusChange = onFocusChange
+        onFocusChange = onFocusChange,
+        position = position,
+        size = size,
+        onCreated = {
+            controlCenterState.size = size
+            controlCenterState.position = position
+        }
     ) {
         SlidingPanel(
             modifier = Modifier.fillMaxHeight(),
             orientation = Horizontal,
             state = controlCenterState,
             onPointerEnter = {
-                controlCenterState.show()
-                windowState.requestFocus()
+                runAsync {
+                    controlCenterState.show()
+                    controlCenterState.requestFocus()
+                }
             }
         ) { isVisible ->
             if (!isVisible) {
                 Divider(
                     modifier = Modifier.fillMaxHeight()
                         .width(controlCenterDividerWidth),
-                    color = Color.Transparent,
+                    color = if (isDebug) Color.Red else Color.Transparent,
                     thickness = controlCenterDividerWidth
                 )
             } else {
@@ -112,7 +143,7 @@ fun ControlCenter(
                     modifier = Modifier.fillMaxHeight()
                         .wrapContentSize()
                         .background(
-                            backgroundColor.copy(alpha = backgroundAlpha)
+                            backgroundColor.copy(alpha = controlCenterBackgroundAlpha)
                         ),
                 ) {
                     Row(
@@ -129,14 +160,13 @@ fun ControlCenter(
                                 verticalArrangement = Arrangement.Top,
                                 horizontalAlignment = Alignment.Start
                             ) {
-                                itemsIndexed(pagesFiltered.value) { idx, page ->
-                                    val isSelected = (pagerState.value == idx)
+                                itemsIndexed(pagesFiltered) { idx, page ->
                                     Icon(
                                         modifier = Modifier
                                             .padding(vertical = 8.dp)
                                             .size(controlCenterIconSize)
                                             .background(
-                                                color = if (isSelected) iconsTintColor else borderColor,
+                                                color = if (pagerState.value == idx) iconsTintColor else borderColor,
                                                 shape = RoundedCornerShape(8.dp)
                                             )
                                             .pointerInput(Unit) {
@@ -161,7 +191,7 @@ fun ControlCenter(
                                             },
                                         imageVector = page.icon,
                                         contentDescription = "",
-                                        tint = if (isSelected) backgroundColor else textColor
+                                        tint = if (pagerState.value == idx) backgroundColor else textColor
                                     )
                                 }
                             }
@@ -184,7 +214,7 @@ fun ControlCenter(
                                     Column(
                                         modifier = Modifier.width(controlCenterExpandedWidth),
                                     ) {
-                                        val page = pagesFiltered.value[pagerState.value]
+                                        val page = pagesFiltered[pagerState.value]
                                         Box(modifier = Modifier.weight(1f)) {
                                             page.render()
                                         }
@@ -208,6 +238,9 @@ fun ControlCenter(
             }
         }
     }
+    LaunchedEffect(position, size) {
+        controlCenterState.size = size
+    }
 }
 
 // todo

+ 5 - 5
src/main/kotlin/eu/mjdev/desktop/components/desktop/Desktop.kt

@@ -10,21 +10,21 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.max
 import eu.mjdev.desktop.components.desktop.widgets.MemoryChart
 import eu.mjdev.desktop.components.file.FolderView
-import eu.mjdev.desktop.components.sliding.base.VisibilityState
-import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.extensions.Compose.preview
 import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
+import eu.mjdev.desktop.windows.ChromeWindowState
+import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 
 @Suppress("FunctionName")
 @Preview
 @Composable
 fun Desktop(
     modifier: Modifier = Modifier,
-    panelState: VisibilityState = rememberVisibilityState(),
+    panelState: ChromeWindowState = rememberChromeWindowState(),
 ) = withDesktopScope {
-    val bottomPadding by rememberCalculated(panelState.enabled) {
-        if (panelState.enabled) 0.dp else max(0.dp, panelState.bounds.height)
+    val bottomPadding by rememberCalculated(panelState.enabled, panelState.height) {
+        if (panelState.enabled) 0.dp else max(0.dp, panelState.height)
     }
     BoxWithConstraints(
         modifier = modifier.padding(16.dp)

+ 80 - 35
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -12,14 +12,13 @@ import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Divider
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.DpSize
@@ -39,11 +38,15 @@ import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.ColorUtils.lighter
 import eu.mjdev.desktop.extensions.Compose.height
 import eu.mjdev.desktop.extensions.Compose.preview
-import eu.mjdev.desktop.extensions.Compose.rememberState
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
+import eu.mjdev.desktop.extensions.Compose.rememberComputed
+import eu.mjdev.desktop.extensions.Compose.runAsync
 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.helpers.internal.KeyEventHandler.Companion.globalKeyEventHandler
+import eu.mjdev.desktop.helpers.keyevents.GlobalKeyListener.Companion.globalKeyEventHandler
+import eu.mjdev.desktop.helpers.mouseevents.GlobalMouseListener.Companion.globalMouseEventHandler
+import eu.mjdev.desktop.helpers.mouseevents.MouseRange
 import eu.mjdev.desktop.provider.DesktopScope
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
@@ -63,7 +66,6 @@ fun DesktopPanel(
     tooltipState: TooltipState = rememberTooltipState(),
     enterAnimation: EnterTransition = DesktopPanelEnterAnimation,
     exitAnimation: ExitTransition = DesktopPanelExitAnimation,
-    position: WindowPosition.Aligned = WindowPosition.Aligned(Alignment.BottomCenter),
     onMenuIconClicked: () -> Unit = {},
     onMenuIconContextMenuClicked: () -> Unit = {},
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
@@ -74,20 +76,39 @@ fun DesktopPanel(
     onLanguageClick: () -> Unit = {},
     onClockClick: () -> Unit = {}
 ) = withDesktopScope {
-    val panelDividerWidth by rememberState(theme.panelDividerWidth)
     val panelHeight: (visible: Boolean) -> Dp = { visible ->
         if (visible) {
-            iconSize.height + iconOuterPadding.height + tooltipState.tooltipHeight +
-                    theme.panelContentPadding.times(2) + 8.dp
+            iconSize.height +
+                    iconOuterPadding.height +
+                    tooltipState.tooltipHeight +
+                    theme.panelContentPadding.times(2) +
+                    8.dp
         } else panelDividerWidth
     }
-    val panelSize = remember(panelState.isVisible, panelState.enabled) {
-        DpSize(containerSize.width, panelHeight(panelState.isVisible))
+    val size by rememberComputed(panelState.isVisible, panelState.enabled) {
+        DpSize(
+            containerSize.width,
+            panelHeight(panelState.isVisible)
+        )
+    }
+    val position by rememberComputed(size) {
+        WindowPosition.Absolute(
+            9.dp,
+            containerSize.height - size.height
+        )
+    }
+    val mouseRange by rememberCalculated(
+        containerSize,
+        size,
+        position
+    ) {
+        MouseRange(
+            x = 0.dp,
+            y = containerSize.height - size.height,
+            width = containerSize.width,
+            height = size.height
+        )
     }
-    val windowState: ChromeWindowState = rememberChromeWindowState(
-        position = position, size = panelSize
-    )
-    panelState.updateSize(panelSize - DpSize(0.dp, tooltipState.tooltipHeight))
     globalKeyEventHandler(
         isEnabled = { panelState.isVisible && panelState.enabled }
     ) {
@@ -95,37 +116,57 @@ fun DesktopPanel(
             panelState.hide()
             true
         }
+        onMenuKey {
+            panelState.show()
+            false
+        }
+    }
+    globalMouseEventHandler {
+        onPointerEnter(mouseRange) {
+            runAsync {
+                println("Pointer enter desktop panel.")
+                if (menuState.isNotVisible) {
+                    panelState.showOrFocus()
+                }
+            }
+        }
     }
     ChromeWindow(
-        windowState = windowState,
+        name = "DesktopPanel",
         visible = true,
-        alwaysOnTop = panelState.enabled,
+        position = position,
+        size = size,
         enterAnimation = enterAnimation,
         exitAnimation = exitAnimation,
-        onFocusChange = onFocusChange
+        onFocusChange = onFocusChange,
+        windowState = panelState,
+        onCreated = {
+            panelState.position = position
+            panelState.size = size
+        }
     ) {
         SlidingPanel(
             modifier = Modifier.fillMaxWidth(),
             orientation = Vertical,
             state = panelState,
             onPointerEnter = {
-                panelState.show()
-                if (!menuState.isVisible) {
-                    windowState.requestFocus()
-                }
-            },
-            onPointerLeave = {
-                if (!menuState.isVisible) {
-                    panelState.hide()
+                runAsync {
+                    panelState.show()
+                    if (menuState.isVisible) {
+                        menuState.requestFocus()
+                    } else {
+                        panelState.requestFocus()
+                    }
                 }
             }) { isVisible ->
-            Divider(
-                modifier = Modifier.fillMaxWidth()
-                    .height(panelDividerWidth),
-                color = Color.Transparent,
-                thickness = panelDividerWidth
-            )
-            if (isVisible) {
+            if (!isVisible) {
+                Divider(
+                    modifier = Modifier.fillMaxWidth()
+                        .height(panelDividerWidth),
+                    color = if (isDebug) Color.Red else Color.Transparent,
+                    thickness = panelDividerWidth
+                )
+            } else {
                 TooltipArea(
                     modifier = Modifier.fillMaxWidth()
                         .wrapContentHeight(),
@@ -147,7 +188,7 @@ fun DesktopPanel(
                         BlurPanel(
                             modifier = Modifier.fillMaxWidth()
                                 .wrapContentHeight()
-                                .clip(RoundedCornerShape(panelSize.height.div(2)))
+                                .clip(RoundedCornerShape(size.height.div(2)))
                         ) {
                             Column(
                                 modifier = Modifier.fillMaxWidth()
@@ -161,7 +202,7 @@ fun DesktopPanel(
                                             )
                                         )
                                     )
-                                    .clip(RoundedCornerShape(panelSize.height.div(2)))
+                                    .clip(RoundedCornerShape(size.height.div(2)))
                                     .topShadow(
                                         color = borderColor.alpha(0.3f),
                                         blur = 4.dp
@@ -169,7 +210,7 @@ fun DesktopPanel(
                                         start = 4.dp,
                                         end = 4.dp,
                                         bottom = 4.dp
-                                    ).onPlaced(panelState::onPlaced),
+                                    ),
                             ) {
                                 Divider(
                                     modifier = Modifier.fillMaxWidth()
@@ -228,6 +269,10 @@ fun DesktopPanel(
             }
         }
     }
+    LaunchedEffect(size, position) {
+        panelState.tooltipHeight = tooltipState.tooltipHeight
+        panelState.size = size
+    }
 }
 
 // todo

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

@@ -17,7 +17,6 @@ import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.Compose.preview
 import eu.mjdev.desktop.extensions.Custom.plus
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
-import eu.mjdev.desktop.extensions.Modifier.circleShadow
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import java.io.File
 
@@ -57,7 +56,6 @@ fun FolderIcon(
             Box(
                 modifier = Modifier.size(computedSize)
                     .circleBorder(2.dp, textColor)
-                    .circleShadow(4.dp)
             ) {
                 FontIcon(
                     iconId = iconId,

+ 14 - 13
src/main/kotlin/eu/mjdev/desktop/components/gallery/Gallery.kt

@@ -26,6 +26,7 @@ import eu.mjdev.desktop.extensions.Compose.preview
 import eu.mjdev.desktop.extensions.Compose.rememberDerivedState
 import eu.mjdev.desktop.extensions.Compose.rememberFocusRequester
 import eu.mjdev.desktop.helpers.bitmap.Bitmap
+import eu.mjdev.desktop.log.Log
 import kotlinx.coroutines.delay
 import java.io.File
 
@@ -59,18 +60,18 @@ fun Gallery(
     ) -> Unit = { _, _, _ -> },
 ) {
     val initialized = remember(list, index) { mutableStateOf(false) }
-    val nextItem: () -> Unit = {
-        if (currentItemIndex.intValue < (list.size - 1)) {
-            currentItemIndex.intValue += 1
-        }
-        controlsState.value = true
-    }
-    val prevItem: () -> Unit = {
-        if (currentItemIndex.intValue > 0) {
-            currentItemIndex.intValue -= 1
-        }
-        controlsState.value = true
-    }
+//    val nextItem: () -> Unit = {
+//        if (currentItemIndex.intValue < (list.size - 1)) {
+//            currentItemIndex.intValue += 1
+//        }
+//        controlsState.value = true
+//    }
+//    val prevItem: () -> Unit = {
+//        if (currentItemIndex.intValue > 0) {
+//            currentItemIndex.intValue -= 1
+//        }
+//        controlsState.value = true
+//    }
     val itemState = rememberDerivedState(0, currentItemIndex.intValue) {
         list[currentItemIndex.intValue]
     }
@@ -146,7 +147,7 @@ fun Gallery(
                                 try {
                                     fr.requestFocus()
                                 } catch (e: Exception) {
-                                    e.printStackTrace()
+                                    Log.e(e)
                                 }
                             }
                         }

+ 2 - 1
src/main/kotlin/eu/mjdev/desktop/components/greeter/Greeter.kt

@@ -19,6 +19,7 @@ import eu.mjdev.desktop.components.input.PasswordTextView
 import eu.mjdev.desktop.components.user.UserAvatar
 import eu.mjdev.desktop.data.User
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
+import eu.mjdev.desktop.extensions.Compose.isDesign
 import eu.mjdev.desktop.extensions.Compose.preview
 import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.extensions.Compose.runAsync
@@ -33,7 +34,7 @@ import kotlinx.coroutines.flow.collectLatest
 fun Greeter() = withDesktopScope {
     val user: User by rememberState(api.currentUser)
     var isUserLoggedIn by rememberState(user.isLoggedIn)
-    var passwordVisible by rememberState(isDebug)
+    var passwordVisible by rememberState(isDesign)
     val onDone: (password: String) -> Unit = { password ->
         runAsync {
             user.login(api, password).collectLatest { logState ->

+ 2 - 1
src/main/kotlin/eu/mjdev/desktop/components/grid/base/GridPlacedScopeImpl.kt

@@ -17,6 +17,7 @@ import eu.mjdev.desktop.components.grid.base.GridPlacedSpanAnchor.Companion.bott
 import eu.mjdev.desktop.components.grid.base.GridPlacedSpanAnchor.Companion.leftBound
 import eu.mjdev.desktop.components.grid.base.GridPlacedSpanAnchor.Companion.rightBound
 import eu.mjdev.desktop.components.grid.base.GridPlacedSpanAnchor.Companion.topBound
+import eu.mjdev.desktop.log.Log
 
 class GridPlacedScopeImpl(
     private val cells: GridPlacedCells,
@@ -120,7 +121,7 @@ class GridPlacedScopeImpl(
     }
 
     private fun onSkipped(row: Int?, column: Int?, rowSpan: Int, columnSpan: Int) {
-        println(
+        Log.i(
             """
                 Skipped position: [${row}x$column], span size: [${rowSpan}x$columnSpan]
                 Grid size: [${cells.rowCount}x${cells.columnCount}]

+ 0 - 1
src/main/kotlin/eu/mjdev/desktop/components/input/PasswordTextView.kt

@@ -35,7 +35,6 @@ fun PasswordTextView(
     password: MutableState<String> = rememberSaveable { mutableStateOf("") },
     passwordVisible: MutableState<Boolean> = rememberSaveable { mutableStateOf(false) },
     backgroundColor: Color = Color.White.copy(0.5f),
-    textColor: Color = Color.Black,
     label: @Composable (() -> Unit)? = null,
     placeholder: @Composable () -> Unit = {},
     shape: Shape = RoundedCornerShape(8.dp),

+ 32 - 21
src/main/kotlin/eu/mjdev/desktop/components/main/MainWindow.kt

@@ -15,6 +15,7 @@ import eu.mjdev.desktop.components.installer.Installer
 import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.extensions.Compose.preview
 import eu.mjdev.desktop.helpers.system.Shell
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 import eu.mjdev.desktop.windows.DesktopWindow
@@ -22,11 +23,17 @@ import eu.mjdev.desktop.windows.DesktopWindow
 // todo focus manager
 @Composable
 fun MainWindow() = withDesktopScope {
-    val controlCenterState = rememberChromeWindowState()
-    val panelState = rememberChromeWindowState(visible = true, enabled = panelAutoHideEnabled)
+    val controlCenterState = rememberChromeWindowState(
+//        hideDelay = controlPanelHideDelay
+    )
+    val panelState = rememberChromeWindowState(
+        visible = true,
+        enabled = panelAutoHideEnabled,
+        hideDelay = panelHideDelay
+    )
     val menuState = rememberChromeWindowState()
-    val installWindowSate = rememberVisibilityState()
-    val infoWindowSate = rememberVisibilityState(false) //(api.isFirstStart || api.isDebug) // todo
+    val installWindowState = rememberVisibilityState()
+    val infoWindowState = rememberVisibilityState(false) //(api.isFirstStart || api.isDebug) // todo
     DesktopWindow(
         panelState = panelState,
         controlCenterState = controlCenterState,
@@ -39,53 +46,57 @@ fun MainWindow() = withDesktopScope {
         DesktopPanel(
             panelState = panelState,
             menuState = menuState,
-            onMenuIconClicked = { if (!menuState.isVisible) menuState.show() },
-            onFocusChange = { focus ->
-                if (panelState.enabled && !focus && !menuState.isVisible) panelState.hide()
+            onMenuIconClicked = { menuState.toggle() },
+            onFocusChange = { focused ->
+                if (panelState.enabled && menuState.isNotVisible && !focused) {
+                    panelState.hide()
+                }
             }
         )
         AppsMenu(
             menuState = menuState,
             panelState = panelState,
-            onFocusChange = { focus ->
-                if (!focus) {
+            onFocusChange = { focused ->
+                if (!focused) {
                     menuState.hide()
                 }
             }
         )
         ControlCenter(
             controlCenterState = controlCenterState,
-            onFocusChange = { focus ->
-                if (!focus) controlCenterState.hide()
+            onFocusChange = { focused ->
+                if (!focused) {
+                    controlCenterState.hide()
+                }
             }
         )
         Greeter()
         InfoWindow(
-            state = infoWindowSate,
+            state = infoWindowState,
             showInstallWindow = {
-                infoWindowSate.hide()
-                installWindowSate.show()
+                infoWindowState.hide()
+                installWindowState.show()
             }
         )
         Installer(
-            state = installWindowSate
+            state = installWindowState
         )
     }
     DisposableEffect(Unit) {
-        println("App started with args: $appArgs")
-        println("First start : $isFirstStart")
-        println("Debug mode : $isDebug")
+        Log.i("App started with args: $appArgs")
+        Log.i("First start : $isFirstStart")
+        Log.i("Debug mode : $isDebug")
         Shell {
             if (!isDebug) {
-                println("Starting autostart apps")
+                Log.i("Starting autostart apps")
                 autoStartApps()
             } else {
-                println("Starting autostart apps omitted in debug mode.")
+                Log.i("Starting autostart apps omitted in debug mode.")
             }
         }
         onDispose {
             dispose()
-            println("App ended.")
+            Log.i("App ended.")
         }
     }
 }

+ 21 - 30
src/main/kotlin/eu/mjdev/desktop/components/sliding/base/VisibilityState.kt

@@ -4,24 +4,19 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.unit.DpSize
-import eu.mjdev.desktop.helpers.internal.DpBounds
-import eu.mjdev.desktop.helpers.internal.DpBounds.Companion.toDpBounds
+import eu.mjdev.desktop.log.Log
+import kotlinx.coroutines.*
 
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 open class VisibilityState(
-    private val startState: Boolean = false,
-    var enabled: Boolean = true
+    val startState: Boolean = false,
+    var enabled: Boolean = true,
+    var hideDelay: Long = 0L,
+    val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
 ) {
-    private val visibleState: MutableState<Boolean> = mutableStateOf(startState)
-    private val boundsState: MutableState<DpBounds> = mutableStateOf(DpBounds.Zero)
+    val visibleState: MutableState<Boolean> = mutableStateOf(startState)
 
-    var bounds: DpBounds
-        get() = boundsState.value
-        set(value) {
-            boundsState.value = value
-        }
+    private var hideJob: Job? = null
 
     var isVisible: Boolean
         get() = visibleState.value
@@ -30,21 +25,26 @@ open class VisibilityState(
                 visibleState.value = value
             } else {
                 visibleState.value = startState
-                println("Visible state disabled, can not set value.")
+                Log.i("Visible state disabled, can not set value.")
             }
         }
 
+    val isNotVisible
+        get() = !isVisible
+
     fun show() {
-        if (!isVisible) {
-            if (enabled) {
-                visibleState.value = true
-            }
-        }
+        hideJob?.cancel()
+        visibleState.value = true
     }
 
     fun hide() {
-        if (isVisible) {
-            if (enabled) {
+        if (!enabled) return
+        hideJob?.cancel()
+        if (hideDelay == 0L) {
+            visibleState.value = false
+        } else {
+            hideJob = scope.launch {
+                delay(hideDelay)
                 visibleState.value = false
             }
         }
@@ -54,15 +54,6 @@ open class VisibilityState(
         visibleState.value = !visibleState.value
     }
 
-    fun onPlaced(lc: LayoutCoordinates) {
-        bounds = lc.toDpBounds()
-    }
-
-    fun updateSize(panelSize: DpSize) {
-        bounds.height = panelSize.height
-        bounds.width = panelSize.width
-    }
-
     companion object {
         @Composable
         fun rememberVisibilityState(

+ 4 - 4
src/main/kotlin/eu/mjdev/desktop/components/surface/Surface.kt

@@ -8,6 +8,10 @@
 
 package eu.mjdev.desktop.components.surface
 
+//import androidx.compose.material3.LocalAbsoluteTonalElevation
+//import androidx.compose.material3.LocalContentColor
+//import androidx.compose.material3.MaterialTheme
+//import androidx.compose.material3.surfaceColorAtElevation
 import androidx.compose.animation.core.CubicBezierEasing
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.animation.core.animateFloatAsState
@@ -18,10 +22,6 @@ import androidx.compose.foundation.interaction.*
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.material.LocalContentColor
-//import androidx.compose.material3.LocalAbsoluteTonalElevation
-//import androidx.compose.material3.LocalContentColor
-//import androidx.compose.material3.MaterialTheme
-//import androidx.compose.material3.surfaceColorAtElevation
 import androidx.compose.runtime.*
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color

+ 1 - 0
src/main/kotlin/eu/mjdev/desktop/components/surface/base/SurfaceGlow.kt

@@ -178,6 +178,7 @@ private class SurfaceGlowNode(
     }
 
     // todo
+    @Suppress("UNUSED_PARAMETER", "UnusedReceiverParameter")
     fun Paint.setShadowLayer(radius: Float, dx: Float, dy: Float, shadowColor: Color) {
 //        this.setShadowLayer(radius, dx, dy, shadowColor)
     }

+ 2 - 1
src/main/kotlin/eu/mjdev/desktop/components/video/VideoView.kt

@@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.layout.ContentScale
 import eu.mjdev.desktop.components.video.base.VideoPlayerFFMpeg
 import eu.mjdev.desktop.extensions.Compose.preview
+import eu.mjdev.desktop.log.Log
 import java.io.File
 
 // todo
@@ -24,7 +25,7 @@ fun VideoView(
     contentDescription: String? = "",
     colorFilter: ColorFilter? = null,
     contentScale: ContentScale = ContentScale.Crop,
-    onFail: (Throwable) -> Unit = { e -> e.printStackTrace() }
+    onFail: (Throwable) -> Unit = { e -> Log.e(e) }
 ) = Box(
     modifier = modifier
 ) {

+ 11 - 11
src/main/kotlin/eu/mjdev/desktop/components/video/base/KFFMpeg.kt

@@ -2,6 +2,7 @@ package eu.mjdev.desktop.components.video.base
 
 import androidx.compose.ui.graphics.asComposeImageBitmap
 import androidx.compose.ui.unit.IntSize
+import eu.mjdev.desktop.log.Log
 import org.bytedeco.ffmpeg.avcodec.AVCodecContext
 import org.bytedeco.ffmpeg.avcodec.AVCodecHWConfig
 import org.bytedeco.ffmpeg.avcodec.AVCodecParameters
@@ -103,7 +104,7 @@ class KAVFormatContext {
 
     fun findVideoStreams(): List<KVideoStream> = (0 until fmtCtx.nb_streams()).mapNotNull { i ->
         val type = KAVMediaType.fromId(fmtCtx.streams(i).codecpar().codec_type())
-        println("Stream: $i type: $type")
+        Log.i("Stream: $i type: $type")
         if (type == KAVMediaType.VIDEO) {
             val stream = fmtCtx.streams(i)
             val width = stream.codecpar().width()
@@ -254,10 +255,10 @@ class KFrameGrabber(
             avformat.av_seek_frame(fmtCtx, stream, 120, avformat.AVSEEK_FLAG_BACKWARD)
         }*/
         val timeDiff = currentImageTS - ts
-        //println("pts: ${hwFrame.pts()} td: $timeDiff")
+        Log.i("pts: ${hwFrame.pts()} td: $timeDiff")
         if (timeDiff.absoluteValue > 500) {
             val seekPts = ts * timeBaseDen / timeBaseNum / 1000L
-//            println("Time diff: $timeDiff seek to $seekPts ${hwFrame.pts()}")
+            Log.i("Time diff: $timeDiff seek to $seekPts ${hwFrame.pts()}")
             avformat.av_seek_frame(fmtCtx, stream.index(), seekPts, avformat.AVSEEK_FLAG_BACKWARD)
         } else if (timeDiff in 11..499) {
             return
@@ -273,8 +274,8 @@ class KFrameGrabber(
             val r2 = avcodec.avcodec_receive_frame(codecCtx, hwFrame)
             decodedFrameCounter++
             if (r2 < 0) {
-                println("ret1 $r1 ret2 $r2")
-                println("Frame skipped!")
+                Log.i("ret1 $r1 ret2 $r2")
+                Log.i("Frame skipped!")
             }
             val frameTime = hwFrame.pts() * 1000L * timeBaseNum / timeBaseDen
             currentImageTS = frameTime
@@ -291,17 +292,16 @@ class KFrameGrabber(
         } else {
             hwFrame
         }
-        //val frameFormat = avutil.av_get_pix_fmt_name(hwFrame.format()).getString(Charset.defaultCharset())
-        //val srcFormat = avutil.av_get_pix_fmt_name(srcFrame.format()).getString(Charset.defaultCharset())
-        //println("hwFormat: $frameFormat -> $srcFormat")
-        //println("Frame: ${hwFrame.best_effort_timestamp()} format: ${frame.format()} (hw:${hwFrame.format()})")
-
+        val frameFormat = avutil.av_get_pix_fmt_name(hwFrame.format()).getString(Charset.defaultCharset())
+        val srcFormat = avutil.av_get_pix_fmt_name(srcFrame.format()).getString(Charset.defaultCharset())
+        Log.i("hwFormat: $frameFormat -> $srcFormat")
+        Log.i("Frame: ${hwFrame.best_effort_timestamp()} format: ${frame.format()} (hw:${hwFrame.format()})")
         val swsCtx = swscale.sws_getContext(
             codecCtx.width(), codecCtx.height(), srcFrame.format(), //Src size, format
             targetWidth, targetHeight, dstFormat, //Dst size, format
             swscale.SWS_BILINEAR, null, null, null as DoublePointer?
         )
-        //println("Frame format: ${frame.format()}")
+        Log.i("Frame format: ${frame.format()}")
         errCheck(
             swscale.sws_scale(
                 swsCtx, srcFrame.data(), srcFrame.linesize(),

+ 5 - 4
src/main/kotlin/eu/mjdev/desktop/components/video/base/VideoPlayerFFMpeg.kt

@@ -8,6 +8,7 @@ import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.unit.IntSize
+import eu.mjdev.desktop.log.Log
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.isActive
 import kotlin.math.min
@@ -20,6 +21,7 @@ class FFmpegVideoPlayerState {
         internal set
     var progress: Float by mutableStateOf(0f)
         internal set
+
     @Suppress("RedundantSetter")
     private var aspectRatio: Float by mutableStateOf(1f)
         private set
@@ -27,7 +29,6 @@ class FFmpegVideoPlayerState {
         internal set
     var decodedFPS: Float by mutableStateOf(0f)
         internal set
-
     var frameGrabber: KFrameGrabber? by mutableStateOf(null)
         private set
 
@@ -85,8 +86,8 @@ fun VideoPlayerFFMpeg(
         state.open(file)
         val stream = state.streams().first()
         val codec = state.codec(stream)
-        println("Codec: ${codec.name}")
-        codec.hwDecoder.forEach { println("   Decoder: ${it.name}") }
+        Log.i("Codec: ${codec.name}")
+        codec.hwDecoder.forEach { Log.i("   Decoder: ${it.name}") }
         val scale = 1
         val targetSize = IntSize(stream.width / scale, stream.height / scale)
         state.play(stream, codec.hwDecoder.firstOrNull(), targetSize) // Hw accel
@@ -137,7 +138,7 @@ fun VideoPlayerFFMpeg(
                 scale = min(scaleH, scaleW),
                 pivot = Offset.Zero
             ) {
-                println("$image")
+                Log.i("$image")
                 drawImage(image)
             }
         })

+ 6 - 5
src/main/kotlin/eu/mjdev/desktop/data/App.kt

@@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.graphics.Color
 import eu.mjdev.desktop.helpers.exception.EmptyException.Companion.EmptyException
 import eu.mjdev.desktop.helpers.system.Shell
+import eu.mjdev.desktop.log.Log
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -76,19 +77,19 @@ class App(
         scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
     ) = scope.launch(Dispatchers.IO) {
         runCatching {
-            println("Starting app: $name [$windowClass].")
-            println("dex -w ${desktopFile.absolutePath}")
+            Log.i("Starting app: $name [$windowClass].")
+            Log.i("dex -w ${desktopFile.absolutePath}")
             triggerStart()
             Shell {
                 startApp(
                     app = this@App,
                     onStarted = {
-                        println("App started app: $name [$windowClass].")
-                        println("Desktop File:")
+                        Log.i("App started app: $name [$windowClass].")
+                        Log.i("Desktop File:")
                         triggerStarted()
                     },
                     onStopped = { e ->
-                        println("App stopped: $name [$windowClass].")
+                        Log.i("App stopped: $name [$windowClass].")
                         triggerStop(e)
                     }
                 )

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

@@ -1,10 +1,8 @@
 package eu.mjdev.desktop.data
 
 import androidx.compose.ui.graphics.Color
-import eu.mjdev.desktop.helpers.backgrounds.ProviderErzvo
 import eu.mjdev.desktop.helpers.backgrounds.ProviderLocal
-import eu.mjdev.desktop.helpers.backgrounds.ProviderSmug
-import eu.mjdev.desktop.helpers.internal.ImagesProvider
+import eu.mjdev.desktop.helpers.compose.ImagesProvider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch

+ 5 - 4
src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt

@@ -5,6 +5,7 @@ import eu.mjdev.desktop.extensions.Custom.ParsedList
 import eu.mjdev.desktop.extensions.Custom.ParsedString
 import eu.mjdev.desktop.extensions.Custom.text
 import eu.mjdev.desktop.helpers.desktopparser.DesktopFileParser
+import eu.mjdev.desktop.log.Log
 import org.ini4j.Ini
 import org.ini4j.Profile.Section
 import java.io.File
@@ -18,7 +19,7 @@ class DesktopFile(
     private var content: DesktopFileParser = (runCatching {
         DesktopFileParser(fileData)
     }.onFailure { e ->
-        e.printStackTrace()
+        Log.e(e)
     }.getOrNull() ?: DesktopFileParser())
 ) {
     var fileName: String = if (correctedFile.exists()) correctedFile.name else file.name
@@ -59,7 +60,7 @@ class DesktopFile(
                     content = DesktopFileParser(fileData)
                 }
             }.onFailure { e ->
-                e.printStackTrace()
+                Log.e(e)
             }
         }
     }
@@ -104,7 +105,7 @@ class DesktopFile(
     }
 
     fun writeTo(f: File) {
-        println("Writing: file:///${f.absolutePath}")
+        Log.i("Writing: file:///${f.absolutePath}")
         if (!f.parentFile.exists()) {
             f.parentFile.mkdirs()
         }
@@ -114,7 +115,7 @@ class DesktopFile(
         if (f.parentFile.exists()) {
             f.createNewFile()
         } else {
-            println("Cant store file: ${f.absolutePath}")
+            Log.i("Cant store file: ${f.absolutePath}")
         }
         content.store(f)
     }

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

@@ -9,7 +9,7 @@ import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 import eu.mjdev.desktop.fonts.MaterialIconFont
 import eu.mjdev.desktop.fonts.MaterialSymbolsSharp
 
-// todo all customizable
+// todo all components customizable
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 class Theme(
     backgroundColor: Color,
@@ -103,10 +103,10 @@ class Theme(
             panelLocation = PanelLocation.Bottom,
             panelDividerWidth = 16.dp,
             panelContentPadding = 4.dp,
-            panelHideDelay = 0L,
+            panelHideDelay = 2000L,
 
             controlCenterLocation = ControlCenterLocation.Right,
-            controlPanelHideDelay = 5000L,
+            controlPanelHideDelay = 2000L,
             controlCenterExpandedWidthPercent = 25,
             controlCenterDividerColor = Color.SuperDarkGray,
             controlCenterDividerWidth = 4.dp,

+ 8 - 12
src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt

@@ -33,6 +33,7 @@ import androidx.compose.ui.input.key.*
 import androidx.compose.ui.input.pointer.*
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.*
 import coil3.ImageLoader
@@ -44,6 +45,7 @@ import coil3.svg.SvgDecoder
 import eu.mjdev.desktop.components.guide.GuideLines
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.helpers.compose.FocusHelper
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopScope
@@ -93,6 +95,10 @@ object Compose {
 
     val Color.hexRgba get() = rgbaToHex()
 
+    val isDesign
+        @Composable
+        get() = LocalInspectionMode.current
+
     val PaddingValues.width: Dp
         get() = calculateLeftPadding(LayoutDirection.Ltr) + calculateRightPadding(LayoutDirection.Ltr)
 
@@ -300,7 +306,7 @@ object Compose {
                         }
                     }
                 } catch (e: Throwable) {
-                    e.printStackTrace()
+                    Log.e(e)
                 }
             }
         )
@@ -477,7 +483,7 @@ object Compose {
         state: LazyListState
     ) = this then pointerInput(Unit) {
         detectVerticalDragGestures { _, dragAmount ->
-            println("drag ammount : $dragAmount")
+            Log.i("drag ammount : $dragAmount")
             state.dispatchRawDelta(-dragAmount)
         }
     }
@@ -597,14 +603,4 @@ object Compose {
 //        block: suspend () -> Unit
 //    ) = scope.launch(context) { block() }
 
-//    Toolkit.getDefaultToolkit().addAWTEventListener({ event ->
-//        }, AWTEvent.MOUSE_EVENT_MASK or AWTEvent.FOCUS_EVENT_MASK
-//    )
-
-//    Toolkit.getDefaultToolkit().addAWTEventListener(object:AWTEventListener {
-//        override fun eventDispatched(event: AWTEvent?) {
-//            println(event)
-//        }
-//    }, AWTEvent.WINDOW_EVENT_MASK)
-
 }

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt

@@ -10,6 +10,7 @@ import eu.mjdev.desktop.data.DesktopFile
 import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.extensions.Locale.toLocale
 import eu.mjdev.desktop.helpers.streams.ResourceStream
+import eu.mjdev.desktop.log.Log
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.catch
@@ -197,7 +198,7 @@ object Custom {
         initial: T,
         delay: Long = 0L,
         coroutineContext: CoroutineContext = Dispatchers.IO,
-        onError: (Throwable) -> Unit = { e -> e.printStackTrace() },
+        onError: (Throwable) -> Unit = { e -> Log.e(e) },
         block: suspend () -> T,
     ): MutableState<T> {
         val result = remember { mutableStateOf(initial) }
@@ -224,7 +225,7 @@ object Custom {
         initial: T,
         key: Any,
         coroutineContext: CoroutineContext = Dispatchers.IO,
-        onError: (Throwable) -> Unit = { e -> e.printStackTrace() },
+        onError: (Throwable) -> Unit = { e -> Log.e(e) },
         block: suspend () -> T,
     ): MutableState<T> {
         val result = remember { mutableStateOf(initial) }

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/helpers/adb/AdbDiscover.kt

@@ -1,11 +1,12 @@
 package eu.mjdev.desktop.helpers.adb
 
+import eu.mjdev.desktop.log.Log
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 
-@Suppress("MemberVisibilityCanBePrivate")
+@Suppress("MemberVisibilityCanBePrivate", "unused")
 class AdbDiscover(
     coroutineScope: CoroutineScope? = null,
     discoverDelay: Long = 1000L,
@@ -30,7 +31,7 @@ class AdbDiscover(
                         onAdded(d)
                     }
                 }.onFailure { e ->
-                    println("Error : ${e.message}")
+                    Log.e(e)
                 }
                 delay(discoverDelay)
             }

+ 2 - 2
src/main/kotlin/eu/mjdev/desktop/helpers/adb/IAdb.kt

@@ -214,8 +214,8 @@ interface IAdb : AutoCloseable {
     }
 
     @Throws(IOException::class)
-    fun unroot() {
-        val response = restartAdb(this, "unroot:")
+    fun unRoot() {
+        val response = restartAdb(this, "unRoot:")
         if (!response.startsWith("restarting") && !response.contains("not running as root")) {
             throw IOException("Failed to restart adb as root: $response")
         }

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/helpers/adb/forwarding/TcpForwarder.kt

@@ -1,7 +1,7 @@
 package eu.mjdev.desktop.helpers.adb.forwarding
 
 import eu.mjdev.desktop.helpers.adb.IAdb
-import eu.mjdev.desktop.helpers.adb.helpers.log
+import eu.mjdev.desktop.log.Log
 import okio.*
 import java.io.IOException
 import java.io.InterruptedIOException
@@ -34,7 +34,8 @@ internal class TcpForwarder(
             } catch (_: SocketException) {
                 // Do nothing
             } catch (e: IOException) {
-                log { "could not start TCP port forwarding: ${e.message}" }
+                Log.e("could not start TCP port forwarding: ${e.message}")
+                Log.e(e)
             } finally {
                 moveToState(State.STOPPED)
             }

+ 1 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbConnection.kt

@@ -10,6 +10,7 @@ import java.io.IOException
 import java.net.Socket
 import java.util.*
 
+@Suppress("MemberVisibilityCanBePrivate")
 internal class AdbConnection(
     adbReader: AdbReader,
     private val adbWriter: AdbWriter,

+ 2 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbReader.kt

@@ -1,5 +1,6 @@
 package eu.mjdev.desktop.helpers.adb.helpers
 
+import eu.mjdev.desktop.log.Log
 import okio.Source
 import okio.buffer
 
@@ -19,7 +20,7 @@ internal class AdbReader(
                 val magic = readIntLe()
                 val payload = readByteArray(payloadLength.toLong())
                 return AdbMessage(command, arg0, arg1, payloadLength, checksum, magic, payload).also {
-                    log { "(${Thread.currentThread().name}) < $it" }
+                    Log.i("(${Thread.currentThread().name}) < $it")
                 }
             }
         }

+ 1 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbShellResponse.kt

@@ -1,5 +1,6 @@
 package eu.mjdev.desktop.helpers.adb.helpers
 
+@Suppress("MemberVisibilityCanBePrivate")
 class AdbShellResponse(
     val output: String,
     val errorOutput: String,

+ 0 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbStream.kt → src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbStreamImpl.kt


+ 11 - 10
src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbSync.kt → src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbSyncStream.kt

@@ -6,6 +6,7 @@ import okio.Source
 import java.io.IOException
 import java.nio.charset.StandardCharsets
 
+@Suppress("MemberVisibilityCanBePrivate")
 class AdbSyncStream(
     private val stream: IAdbStream
 ) : AutoCloseable {
@@ -76,16 +77,16 @@ class AdbSyncStream(
     }
 
     companion object {
-        internal const val LIST = "LIST"
-        internal const val RECV = "RECV"
-        internal const val SEND = "SEND"
-        internal const val STAT = "STAT"
-        internal const val DATA = "DATA"
-        internal const val DONE = "DONE"
-        internal const val OKAY = "OKAY"
-        internal const val QUIT = "QUIT"
-        internal const val FAIL = "FAIL"
+        const val LIST = "LIST"
+        const val RECV = "RECV"
+        const val SEND = "SEND"
+        const val STAT = "STAT"
+        const val DATA = "DATA"
+        const val DONE = "DONE"
+        const val OKAY = "OKAY"
+        const val QUIT = "QUIT"
+        const val FAIL = "FAIL"
 
         val SYNC_IDS = setOf(LIST, RECV, SEND, STAT, DATA, DONE, OKAY, QUIT, FAIL)
     }
-}
+}

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/AdbWriter.kt

@@ -1,5 +1,6 @@
 package eu.mjdev.desktop.helpers.adb.helpers
 
+import eu.mjdev.desktop.log.Log
 import okio.Sink
 import okio.buffer
 import java.nio.ByteBuffer
@@ -55,7 +56,7 @@ internal class AdbWriter(sink: Sink) : AutoCloseable {
         offset: Int,
         length: Int
     ) {
-        log {
+        Log.i(
             "(${Thread.currentThread().name}) > ${
                 AdbMessage(
                     command,
@@ -67,7 +68,7 @@ internal class AdbWriter(sink: Sink) : AutoCloseable {
                     payload ?: ByteArray(0)
                 )
             }"
-        }
+        )
         synchronized(bufferedSink) {
             bufferedSink.apply {
                 writeIntLe(command)

+ 0 - 10
src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/Logging.kt

@@ -1,10 +0,0 @@
-package eu.mjdev.desktop.helpers.adb.helpers
-
-// todo
-private const val ENABLED = true
-
-fun log(block: () -> String) {
-    if (ENABLED) {
-        println(block())
-    }
-}

+ 0 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/adb/helpers/MessageQueue.kt

@@ -39,7 +39,6 @@ abstract class MessageQueue<V> {
 
     @TestOnly
     fun ensureEmpty() {
-        @Suppress("UsePropertyAccessSyntax")
         check(queues.isEmpty()) { "Queues is not empty: ${queues.keys.map { String.format("%X", it) }}" }
         check(openStreams.isEmpty())
     }

+ 2 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/application/Application.kt

@@ -22,6 +22,7 @@ import eu.mjdev.desktop.helpers.application.base.GlobalLayoutDirection
 import eu.mjdev.desktop.helpers.application.base.GlobalSnapshotManager
 import eu.mjdev.desktop.helpers.application.base.YieldFrameClock
 import eu.mjdev.desktop.helpers.application.l10n.defaultPlatformLocalization
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.rememberDesktopProvider
 import kotlinx.coroutines.launch
@@ -45,7 +46,7 @@ fun application(
         }
     }
     if (exitProcessOnExit) {
-        println("Exiting application.")
+        Log.i("Exiting application.")
         exitProcess(0)
     }
 }

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/backgrounds/ProviderErzvo.kt

@@ -8,7 +8,7 @@
 
 package eu.mjdev.desktop.helpers.backgrounds
 
-import eu.mjdev.desktop.helpers.internal.ImagesProvider
+import eu.mjdev.desktop.helpers.compose.ImagesProvider
 
 // todo
 @Suppress("unused")

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/backgrounds/ProviderLocal.kt

@@ -11,7 +11,7 @@ package eu.mjdev.desktop.helpers.backgrounds
 import eu.mjdev.desktop.data.User
 import eu.mjdev.desktop.extensions.Custom.get
 import eu.mjdev.desktop.extensions.Custom.listFilesOnly
-import eu.mjdev.desktop.helpers.internal.ImagesProvider
+import eu.mjdev.desktop.helpers.compose.ImagesProvider
 import eu.mjdev.desktop.managers.apps.AppsManager.Companion.DIR_NAME_BACKGROUNDS
 import eu.mjdev.desktop.managers.apps.AppsManager.Companion.DIR_NAME_DOT_LOCAL
 import eu.mjdev.desktop.managers.apps.AppsManager.Companion.DIR_NAME_SHARE

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/backgrounds/ProviderSmug.kt

@@ -8,7 +8,7 @@
 
 package eu.mjdev.desktop.helpers.backgrounds
 
-import eu.mjdev.desktop.helpers.internal.ImagesProvider
+import eu.mjdev.desktop.helpers.compose.ImagesProvider
 
 // todo
 @Suppress("unused")

+ 9 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/internal/DpBounds.kt → src/main/kotlin/eu/mjdev/desktop/helpers/compose/DpBounds.kt

@@ -1,4 +1,12 @@
-package eu.mjdev.desktop.helpers.internal
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.compose
 
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInParent

+ 2 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/internal/ImagesProvider.kt → src/main/kotlin/eu/mjdev/desktop/helpers/compose/ImagesProvider.kt

@@ -6,11 +6,12 @@
  *  w: https://mjdev.org
  */
 
-package eu.mjdev.desktop.helpers.internal
+package eu.mjdev.desktop.helpers.compose
 
 interface ImagesProvider {
     suspend fun get(): Any?
 
+    @Suppress("unused")
     companion object {
         suspend fun ImagesProvider.getOne(): Any? = get().let { if (it is List<*>) it.firstOrNull() else it }
     }

+ 56 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/generic/Queue.kt

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.generic
+
+import eu.mjdev.desktop.log.Log
+
+@Suppress("unused")
+class Queue<E>(
+    val source: List<E>
+) : Iterator<E> {
+    var currentIndex = 0
+
+    override fun hasNext(): Boolean =
+        source.isNotEmpty() && source.size > currentIndex
+
+    @Deprecated("Deprecated please use for safety nextOrNull().")
+    override fun next(): E = when {
+        source.isEmpty() -> throw (IllegalStateException("Source is empty."))
+        hasNext() -> {
+            source[currentIndex].apply {
+                currentIndex += 1
+            }
+        }
+
+        else -> {
+            currentIndex = 0
+            @Suppress("DEPRECATION")
+            next()
+        }
+    }
+
+    fun nextOrNull(): E? = runCatching {
+        when {
+            source.isEmpty() -> null
+            hasNext() -> {
+                source[currentIndex].apply {
+                    currentIndex += 1
+                }
+            }
+
+            else -> {
+                currentIndex = 0
+                @Suppress("DEPRECATION")
+                next()
+            }
+        }
+    }.onFailure { e ->
+        Log.e(e)
+    }.getOrNull()
+}

+ 0 - 31
src/main/kotlin/eu/mjdev/desktop/helpers/internal/Queue.kt

@@ -1,31 +0,0 @@
-package eu.mjdev.desktop.helpers.internal
-
-@Suppress("unused")
-class Queue<E>(
-    val source: Collection<E>
-) : Iterator<E> {
-    var iterator: Iterator<E> = source.iterator()
-
-    override fun hasNext(): Boolean = source.isNotEmpty()
-
-    @Deprecated(
-        message = "Deprecated please use for safety nextOrNull().",
-    )
-    override fun next(): E = when {
-        source.isEmpty() -> throw (IllegalStateException("Source is empty."))
-        iterator.hasNext() -> iterator.next()
-        else -> {
-            iterator = source.iterator()
-            iterator.next()
-        }
-    }
-
-    fun nextOrNull(): E? = when {
-        source.isEmpty() -> null
-        iterator.hasNext() -> iterator.next()
-        else -> {
-            iterator = source.iterator()
-            nextOrNull()
-        }
-    }
-}

+ 58 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/keyevents/GlobalKeyListener.kt

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.keyevents
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import java.awt.AWTEvent
+import java.awt.Toolkit
+import java.awt.event.AWTEventListener
+import java.awt.event.KeyEvent.VK_TAB
+import java.awt.event.KeyEvent as AwtKeyEvent
+
+class GlobalKeyListener(
+    val onEvent: (event: AwtKeyEvent) -> Unit
+) : AWTEventListener {
+    init {
+        Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK)
+    }
+
+    fun dispose() {
+        Toolkit.getDefaultToolkit().removeAWTEventListener(this)
+    }
+
+    override fun eventDispatched(event: AWTEvent?) {
+        (event as? AwtKeyEvent)?.also { kev ->
+            val notConsumed = !kev.isConsumed
+            val isAlt = kev.isAltDown
+            val keyCode = kev.keyCode
+            val isAltTab = (isAlt && keyCode == VK_TAB)
+            if (isAltTab) return
+            if (notConsumed) onEvent(kev)
+        }
+    }
+
+    companion object {
+        @Composable
+        fun globalKeyEventHandler(
+            isEnabled: () -> Boolean = { true },
+            block: KeyEventHandler.() -> Unit,
+        ) {
+            DisposableEffect(Unit) {
+                val handler = KeyEventHandler(isEnabled, block)
+                val globalHandler = GlobalKeyListener { event ->
+                    handler.onEvent(event)
+                }
+                onDispose {
+                    globalHandler.dispose()
+                }
+            }
+        }
+    }
+}

+ 21 - 54
src/main/kotlin/eu/mjdev/desktop/helpers/internal/KeyEventHandler.kt → src/main/kotlin/eu/mjdev/desktop/helpers/keyevents/KeyEventHandler.kt

@@ -6,16 +6,12 @@
  *  w: https://mjdev.org
  */
 
-package eu.mjdev.desktop.helpers.internal
+package eu.mjdev.desktop.helpers.keyevents
 
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.ui.input.key.Key
 import eu.mjdev.desktop.extensions.Custom.isPrintable
-import java.awt.AWTEvent
-import java.awt.Toolkit
-import java.awt.event.AWTEventListener
-import java.awt.event.KeyEvent.VK_TAB
+import eu.mjdev.desktop.log.Log
+import java.awt.event.KeyEvent.VK_WINDOWS
 import java.awt.event.KeyEvent as AwtKeyEvent
 
 @Suppress("CanBeParameter", "unused", "MemberVisibilityCanBePrivate")
@@ -26,7 +22,11 @@ class KeyEventHandler(
     private val listeners = mutableListOf<KeyEventListener>()
 
     fun addListener(key: Key?, block: (Char) -> Boolean) {
-        listeners.add(KeyEventListener(key, block))
+        listeners.add(KeyEventListenerCompose(key, block))
+    }
+
+    fun addListener(key: Int, block: (Int) -> Boolean) {
+        listeners.add(KeyEventListenerAwt(key, block))
     }
 
     init {
@@ -40,10 +40,11 @@ class KeyEventHandler(
         val code = event.keyCode
         val char = parseCharacter(event)
         val isKeyUp = event.id == AwtKeyEvent.KEY_RELEASED
+        Log.i("Got key: $code")
         when {
             isKeyUp && char.isPrintable -> {
                 var consumed = false
-                listeners.filter { l ->
+                listeners.filterIsInstance<KeyEventListenerCompose>().filter { l ->
                     l.key == null
                 }.forEach { l ->
                     consumed = consumed || l.block(char)
@@ -54,9 +55,17 @@ class KeyEventHandler(
             isKeyUp -> {
                 var consumed = false
                 listeners.filter { l ->
-                    l.key?.keyCode?.toInt() == code
+                    when (l) {
+                        is KeyEventListenerCompose -> l.key?.keyCode?.toInt() == code
+                        is KeyEventListenerAwt -> l.key == code
+                        else -> false
+                    }
                 }.forEach { l ->
-                    consumed = consumed || l.block(char)
+                    consumed = consumed || when (l) {
+                        is KeyEventListenerCompose -> l.block(char)
+                        is KeyEventListenerAwt -> l.block(code)
+                        else -> false
+                    }
                 }
                 if (consumed) event.consume()
             }
@@ -75,47 +84,5 @@ class KeyEventHandler(
     fun onEnter(block: (Char) -> Boolean) = addListener(Key.Enter, block)
     fun onBack(block: (Char) -> Boolean) = addListener(Key.Back, block)
     fun onBackSpace(block: (Char) -> Boolean) = addListener(Key.Backspace, block)
-
-    class KeyEventListener(val key: Key?, val block: (Char) -> Boolean)
-
-    class GlobalKeyListener(
-        val onKey: (event: AwtKeyEvent) -> Unit
-    ) : AWTEventListener {
-        init {
-            Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK)
-        }
-
-        fun dispose() {
-            Toolkit.getDefaultToolkit().removeAWTEventListener(this)
-        }
-
-        override fun eventDispatched(event: AWTEvent?) {
-            (event as? AwtKeyEvent)?.also { kev ->
-                val notConsumed = !kev.isConsumed
-                val isAlt = kev.isAltDown
-                val keyCode = kev.keyCode
-                val isAltTab = (isAlt && keyCode == VK_TAB)
-                if (isAltTab) return
-                if (notConsumed) onKey(kev)
-            }
-        }
-    }
-
-    companion object {
-        @Composable
-        fun globalKeyEventHandler(
-            isEnabled: () -> Boolean = { true },
-            block: KeyEventHandler.() -> Unit,
-        ) {
-            DisposableEffect(Unit) {
-                val handler = KeyEventHandler(isEnabled, block)
-                val globalHandler = GlobalKeyListener { event ->
-                    handler.onEvent(event)
-                }
-                onDispose {
-                    globalHandler.dispose()
-                }
-            }
-        }
-    }
+    fun onMenuKey(block: (Int) -> Boolean) = addListener(VK_WINDOWS, block)
 }

+ 23 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/keyevents/KeyEventListener.kt

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.keyevents
+
+import androidx.compose.ui.input.key.Key
+
+interface KeyEventListener
+
+class KeyEventListenerCompose(
+    val key: Key?,
+    val block: (Char) -> Boolean
+) : KeyEventListener
+
+class KeyEventListenerAwt(
+    val key: Int,
+    val block: (Int) -> Boolean
+) : KeyEventListener

+ 64 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/GlobalMouseListener.kt

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.mouseevents
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.rememberCoroutineScope
+import kotlinx.coroutines.*
+import java.awt.MouseInfo
+import java.awt.Point
+
+class GlobalMouseListener(
+    val scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
+    val delay: Long = 200L,
+    val onEvent: (event: Point) -> Unit
+) {
+    private var job: Job? = null
+    private var lastPoint: Point? = null
+
+    init {
+        job = scope.launch {
+            while (isActive) {
+                MouseInfo.getPointerInfo().location.also { point ->
+                    if (!point.equals(lastPoint)) {
+                        lastPoint = point
+                        onEvent(point)
+                    }
+                }
+                delay(delay)
+            }
+        }
+    }
+
+    fun dispose() {
+        job?.cancel()
+    }
+
+    companion object {
+        @Composable
+        fun globalMouseEventHandler(
+            isEnabled: () -> Boolean = { true },
+            scope: CoroutineScope = rememberCoroutineScope(),
+            block: MouseEventHandler.() -> Unit,
+        ) {
+            DisposableEffect(Unit) {
+                val handler = MouseEventHandler(isEnabled, block)
+                val globalHandler = GlobalMouseListener(
+                    scope = scope
+                ) { event ->
+                    handler.onEvent(event)
+                }
+                onDispose {
+                    globalHandler.dispose()
+                }
+            }
+        }
+    }
+}

+ 61 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/MouseEventHandler.kt

@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.mouseevents
+
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+import java.awt.Point
+
+@Suppress("CanBeParameter", "unused", "MemberVisibilityCanBePrivate")
+class MouseEventHandler(
+    private val isEnabled: () -> Boolean = { true },
+    private val block: MouseEventHandler.() -> Unit
+) {
+    private val listeners = mutableListOf<MouseEventListener>()
+
+    init {
+        block()
+    }
+
+    fun addListener(
+        type: MouseEventType,
+        range: MouseRange,
+        block: (offset: DpOffset) -> Unit
+    ) = listeners.add(MouseEventListener(type, range, block))
+
+    fun onEvent(point: Point) {
+        if (!isEnabled()) return
+        val offset = DpOffset(point.x.dp, point.y.dp)
+        listeners.filter { ev ->
+            ev.type == MouseEventType.ENTER
+        }.filter { ev ->
+            ev.isInRange(offset)
+        }.forEach { l ->
+            l.block(offset)
+        }
+        listeners.filter { ev ->
+            ev.type == MouseEventType.LEAVE
+        }.filter { ev ->
+            !ev.isInRange(offset)
+        }.forEach { l ->
+            l.block(offset)
+        }
+    }
+
+    fun onPointerEnter(
+        range: MouseRange,
+        block: (offset: DpOffset) -> Unit
+    ) = addListener(MouseEventType.ENTER, range, block)
+
+    fun onPointerLeave(
+        range: MouseRange,
+        block: (offset: DpOffset) -> Unit
+    ) = addListener(MouseEventType.LEAVE, range, block)
+
+}

+ 26 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/MouseEventListener.kt

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.mouseevents
+
+import androidx.compose.ui.unit.DpOffset
+
+@Suppress("MemberVisibilityCanBePrivate")
+class MouseEventListener(
+    val type: MouseEventType,
+    val range: MouseRange,
+    val block: (offset: DpOffset) -> Unit
+) {
+    fun isInRange(
+        offset: DpOffset
+    ): Boolean {
+        val inX = offset.x >= range.x && offset.x <= (range.x + range.width)
+        val inY = offset.y >= range.y && offset.y <= (range.y + range.height)
+        return inX && inY
+    }
+}

+ 14 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/MouseEventType.kt

@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.mouseevents
+
+enum class MouseEventType {
+    ENTER,
+    LEAVE
+}

+ 18 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/mouseevents/MouseRange.kt

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.mouseevents
+
+import androidx.compose.ui.unit.Dp
+
+data class MouseRange(
+    val x: Dp,
+    val y: Dp,
+    val width: Dp,
+    val height: Dp
+)

+ 9 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/internal/Palette.kt → src/main/kotlin/eu/mjdev/desktop/helpers/palette/Palette.kt

@@ -1,4 +1,12 @@
-package eu.mjdev.desktop.helpers.internal
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.palette
 
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf

+ 5 - 7
src/main/kotlin/eu/mjdev/desktop/helpers/system/Shell.kt

@@ -4,6 +4,7 @@ import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.extensions.Custom.consoleOutput
 import eu.mjdev.desktop.helpers.exception.ErrorException.Companion.error
 import eu.mjdev.desktop.helpers.exception.SuccessException.Companion.success
+import eu.mjdev.desktop.log.Log
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -54,9 +55,8 @@ class Shell(
     }.also { processBuilder ->
         runCatching {
             processBuilder.start()
-
         }.onFailure { e ->
-            e.printStackTrace()
+            Log.e(e)
         }
     }
 
@@ -100,7 +100,7 @@ class Shell(
         fun executeAndRead(
             cmd: String,
             vararg args: String,
-            onError: (Throwable) -> Unit = { e -> e.printStackTrace() }
+            onError: (Throwable) -> Unit = { e -> Log.e(e) }
         ): String = runCatching {
             Runtime.getRuntime().exec(arrayOf(cmd) + args).apply {
                 waitFor()
@@ -108,13 +108,13 @@ class Shell(
                 onError(e)
             }
         }.onFailure { e ->
-            e.printStackTrace()
+            onError(e)
         }.getOrNull()?.inputReader()?.readText().orEmpty()
 
         fun executeAndReadLines(
             cmd: String,
             vararg args: String,
-            onError: (Throwable) -> Unit = { e -> e.printStackTrace() }
+            onError: (Throwable) -> Unit = { e -> Log.e(e) }
         ): List<String> = runCatching {
             Runtime.getRuntime().exec(arrayOf(cmd) + args).apply {
                 waitFor()
@@ -144,5 +144,3 @@ class Shell(
         }
     }
 }
-
-

+ 79 - 0
src/main/kotlin/eu/mjdev/desktop/log/Log.kt

@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.log
+
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.PrintStream
+import java.io.PrintWriter
+
+// todo colors
+object Log {
+    private val creationDate = System.currentTimeMillis().toString()
+    private val logFile = File("/var/tmp/mjdev-desktop/log/$creationDate.log")
+    private val logWriter = PrintWriter(logFile.apply {
+        runCatching {
+            parentFile.mkdirs()
+            createNewFile()
+        }
+    })
+
+    fun i(message: String) {
+        println(message)
+        runCatching {
+            logWriter.println(message)
+        }
+    }
+
+    fun i(e: Throwable) {
+        e.completeText.also { message ->
+            println(message)
+            runCatching {
+                logWriter.println(message)
+            }
+        }
+    }
+
+    fun w(message: String) {
+        println(message)
+        runCatching {
+            logWriter.println(message)
+        }
+    }
+
+    fun w(e: Throwable) {
+        e.completeText.also { message ->
+            println(message)
+            runCatching {
+                logWriter.println(message)
+            }
+        }
+    }
+
+    fun e(message: String) {
+        RuntimeException(message).also { e -> e(e) }
+    }
+
+    fun e(e: Throwable) {
+        e.completeText.also { message ->
+            println(message)
+            runCatching {
+                logWriter.println(message)
+            }
+        }
+    }
+
+    private val Throwable.completeText: String
+        get() {
+            val out = ByteArrayOutputStream()
+            val stream = PrintStream(out)
+            printStackTrace(stream)
+            return String(out.toByteArray())
+        }
+}

+ 4 - 3
src/main/kotlin/eu/mjdev/desktop/managers/artificialintelligence/actions/ActionsProvider.kt

@@ -9,6 +9,7 @@
 package eu.mjdev.desktop.managers.artificialintelligence.actions
 
 import eu.mjdev.desktop.extensions.Custom.addIfNotExists
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.managers.artificialintelligence.actions.base.Action
 import eu.mjdev.desktop.managers.artificialintelligence.actions.base.ActionException
 import eu.mjdev.desktop.managers.artificialintelligence.actions.base.ActionException.ActionFail
@@ -62,7 +63,7 @@ class ActionsProvider(
                 a.text.contentEquals(t, true) || a.history.contains(t)
             }
             if (action != null) {
-                println("got action ${action}, relevance: exact.")
+                Log.i("got action ${action}, relevance: exact.")
             } else {
                 val fuzzy: Pair<Int, Action>? = map { a ->
                     Pair(FuzzySearch.ratio(a.text, t), a)
@@ -71,7 +72,7 @@ class ActionsProvider(
                 }?.let { p -> if (p.first > 70) p else null }
                 action = fuzzy?.second
                 if (action != null) {
-                    println("got action ${fuzzy?.second}, relevance: ${fuzzy?.first}.")
+                    Log.i("got action ${fuzzy?.second}, relevance: ${fuzzy?.first}.")
                 }
             }
             if (action != null) {
@@ -88,4 +89,4 @@ class ActionsProvider(
             }
         }
     }
-}
+}

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/managers/artificialintelligence/tts/TTSPluginMain.kt

@@ -8,6 +8,7 @@
 
 package eu.mjdev.desktop.managers.artificialintelligence.tts
 
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.managers.artificialintelligence.tts.base.TTSPlugin
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -25,9 +26,9 @@ class TTSPluginMain(
     }
 
     override fun talk(text: String, clearQueue: Boolean) {
-        println("talking: $text")
+        Log.i("talking: $text")
         scope.launch(Dispatchers.IO) {
             textToSpeech.await()?.say(text, clearQueue)
         }
     }
-}
+}

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/managers/artificialintelligence/tts/TTSPluginSwift.kt

@@ -9,6 +9,7 @@
 package eu.mjdev.desktop.managers.artificialintelligence.tts
 
 import eu.mjdev.desktop.helpers.system.Shell
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.managers.artificialintelligence.tts.base.TTSPlugin
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -19,7 +20,7 @@ class TTSPluginSwift(
     val scope: CoroutineScope
 ) : TTSPlugin {
     override fun talk(text: String, clearQueue: Boolean) {
-        println("talking: $text")
+        Log.i("talking: $text")
         scope.launch(Dispatchers.IO) {
             Shell.executeAndRead(
                 "/opt/swift/bin/swift",
@@ -27,4 +28,4 @@ class TTSPluginSwift(
             )
         }
     }
-}
+}

+ 31 - 20
src/main/kotlin/eu/mjdev/desktop/managers/connectivity/ConnectivityManager.kt

@@ -2,42 +2,53 @@ package eu.mjdev.desktop.managers.connectivity
 
 import eu.mjdev.desktop.data.WifiInfo
 import eu.mjdev.desktop.helpers.system.Shell
-import eu.mjdev.desktop.helpers.system.Shell.Companion.CMD_NMCLI
-import eu.mjdev.desktop.helpers.system.Shell.Companion.CMD_NMCLI_GET_NETWORKS
-import eu.mjdev.desktop.helpers.system.Shell.Companion.CMD_NMCLI_RESCAN
+import eu.mjdev.desktop.log.Log
 
+// todo improve
 @Suppress("MemberVisibilityCanBePrivate", "unused")
 class ConnectivityManager {
-    // todo
-    val isWifiAdapterAvailable
-        get() = true
+    val netDevices: List<String> = Shell.executeAndReadLines(
+        "ls",
+        "-1",
+        "/sys/class/net/"
+    ).filter { nd -> !nd.contentEquals("lo") }
+
+    val isWifiAdapterAvailable: Boolean
+        get() = netDevices.map { wa ->
+            Shell.executeAndReadLines(
+                "ls",
+                "-1",
+                "/sys/class/net/$wa/"
+            ).any { w -> w.contentEquals("wireless") }
+        }.any { hasWifi -> hasWifi }
 
     // todo
     val isEthAdapterAvailable
-        get() = true
+        get() = netDevices.isNotEmpty()
 
     // todo
     val isBthAdapterAvailable
-        get() = true
+        get() = netDevices.isNotEmpty()
 
     // todo dbus
     val wifiConnections
         get() = try {
-            rescanWifi()
-            getWifiNetworks()
-        } catch (t: Throwable) {
-            t.printStackTrace()
+//            rescanWifi()
+//            getWifiNetworks()
+            emptyList<WifiInfo>()
+        } catch (e: Throwable) {
+            Log.e(e)
             emptyList()
         }
 
-    fun rescanWifi() = Shell.executeAndReadLines(
-        CMD_NMCLI, *CMD_NMCLI_RESCAN
-    )
+//    fun rescanWifi() = Shell.executeAndReadLines(
+//        CMD_NMCLI, *CMD_NMCLI_RESCAN
+//    )
 
-    fun getWifiNetworks() = Shell.executeAndReadLines(
-        CMD_NMCLI, *CMD_NMCLI_GET_NETWORKS
-    ).map { ws ->
-        WifiInfo(ws.split(":"))
-    }.distinctBy { it.ssid }.sortedByDescending { it.isActive }
+//    fun getWifiNetworks() = Shell.executeAndReadLines(
+//        CMD_NMCLI, *CMD_NMCLI_GET_NETWORKS
+//    ).map { ws ->
+//        WifiInfo(ws.split(":"))
+//    }.distinctBy { it.ssid }.sortedByDescending { it.isActive }
 
 }

+ 81 - 0
src/main/kotlin/eu/mjdev/desktop/managers/connectivity/DeviceType.kt

@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.managers.connectivity
+
+// "/sys/class/net/$wa/type"
+enum class DeviceType(
+    val type: Int
+) {
+    ARPHRD_NETROM(0),               /* from KA9Q: NET/ROM pseudo	*/
+    ARPHRD_ETHER(1),                /* Ethernet 10Mbps		*/
+    ARPHRD_EETHER(2),               /* Experimental Ethernet	*/
+    ARPHRD_AX25(3),                 /* AX.25 Level 2		*/
+    ARPHRD_PRONET(4),               /* PROnet token ring		*/
+    ARPHRD_CHAOS(5),                /* Chaosnet			*/
+    ARPHRD_IEEE802(6),              /* IEEE 802.2 Ethernet/TR/TB	*/
+    ARPHRD_ARCNET(7),               /* ARCnet			*/
+    ARPHRD_APPLETLK(8),             /* APPLEtalk			*/
+    ARPHRD_DLCI(15),                /* Frame Relay DLCI		*/
+    ARPHRD_ATM(19),                 /* ATM 				*/
+    ARPHRD_METRICOM(23),            /* Metricom STRIP (new IANA id)	*/
+    ARPHRD_IEEE1394(24),            /* IEEE 1394 IPv4 - RFC 2734	*/
+    ARPHRD_EUI64(27),               /* EUI-64                       */
+    ARPHRD_INFINIBAND(32),          /* InfiniBand			*/
+    ARPHRD_SLIP(256),
+    ARPHRD_CSLIP(257),
+    ARPHRD_SLIP6(258),
+    ARPHRD_CSLIP6(259),
+    ARPHRD_RSRVD(260),              /* Notional KISS type 		*/
+    ARPHRD_ADAPT(264),
+    ARPHRD_ROSE(270),
+    ARPHRD_X25(271),                /* CCITT X.25			*/
+    ARPHRD_HWX25(272),              /* Boards with X.25 in firmware	*/
+    ARPHRD_CAN(280),                /* Controller Area Network      */
+    ARPHRD_MCTP(290),
+    ARPHRD_PPP(512),
+    ARPHRD_CISCO(513),              /* Cisco HDLC	 		*/
+    ARPHRD_HDLC(ARPHRD_CISCO.type),
+    ARPHRD_LAPB(516),               /* LAPB				*/
+    ARPHRD_DDCMP(517),              /* Digital's DDCMP protocol     */
+    ARPHRD_RAWHDLC(518),            /* Raw HDLC			*/
+    ARPHRD_RAWIP(519),              /* Raw IP                       */
+    ARPHRD_TUNNEL(768),             /* IPIP tunnel			*/
+    ARPHRD_TUNNEL6(769),            /* IP6IP6 tunnel       		*/
+    ARPHRD_FRAD(770),               /* Frame Relay Access Device    */
+    ARPHRD_SKIP(771),               /* SKIP vif			*/
+    ARPHRD_LOOPBACK(772),           /* Loopback device		*/
+    ARPHRD_LOCALTLK(773),           /* Localtalk device		*/
+    ARPHRD_FDDI(774),               /* Fiber Distributed Data Interface */
+    ARPHRD_BIF(775),                /* AP1000 BIF                   */
+    ARPHRD_SIT(776),                /* sit0 device - IPv6-in-IPv4	*/
+    ARPHRD_IPDDP(777),              /* IP over DDP tunneller	*/
+    ARPHRD_IPGRE(778),              /* GRE over IP			*/
+    ARPHRD_PIMREG(779),             /* PIMSM register interface	*/
+    ARPHRD_HIPPI(780),              /* High Performance Parallel Interface */
+    ARPHRD_ASH(781),                /* Nexus 64Mbps Ash		*/
+    ARPHRD_ECONET(782),             /* Acorn Econet			*/
+    ARPHRD_IRDA(783),               /* Linux-IrDA			*/
+    ARPHRD_FCPP(784),               /* Point to point fibrechannel	*/
+    ARPHRD_FCAL(785),               /* Fibrechannel arbitrated loop */
+    ARPHRD_FCPL(786),               /* Fibrechannel public loop	*/
+    ARPHRD_FCFABRIC(787),           /* Fibrechannel fabric		*/
+    ARPHRD_IEEE802_TR(800),         /* Magic type ident for TR	*/
+    ARPHRD_IEEE80211(801),          /* IEEE 802.11			*/
+    ARPHRD_IEEE80211_PRISM(802),    /* IEEE 802.11 + Prism2 header  */
+    ARPHRD_IEEE80211_RADIOTAP(803), /* IEEE 802.11 + radiotap header */
+    ARPHRD_IEEE802154(804),
+    ARPHRD_IEEE802154_MONITOR(805), /* IEEE 802.15.4 network monitor */
+    ARPHRD_PHONET(820),             /* PhoNet media type		*/
+    ARPHRD_PHONET_PIPE(821),        /* PhoNet pipe header		*/
+    ARPHRD_CAIF(822),               /* CAIF media type		*/
+    ARPHRD_IP6GRE(823),             /* GRE over IPv6		*/
+    ARPHRD_NETLINK(824),            /* Netlink header		*/
+    ARPHRD_6LOWPAN(825),            /* IPv6 over LoWPAN             */
+    ARPHRD_VSOCKMON(826),           /* Vsock monitor header		*/
+}

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/managers/dbus/DBusManager.kt

@@ -32,7 +32,7 @@ class DBusManager {
 //    val currentColorScheme get() = settingsInterface.Read("org.freedesktop.appearance", "color-scheme")
 //
 //    init {
-//        println(recursiveVariantValue(currentColorScheme))
+//        Log.i(recursiveVariantValue(currentColorScheme))
 //    }
 //
 //    fun recursiveVariantValue(variant: Variant<*>): Any {

+ 20 - 15
src/main/kotlin/eu/mjdev/desktop/managers/kcef/linux/KCEFManagerLinux.kt

@@ -9,6 +9,7 @@
 package eu.mjdev.desktop.managers.kcef.linux
 
 import dev.datlag.kcef.KCEF
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.managers.kcef.base.KCEFManagerStub
 import eu.mjdev.desktop.provider.DesktopProvider
 import kotlinx.coroutines.CoroutineScope
@@ -25,24 +26,28 @@ class KCEFManagerLinux(
     override fun init() {
         scope.launch(Dispatchers.IO) {
             if (!initialized.value && !restartRequired.value) {
-                KCEF.init(builder = {
-                    installDir(File("/tmp/kcef-bundle"))
-                    progress {
-                        onDownloading {
-                            downloading.value = max(it, 0F).toInt()
+                KCEF.init(
+                    builder = {
+                        installDir(File("/tmp/kcef-bundle"))
+                        progress {
+                            onDownloading {
+                                downloading.value = max(it, 0F).toInt()
+                            }
+                            onInitialized {
+                                initialized.value = true
+                            }
                         }
-                        onInitialized {
-                            initialized.value = true
+                        settings {
+                            cachePath = File("cache").absolutePath
                         }
+                    },
+                    onError = { e ->
+                        if (e != null) Log.e(e)
+                    },
+                    onRestartRequired = {
+                        restartRequired.value = true
                     }
-                    settings {
-                        cachePath = File("cache").absolutePath
-                    }
-                }, onError = { err ->
-                    err?.printStackTrace()
-                }, onRestartRequired = {
-                    restartRequired.value = true
-                })
+                )
             }
         }
     }

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/managers/theme/linux/ThemeManagerLinux.kt

@@ -17,6 +17,7 @@ import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 import eu.mjdev.desktop.extensions.Compose.hexRgb
 import eu.mjdev.desktop.extensions.Custom.get
 import eu.mjdev.desktop.helpers.system.Shell
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.managers.theme.base.ThemeManagerStub
 import eu.mjdev.desktop.provider.DesktopProvider
 import java.io.File
@@ -43,9 +44,9 @@ class ThemeManagerLinux(
     private val systemGtk4CssFile = systemGtk4ThemeDir["gtk.css"]
 
     override fun clearSystemTheme() {
-        println("Deleting : file:///${systemGtk3CssFile.absolutePath}")
+        Log.i("Deleting : file:///${systemGtk3CssFile.absolutePath}")
         systemGtk3CssFile.delete()
-        println("Deleting : file:///${systemGtk4CssFile.absolutePath}")
+        Log.i("Deleting : file:///${systemGtk4CssFile.absolutePath}")
         systemGtk4CssFile.delete()
     }
 

+ 6 - 6
src/main/kotlin/eu/mjdev/desktop/managers/window/WindowsManager.kt

@@ -38,11 +38,11 @@ class WindowsManager(
 //        get() = environment["WAYLAND_DISPLAY"]
 //    val WlRegistryEvents = object : WlRegistryEvents {
 //        override fun global(emitter: WlRegistryProxy?, name: Int, interface_: String, version: Int) {
-//            println("Got global event: $name")
+//            Log.i("Got global event: $name")
 //        }
 //
 //        override fun globalRemove(emitter: WlRegistryProxy?, name: Int) {
-//            println("Got global remove event: $name")
+//            Log.i("Got global remove event: $name")
 //        }
 //    }
 //    val display: WlDisplayProxy by lazy { WlDisplayProxy.connect(currentDisplay) }
@@ -62,8 +62,8 @@ class WindowsManager(
     }
 
 //    init {
-//        println("Current display : $currentDisplay")
-//        println("Registry : ${registry.id}")
+//        Log.i("Current display : $currentDisplay")
+//        Log.i("Registry : ${registry.id}")
 //    }
 
     fun dispose() {
@@ -72,7 +72,7 @@ class WindowsManager(
     }
 
 //    fun findWindows(app: App): List<SystemWindow> = windows.filter { w ->
-//        println("w: (${w.id},${w.windowClass}, ${w.command})")
+//        Log.i("w: (${w.id},${w.windowClass}, ${w.command})")
 //        w.windowClass.isNotEmpty()
 //    }.filter { w ->
 //        w.windowClass.trim().contains(app.windowClass, true) ||
@@ -80,7 +80,7 @@ class WindowsManager(
 //                w.command.trim().contains(app.fullAppName, true) ||
 //                w.command.trim().contentEquals(app.cmd, true)
 //    }.apply {
-//        println("app[${app.windowClass}] : ${count()}")
+//        Log.i("app[${app.windowClass}] : ${count()}")
 //    }
 
     fun hasWindow(app: App): Boolean =

+ 13 - 9
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -7,9 +7,11 @@ import coil3.ImageLoader
 import eu.mjdev.desktop.components.controlcenter.pages.*
 import eu.mjdev.desktop.data.User
 import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
+import eu.mjdev.desktop.extensions.Compose.isDesign
 import eu.mjdev.desktop.helpers.application.ApplicationScope
-import eu.mjdev.desktop.helpers.internal.Palette
+import eu.mjdev.desktop.helpers.palette.Palette
 import eu.mjdev.desktop.helpers.system.Shell
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.managers.apps.AppsManager
 import eu.mjdev.desktop.managers.artificialintelligence.AIManager
 import eu.mjdev.desktop.managers.artificialintelligence.AIManager.Companion.aiManager
@@ -38,6 +40,7 @@ class DesktopProvider(
     val scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
     val args: List<String> = emptyList(),
     val imageLoader: ImageLoader? = null,
+    val isInDesign: Boolean = false
 ) : AutoCloseable {
     var isFirstStart: Boolean = true // todo
     var isInstalled: Boolean = false // todo
@@ -62,7 +65,7 @@ class DesktopProvider(
     val appArgs
         get() = application?.args ?: emptyList()
     val isDebug: Boolean
-        get() = false // todo
+        get() = args.contains(APP_PARAM_DEBUG) || isInDesign
     val homeDir
         get() = currentUser.homeDir
     val allUsers
@@ -80,11 +83,11 @@ class DesktopProvider(
 //    val adbHandler = adbDevicesHandler(
 //        coroutineScope = scope,
 //        onAdded = { device ->
-//            println("Device discovered : $device")
+//            Log.i("Device discovered : $device")
 //            // when device connected
 //        },
 //        onRemoved = { device ->
-//            println("Device removed : $device")
+//            Log.i("Device removed : $device")
 //            // when device removed
 //        }
 //    )
@@ -120,6 +123,7 @@ class DesktopProvider(
         get() = graphicsDevice.isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency.TRANSLUCENT)
 
 //    init {
+//        Log.i("Got app args: $args")
 //        runCatching {
 //            windowsManager.init()
 //        }
@@ -132,7 +136,7 @@ class DesktopProvider(
 //    }
 
     override fun close() {
-        println("Cleaning up resources.")
+        Log.i("Cleaning up resources.")
         processManager.dispose()
         palette.dispose()
 //        runCatching {
@@ -261,7 +265,7 @@ class DesktopProvider(
         )
 
         val LocalDesktop = staticCompositionLocalOf {
-            println("Default empty desktop provider created.")
+            Log.i("Default empty desktop provider created.")
             DesktopProvider()
         }
 
@@ -273,16 +277,16 @@ class DesktopProvider(
             application: ApplicationScope?,
             scope: CoroutineScope = rememberCoroutineScope(),
             imageLoader: ImageLoader = asyncImageLoader(),
-//            isDebug: Boolean = LocalInspectionMode.current,
             args: List<String> = emptyList(),
+            isInDesign: Boolean = isDesign
         ) = remember {
-            println("default initialized desktop provider created")
+            Log.i("default initialized desktop provider created")
             DesktopProvider(
                 application = application,
                 scope = scope,
                 imageLoader = imageLoader,
                 args = args,
-//                isDebug = isDebug || args.contains(APP_PARAM_DEBUG)
+                isInDesign = isInDesign
             )
         }
     }

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

@@ -118,8 +118,21 @@ open class DesktopScope(
     val appMenuMinHeight = theme.appMenuMinHeight
     val menuPadding = theme.appMenuOuterPadding
 
+    val panelHideDelay
+        get() = theme.panelHideDelay
     val panelAutoHideEnabled
         get() = theme.panelHideDelay > 0L
+    val panelDividerWidth
+        get() = theme.panelDividerWidth
+
+    val controlPanelHideDelay
+        get() = theme.controlPanelHideDelay
+    val controlCenterBackgroundAlpha
+        get() = theme.controlCenterBackgroundAlpha
+    val controlCenterDividerWidth
+        get() = theme.controlCenterDividerWidth
+    val controlCenterIconSize
+        get() = theme.controlCenterIconSize
 
     val backgrounds
         get() = currentUser.backgrounds

+ 24 - 127
src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindow.kt

@@ -1,164 +1,67 @@
 package eu.mjdev.desktop.windows
 
 import androidx.compose.animation.*
-import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.window.WindowPosition
+import eu.mjdev.desktop.extensions.Compose.isDesign
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
-import eu.mjdev.desktop.windows.ChromeWindowScope.Companion.withChromeWindowScope
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberAnimState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberWnState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.updateAnimState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.updateWindowState
 
-@Suppress("FunctionName", "unused")
-@Preview
+@Suppress("FunctionName")
 @Composable
 fun ChromeWindow(
-    size: DpSize = DpSize(Dp.Unspecified, Dp.Unspecified),
-    position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
-    windowState: ChromeWindowState = rememberChromeWindowState(position = position, size = size),
+    name: String? = null,
     visible: Boolean = true,
     transparent: Boolean = true,
     resizable: Boolean = false,
     enabled: Boolean = true,
     focusable: Boolean = true,
-    alwaysOnTop: Boolean = true,
+    alwaysOnTop: Boolean = false,
     alwaysOnBottom: Boolean = false,
-    enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
-    exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
-    onCreate: ChromeWindowState.() -> Unit = {},
-    onCloseRequest: () -> Unit = {},
-    onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
-    onKeyEvent: (KeyEvent) -> Boolean = { false },
-    onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onOpened: ChromeWindowState.() -> Unit = {},
-    onClosed: ChromeWindowState.() -> Unit = {},
-    content: @Composable () -> Unit = {}
-) = ChromeWindow(
-    windowState = windowState,
-    visible = visible,
-    transparent = transparent,
-    resizable = resizable,
-    enabled = enabled,
-    focusable = focusable,
-    alwaysOnTop = alwaysOnTop,
-    alwaysOnBottom = alwaysOnBottom,
-    enterAnimation = enterAnimation,
-    exitAnimation = exitAnimation,
-    onCreate = onCreate,
-    onCloseRequest = onCloseRequest,
-    onPreviewKeyEvent = onPreviewKeyEvent,
-    onKeyEvent = onKeyEvent,
-    onFocusChange = onFocusChange,
-    onOpened = onOpened,
-    onClosed = onClosed,
-    content = content
-)
-
-@Suppress("unused", "FunctionName")
-@Preview
-@Composable
-fun ChromeWindow(
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
-    windowState: ChromeWindowState = rememberChromeWindowState(position = position),
-    visible: Boolean = true,
-    transparent: Boolean = true,
-    resizable: Boolean = false,
-    enabled: Boolean = true,
-    focusable: Boolean = true,
-    alwaysOnTop: Boolean = true,
-    alwaysOnBottom: Boolean = false,
+    size: DpSize = DpSize(Dp.Unspecified, Dp.Unspecified),
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
-    onCreate: ChromeWindowState.() -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
-    onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
+    onCreated: ChromeWindowState.() -> Unit = {},
     onOpened: ChromeWindowState.() -> Unit = {},
     onClosed: ChromeWindowState.() -> Unit = {},
-    content: @Composable () -> Unit = {}
-) = ChromeWindow(
-    windowState = windowState,
-    visible = visible,
-    transparent = transparent,
-    resizable = resizable,
-    enabled = enabled,
-    focusable = focusable,
-    alwaysOnTop = alwaysOnTop,
-    alwaysOnBottom = alwaysOnBottom,
-    enterAnimation = enterAnimation,
-    exitAnimation = exitAnimation,
-    onCreate = onCreate,
-    onCloseRequest = onCloseRequest,
-    onPreviewKeyEvent = onPreviewKeyEvent,
-    onKeyEvent = onKeyEvent,
-    onFocusChange = onFocusChange,
-    onOpened = onOpened,
-    onClosed = onClosed,
-    content = content
-)
-
-@Suppress("FunctionName")
-@Preview
-@Composable
-fun ChromeWindow(
-    windowState: ChromeWindowState = rememberChromeWindowState(),
-    visible: Boolean = true,
-    transparent: Boolean = true,
-    resizable: Boolean = false,
-    enabled: Boolean = true,
-    focusable: Boolean = true,
-    alwaysOnTop: Boolean = true,
-    alwaysOnBottom: Boolean = false,
-    enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
-    exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
-    onCloseRequest: () -> Unit = {},
-    onCreate: ChromeWindowState.() -> Unit = {},
-    onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
-    onKeyEvent: (KeyEvent) -> Boolean = { false },
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onOpened: ChromeWindowState.() -> Unit = {},
-    onClosed: ChromeWindowState.() -> Unit = {},
+    windowState: ChromeWindowState = rememberChromeWindowState(
+        visible = visible,
+        size = size,
+        position = position
+    ),
     content: @Composable () -> Unit = {}
 ) = withDesktopScope {
-    if (isDebug) {
+    if (isDesign) {
         content()
     } else {
         val animState = rememberAnimState(visible)
         val wnState = rememberWnState()
-        windowState.onOpened.add {
-//        if (alwaysOnBottom) {
-            // todo
-//            val focusOwner = window?.mostRecentFocusOwner
-//            window?.toBack()
-//            focusOwner?.requestFocus()
-//        }
-//        else if (alwaysOnTop) {
-//            windowState.requestFocus()
-//        }
-            windowState.updatePositionAndSize()
+        windowState.onOpened {
             if (visible) {
                 animState.value.targetState = true
             }
             onOpened(windowState)
         }
-        windowState.onClosed.add(onClosed)
-        windowState.onFocusChange.add(onFocusChange)
+        windowState.onClosed(onClosed)
+        windowState.onFocusChange(onFocusChange)
         Window(
-            onCreate = { window ->
-                windowState.window = window
-                onCreate(windowState)
-            },
-//        onOpen = {
-//            onOpened(windowState)
-//        },
+            name = name,
+            onCreated = { onCreated(windowState) },
+            onOpened = { onOpened(windowState) },
+            onClosed = { onClosed(windowState) },
             onCloseRequest = onCloseRequest,
             state = windowState,
             visible = wnState.value,
@@ -176,18 +79,12 @@ fun ChromeWindow(
             stateHelper = windowState.stateHelper,
             focusHelper = windowState.focusHelper,
             content = {
-                withChromeWindowScope(
-                    windowState,
-                    animState,
-                    wnState
+                AnimatedVisibility(
+                    animState.value,
+                    enter = enterAnimation,
+                    exit = exitAnimation
                 ) {
-                    AnimatedVisibility(
-                        animState.value,
-                        enter = enterAnimation,
-                        exit = exitAnimation
-                    ) {
-                        content()
-                    }
+                    content()
                 }
             }
         )

+ 0 - 78
src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowScope.kt

@@ -1,78 +0,0 @@
-package eu.mjdev.desktop.windows
-
-import androidx.compose.animation.core.MutableTransitionState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.State
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.DpOffset
-import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.FrameWindowScope
-import androidx.compose.ui.window.WindowPosition
-
-@Suppress("unused", "MemberVisibilityCanBePrivate")
-class ChromeWindowScope(
-    val frameScope: FrameWindowScope,
-    val windowState: ChromeWindowState,
-    val animState: State<MutableTransitionState<Boolean>>,
-    val windowVisibleState: MutableState<Boolean>
-) {
-    val window
-        get() = frameScope.window
-
-    val stateHelper: WindowStateHelper
-        get() = windowState.stateHelper
-
-    val focusHelper: WindowFocusHelper
-        get() = windowState.focusHelper
-
-    val x : Dp
-        get() = frameScope.window.x.dp
-
-    val y : Dp
-        get() = frameScope.window.y.dp
-
-    val width : Dp
-        get() = frameScope.window.width.dp
-
-    val height : Dp
-        get() = frameScope.window.height.dp
-
-    fun apply(
-        block: ChromeWindowScope.() -> Unit
-    ) = with(this) {
-        block()
-    }
-
-//    fun requestFocus() =
-//        stateHelper.requestFocus()
-
-    fun setBounds(
-        x: Dp = this.x,
-        y: Dp = this.y,
-        width: Dp = stateHelper.width,
-        height: Dp = stateHelper.height
-    ) = stateHelper.setBounds(x, y, width, height)
-
-    fun setOffset(offset: DpOffset) =
-        stateHelper.setBounds(x = offset.x, y = offset.y)
-
-    fun setSize(size: DpSize) =
-        stateHelper.setBounds(width = size.width, height = size.height)
-
-    fun setPosition(position: WindowPosition) =
-        stateHelper.setPosition(position)
-
-    companion object {
-        @Composable
-        fun FrameWindowScope.withChromeWindowScope(
-            windowState: ChromeWindowState,
-            animState: State<MutableTransitionState<Boolean>>,
-            windowVisibleState: MutableState<Boolean>,
-            block: @Composable ChromeWindowScope.() -> Unit
-        ) = with(ChromeWindowScope(this, windowState, animState, windowVisibleState)) {
-            block()
-        }
-    }
-}

+ 136 - 48
src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowState.kt

@@ -5,6 +5,7 @@ import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPlacement
 import androidx.compose.ui.window.WindowPosition
 import androidx.compose.ui.window.WindowState
@@ -12,81 +13,165 @@ import eu.mjdev.desktop.components.sliding.base.VisibilityState
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
 import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.extensions.Compose.rememberState
-
-@Suppress("MemberVisibilityCanBePrivate")
-class ChromeWindowState(
+import eu.mjdev.desktop.log.Log
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.awt.Window
+
+@Suppress("RedundantSuspendModifier", "MemberVisibilityCanBePrivate")
+open class ChromeWindowState(
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
     size: DpSize = DpSize(Dp.Unspecified, Dp.Unspecified),
     placement: WindowPlacement = WindowPlacement.Floating,
     closeAction: WindowCloseAction,
     visible: Boolean = false,
     enabled: Boolean = true,
+    hideDelay: Long = 0L,
+    scope: CoroutineScope = CoroutineScope(Dispatchers.Main),
+    var tooltipHeight: Dp = 0.dp,
 ) : VisibilityState(
     startState = visible,
-    enabled = enabled
+    enabled = enabled,
+    hideDelay = hideDelay,
+    scope = scope
 ), WindowState {
 
-    var onFocusChange: MutableList<ChromeWindowState.(focus: Boolean) -> Unit> = mutableStateListOf()
-    var onOpened: MutableList<ChromeWindowState.() -> Unit> = mutableStateListOf()
-    var onClosed: MutableList<ChromeWindowState.() -> Unit> = mutableStateListOf()
+    private var isCreated = false
+
+    private var onFocusChange: MutableList<ChromeWindowState.(focus: Boolean) -> Unit> = mutableStateListOf()
+    private var onOpened: MutableList<ChromeWindowState.() -> Unit> = mutableStateListOf()
+    private var onClosed: MutableList<ChromeWindowState.() -> Unit> = mutableStateListOf()
 
-    private val focusState: MutableState<Boolean> = mutableStateOf(false)
-    var isFocused: Boolean
-        get() = focusState.value
-        internal set(value) {
-            focusState.value = value
+    var window: Window? = null
+        get() = field ?: run {
+            Log.e("Window is empty, implementation error.")
+            null
+        }
+        set(value) {
+            if (value != null) {
+                field = value
+                isCreated = true
+            } else {
+                Log.e("Attempt to set window value to null.")
+            }
         }
 
-    val focusHelper: WindowFocusHelper = WindowFocusHelper { _, focus ->
-        isFocused = focus
-        this@ChromeWindowState.onFocusChange.forEach {
-            it.invoke(this@ChromeWindowState, focus)
+    val focusHelper: WindowFocusListener = WindowFocusListener { _, focus ->
+        this@ChromeWindowState.onFocusChange.forEach { listener ->
+            listener.invoke(this@ChromeWindowState, focus)
         }
     }
 
-    val stateHelper: WindowStateHelper = WindowStateHelper({
-        setSize(size)
-        this@ChromeWindowState.onOpened.forEach {
-            it.invoke(this@ChromeWindowState)
+    val stateHelper: WindowStateListener = WindowStateListener({
+        this@ChromeWindowState.onOpened.forEach { state ->
+            state.invoke(this@ChromeWindowState)
         }
     }, {
-        this@ChromeWindowState.onClosed.forEach {
-            it.invoke(this@ChromeWindowState)
+        this@ChromeWindowState.onClosed.forEach { state ->
+            state.invoke(this@ChromeWindowState)
         }
     })
 
-    var window
-        get() = stateHelper.window
-        set(value) {
-            stateHelper.window = value
-        }
-
     @Suppress("CanBePrimaryConstructorProperty")
     val closeAction: WindowCloseAction = closeAction
 
+    val isFocused
+        get() = window?.isFocused ?: false
+
     override var position: WindowPosition = position
         set(value) {
+            Log.i("Setting position of window : ${window?.name}")
             field = value
-            stateHelper.setPosition(position)
+            if (isCreated) {
+                scope.launch {
+                    setPosition(value)
+                }
+            }
         }
 
     override var size: DpSize = size
         set(value) {
+            Log.i("Setting size of window : ${window?.name}")
+            val oldSize = field
             field = value
-            stateHelper.setSize(value)
+            if (isCreated) {
+                scope.launch {
+                    moveBy(
+                        value.width - oldSize.width,
+                        value.height - oldSize.height
+                    )
+                    setSize(value)
+                }
+            }
         }
 
     @Suppress("CanBePrimaryConstructorProperty")
     override var placement: WindowPlacement = placement
+
     override var isMinimized: Boolean = false
 
-    fun requestFocus() {
-        stateHelper.requestFocus()
+    val x: Dp
+        get() = position.x
+
+    val y: Dp
+        get() = position.y
+
+    val height: Dp
+        get() = size.height
+
+    val width: Dp
+        get() = size.width
+
+    fun onOpened(block: ChromeWindowState.() -> Unit) {
+        onOpened.add(block)
+    }
+
+    fun onClosed(block: ChromeWindowState.() -> Unit) {
+        onClosed.add(block)
     }
 
-    fun updatePositionAndSize() {
-        stateHelper.setSize(size)
-        stateHelper.setPosition(position)
+    fun onFocusChange(block: ChromeWindowState.(Boolean) -> Unit) {
+        onFocusChange.add(block)
+    }
+
+    suspend fun moveBy(x: Dp, y: Dp) = runCatching {
+        Log.i("moveBy called ($x, $y)")
+        if (position is WindowPosition.Absolute) {
+            setPosition(
+                WindowPosition.Absolute(
+                    position.x - x,
+                    position.y - y
+                )
+            )
+        } else {
+            setPosition(position)
+        }
+    }.onFailure { e -> Log.e(e) }
+
+    suspend fun setSize(size: DpSize) = runCatching {
+        Log.i("Setting window size : ${window?.name} to $size")
+        window?.setSizeSafely(size, placement)
+    }.onFailure { e -> Log.e(e) }
+
+    suspend fun setPosition(position: WindowPosition) = runCatching {
+        Log.i("Setting window position : ${window?.name} to $position")
+        window?.setPosition(position, placement)
+    }.onFailure { e -> Log.e(e) }
+
+    suspend fun requestFocus() = runCatching {
+        Log.i("Requesting window focus : ${window?.name}")
+        window?.toFront()
+        if (!isFocused) {
+            window?.requestFocus()
+        }
+    }.onFailure { e -> Log.e(e) }
+
+    suspend fun showOrFocus() {
+        if (isNotVisible) {
+            show()
+        }
+        requestFocus()
     }
 
     companion object {
@@ -99,8 +184,21 @@ class ChromeWindowState(
             closeAction: WindowCloseAction = WindowCloseAction.CLOSE,
             visible: Boolean = false,
             enabled: Boolean = true,
-        ) = remember(position, size, placement, closeAction, visible) {
-            ChromeWindowState(position, size, placement, closeAction, visible, enabled)
+            hideDelay: Long = 0L,
+            tooltipHeight: Dp = 0.dp,
+            scope: CoroutineScope = rememberCoroutineScope()
+        ) = remember(
+            position,
+            size,
+            placement,
+            closeAction,
+            visible,
+            enabled,
+            hideDelay,
+            scope,
+            tooltipHeight
+        ) {
+            ChromeWindowState(position, size, placement, closeAction, visible, enabled, hideDelay, scope, tooltipHeight)
         }
 
         @Composable
@@ -124,8 +222,6 @@ class ChromeWindowState(
             if (animState.value.targetState == currentState && !visible) {
                 if (state.closeAction == WindowCloseAction.CLOSE) {
                     wnState.value = false
-                } else {
-                    state.updatePositionAndSize()
                 }
             }
         }
@@ -142,13 +238,5 @@ class ChromeWindowState(
                 animState.value.targetState = false
             }
         }
-
     }
-
-    @Suppress("unused")
-    enum class WindowCloseAction {
-        CLOSE,
-        MOVE
-    }
-
-}
+}

+ 7 - 22
src/main/kotlin/eu/mjdev/desktop/windows/DesktopWindow.kt

@@ -26,32 +26,21 @@ fun DesktopWindow(
     content: @Composable BoxScope.() -> Unit = {}
 ) = withDesktopScope {
     FullScreenWindow(
-//    onOpened = {
-//        println("Window opened $window")
-//        window?.toBack()
-//    },
+        name = "DesktopWindow",
+        alwaysOnBottom = true,
+        onOpened = {
+            window?.toBack()
+            window?.isFocusable = false
+        },
         onFocusChange = { focus ->
-//        RuntimeException("Focus change: $it").printStackTrace()
-            // todo better solution
-//        val focusOwner = window?.mostRecentFocusOwner
             if (focus) {
                 window?.toBack()
+                window?.isFocusable = false
             }
-//        focusOwner?.requestFocus()
         }
     ) {
         Box(
             modifier = Modifier.fillMaxSize()
-//            .background(backgroundColor)
-//            .pointerInput(Unit) {
-//                awaitPointerEventScope {
-//                    while (true) {
-//                        awaitPointerEvent(pass = PointerEventPass.Initial)
-//                            .changes
-//                            .forEach(PointerInputChange::consume)
-//                    }
-//                }
-//            }
         ) {
             BackgroundImage(
                 modifier = Modifier
@@ -74,10 +63,6 @@ fun DesktopWindow(
                     }
                 }
             )
-//        WidgetsPanel(
-//            modifier = Modifier
-//                .fillMaxSize()
-//        )
             content()
         }
 //    ContextMenu(

+ 25 - 19
src/main/kotlin/eu/mjdev/desktop/windows/FullScreenWindow.kt

@@ -8,15 +8,18 @@ import androidx.compose.ui.window.WindowPlacement
 import androidx.compose.ui.window.WindowPosition
 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.ChromeWindowState.Companion.rememberChromeWindowState
 
 @Suppress("FunctionName")
 @Preview
 @Composable
 fun FullScreenWindow(
+    name: String? = null,
     api: DesktopProvider = LocalDesktop.current,
     enabled: Boolean = true,
     focusable: Boolean = true,
+    alwaysOnBottom: Boolean = false,
     onOpened: ChromeWindowState.() -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
@@ -26,23 +29,26 @@ fun FullScreenWindow(
         size = api.containerSize,
         position = WindowPosition.Aligned(Alignment.Center)
     ),
-    onCreate: ChromeWindowState.() -> Unit = {},
-    onFocusChange: ChromeWindowState.(Boolean) -> Unit = {
-    },
+    onCreated: ChromeWindowState.() -> Unit = {},
+    onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
     content: @Composable () -> Unit = {}
-) = ChromeWindow(
-    windowState = windowState,
-    enabled = enabled,
-    resizable = false,
-    transparent = true,
-    focusable = focusable,
-    visible = true,
-    alwaysOnTop = false,
-    onPreviewKeyEvent = onPreviewKeyEvent,
-    onKeyEvent = onKeyEvent,
-    onCloseRequest = onCloseRequest,
-    onCreate = onCreate,
-    onOpened = onOpened,
-    onFocusChange = onFocusChange,
-    content = content
-)
+) = withDesktopScope {
+    ChromeWindow(
+        name = name ?: "FullScreenWindow",
+        visible = true,
+        enabled = enabled,
+        resizable = false,
+        transparent = true,
+        focusable = focusable,
+        alwaysOnTop = false,
+        alwaysOnBottom = alwaysOnBottom,
+        onPreviewKeyEvent = onPreviewKeyEvent,
+        onKeyEvent = onKeyEvent,
+        onCloseRequest = onCloseRequest,
+        onCreated = onCreated,
+        onOpened = onOpened,
+        onFocusChange = onFocusChange,
+        windowState = windowState,
+        content = content
+    )
+}

+ 110 - 51
src/main/kotlin/eu/mjdev/desktop/windows/Window.kt

@@ -1,3 +1,5 @@
+@file:Suppress("unused")
+
 package eu.mjdev.desktop.windows
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
@@ -12,6 +14,7 @@ import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.*
 import androidx.compose.ui.window.*
+import eu.mjdev.desktop.log.Log
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 import eu.mjdev.desktop.windows.blur.WindowBlurManager.Companion.windowBlur
 import java.awt.*
@@ -24,19 +27,8 @@ import kotlin.math.roundToInt
 @Preview
 @Composable
 fun Window(
-    onCreate: (window: ComposeWindow) -> Unit = {},
-    onOpen: (window: ComposeWindow) -> Unit = {},
-    onClosing: (window: ComposeWindow) -> Unit = {},
-    onClosed: (window: ComposeWindow) -> Unit = {},
-    onActivated: (window: ComposeWindow) -> Unit = {},
-    onDeactivated: (window: ComposeWindow) -> Unit = {},
-    onIconified: (window: ComposeWindow) -> Unit = {},
-    onDeIconified: (window: ComposeWindow) -> Unit = {},
-    onGainFocus: (window: ComposeWindow) -> Unit = {},
-    onLostFocus: (window: ComposeWindow) -> Unit = {},
-    onStateChanged: (window: ComposeWindow) -> Unit = {},
+    name: String? = null,
     onCloseRequest: () -> Unit,
-    state: ChromeWindowState = rememberChromeWindowState(),
     visible: Boolean = true,
     title: String = "",
     icon: Painter? = null,
@@ -49,12 +41,25 @@ fun Window(
     alwaysOnBottom: Boolean = false,
     closeAction: Int = JFrame.DO_NOTHING_ON_CLOSE,
     blurEnabled: Boolean = false, // todo
-    focusHelper: WindowFocusHelper,
-    stateHelper: WindowStateHelper,
+    focusHelper: WindowFocusListener,
+    stateHelper: WindowStateListener,
+    state: ChromeWindowState = rememberChromeWindowState(),
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
+    onCreated: (window: ComposeWindow) -> Unit = {},
+    onOpened: (window: ComposeWindow) -> Unit = {},
+    onClosing: (window: ComposeWindow) -> Unit = {},
+    onClosed: (window: ComposeWindow) -> Unit = {},
+    onActivated: (window: ComposeWindow) -> Unit = {},
+    onDeactivated: (window: ComposeWindow) -> Unit = {},
+    onIconified: (window: ComposeWindow) -> Unit = {},
+    onDeIconified: (window: ComposeWindow) -> Unit = {},
+    onGainFocus: (window: ComposeWindow) -> Unit = {},
+    onLostFocus: (window: ComposeWindow) -> Unit = {},
+    onStateChanged: (window: ComposeWindow) -> Unit = {},
     content: @Composable FrameWindowScope.() -> Unit
 ) {
+    val currentName by rememberUpdatedState(name ?: "-")
     val currentState by rememberUpdatedState(state)
     val currentTitle by rememberUpdatedState(title)
     val currentIcon by rememberUpdatedState(icon)
@@ -96,13 +101,15 @@ fun Window(
             ComposeWindow(
                 graphicsConfiguration = WindowLocationTracker.lastActiveGraphicsConfiguration
             ).apply {
+                val window = this
+                state.window = window
                 defaultCloseOperation = closeAction
                 listeners.windowListenerRef.registerWithAndSet(
                     this,
                     WindowEventsAdapter(
                         this,
                         currentOnCloseRequest,
-                        onOpen,
+                        onOpened,
                         onClosing,
                         onClosed,
                         onActivated,
@@ -123,7 +130,18 @@ fun Window(
                 listeners.componentListenerRef.registerWithAndSet(
                     this,
                     object : ComponentAdapter() {
+//                        override fun componentShown(e: ComponentEvent?) {
+//                            super.componentShown(e)
+//                        }
+
+//                        override fun componentHidden(e: ComponentEvent?) {
+//                            super.componentHidden(e)
+//                        }
+
                         override fun componentResized(e: ComponentEvent) {
+                            if (currentState.window == null) {
+                                currentState.window = window
+                            }
                             currentState.placement = placement
                             currentState.size = DpSize(width.dp, height.dp)
                             appliedState.placement = currentState.placement
@@ -131,19 +149,22 @@ fun Window(
                         }
 
                         override fun componentMoved(e: ComponentEvent) {
+                            if (currentState.window == null) {
+                                currentState.window = window
+                            }
                             currentState.position = WindowPosition(x.dp, y.dp)
                             appliedState.position = currentState.position
                         }
                     }
                 )
-                onCreate(this)
-                WindowLocationTracker.onWindowCreated(this)
+                WindowLocationTracker.onWindowCreated(window)
+                onCreated(window)
             }
         },
-        dispose = {
-            WindowLocationTracker.onWindowDisposed(it)
-            listeners.removeFromAndClear(it)
-            it.dispose()
+        dispose = { window ->
+            WindowLocationTracker.onWindowDisposed(window)
+            listeners.removeFromAndClear(window)
+            window.dispose()
         },
         update = { window ->
             updater.update {
@@ -153,6 +174,7 @@ fun Window(
                 set(currentTransparent, window::isTransparent::set)
                 set(currentResizable, window::setResizable)
                 set(currentEnabled, window::setEnabled)
+                set(currentName, window::setName)
                 if (currentAlwaysOnBottom) {
                     set(false, window::setFocusableWindowState)
                     set(false, window::setAlwaysOnTop)
@@ -170,7 +192,6 @@ fun Window(
                 window.setPositionSafely(
                     state.position,
                     state.placement,
-                    platformDefaultPosition = { WindowLocationTracker.getCascadeLocationFor(window) }
                 )
                 appliedState.position = state.position
             }
@@ -192,7 +213,7 @@ fun Window(
     )
 }
 
-internal fun Window.align(alignment: Alignment) {
+fun Window.align(alignment: Alignment) {
     val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(graphicsConfiguration)
     val screenBounds = graphicsConfiguration.bounds
     val size = IntSize(size.width, size.height)
@@ -201,36 +222,57 @@ internal fun Window.align(alignment: Alignment) {
         screenBounds.height - screenInsets.top - screenInsets.bottom
     )
     val location = alignment.align(size, screenSize, LayoutDirection.Ltr)
-
-    setLocation(
-        screenBounds.x + screenInsets.left + location.x,
-        screenBounds.y + screenInsets.top + location.y
-    )
+    val x = screenBounds.x + screenInsets.left + location.x
+    val y = screenBounds.y + screenInsets.top + location.y
+    Log.i("Window location (aligned) set to : ($x, $y)")
+    setLocation(x, y)
 }
 
-internal fun Window.setPositionImpl(
+fun Window.setPositionImpl(
     position: WindowPosition,
     platformDefaultPosition: () -> Point
 ) = when (position) {
-    WindowPosition.PlatformDefault -> location = platformDefaultPosition()
-    is WindowPosition.Aligned -> align(position.alignment)
-    is WindowPosition.Absolute -> setLocation(
-        position.x.value.roundToInt(),
-        position.y.value.roundToInt()
-    )
+    WindowPosition.PlatformDefault -> {
+        val loc = platformDefaultPosition()
+        Log.i("Window location (platform) set to : (${loc.x}, ${loc.y})")
+        location = loc
+    }
+
+    is WindowPosition.Aligned -> {
+        align(position.alignment)
+    }
+
+    is WindowPosition.Absolute -> {
+        Log.i("Window location (absolute) set to : $position")
+        if ((position.x != Dp.Unspecified) && (position.y != Dp.Unspecified)) {
+            val x = position.x.value.roundToInt()
+            val y = position.y.value.roundToInt()
+            Log.i("Window location (absolute) set to : ($x,$y)")
+            setLocation(x, y)
+        } else {
+            Log.i("Window location unknown, omitted.")
+        }
+    }
 }
 
-internal fun Window.setPositionSafely(
+fun Window.setPositionSafely(
     position: WindowPosition,
     placement: WindowPlacement,
-    platformDefaultPosition: () -> Point
+    platformDefaultPosition: () -> Point = { WindowLocationTracker.getCascadeLocationFor(this) }
 ) {
     if (!isVisible || (placement == WindowPlacement.Floating)) {
         setPositionImpl(position, platformDefaultPosition)
     }
 }
 
-private fun Window.setSizeImpl(size: DpSize) {
+fun Window.setPosition(
+    position: WindowPosition,
+    placement: WindowPlacement,
+) = setPositionSafely(position, placement) {
+    WindowLocationTracker.getCascadeLocationFor(this)
+}
+
+fun Window.setSizeImpl(size: DpSize) {
     val availableSize by lazy {
         val screenBounds = graphicsConfiguration.bounds
         val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(graphicsConfiguration)
@@ -262,20 +304,25 @@ private fun Window.setSizeImpl(size: DpSize) {
         preferredSize = Dimension(width, height)
         pack()
     }
-    setSize(
-        if (isWidthSpecified) width else computedPreferredSize!!.width,
-        if (isHeightSpecified) height else computedPreferredSize!!.height,
-    )
+    val finalWidth = if (isWidthSpecified) width else computedPreferredSize!!.width
+    val finalHeight = if (isHeightSpecified) height else computedPreferredSize!!.height
+    Log.i("Window $name size set to : $finalWidth x  $finalHeight.")
+    setSize(finalWidth, finalHeight)
     revalidate()
 }
 
-internal fun Window.setSizeSafely(size: DpSize, placement: WindowPlacement) {
+fun Window.setSizeSafely(size: DpSize, placement: WindowPlacement) {
     if (!isVisible || (placement == WindowPlacement.Floating)) {
         setSizeImpl(size)
     }
 }
 
-internal class ListenerOnWindowRef<T>(
+fun Window.setSize(
+    size: DpSize,
+    placement: WindowPlacement
+) = setSizeSafely(size, placement)
+
+class ListenerOnWindowRef<T>(
     private val register: Window.(T) -> Unit,
     private val unregister: Window.(T) -> Unit
 ) {
@@ -294,38 +341,45 @@ internal class ListenerOnWindowRef<T>(
     }
 }
 
-internal fun windowStateListenerRef() = ListenerOnWindowRef<WindowStateListener>(
+// todo move?
+fun windowStateListenerRef() = ListenerOnWindowRef<java.awt.event.WindowStateListener>(
     register = Window::addWindowStateListener,
     unregister = Window::removeWindowStateListener
 )
 
-internal fun windowListenerRef() = ListenerOnWindowRef<WindowListener>(
+// todo move?
+fun windowListenerRef() = ListenerOnWindowRef<WindowListener>(
     register = Window::addWindowListener,
     unregister = Window::removeWindowListener
 )
 
-internal fun componentListenerRef() = ListenerOnWindowRef<ComponentListener>(
+// todo move?
+fun componentListenerRef() = ListenerOnWindowRef<ComponentListener>(
     register = Component::addComponentListener,
     unregister = Component::removeComponentListener
 )
 
-internal val GraphicsConfiguration.density: Density
+// todo move?
+val GraphicsConfiguration.density: Density
     get() = Density(
         defaultTransform.scaleX.toFloat(),
         fontScale = 1f
     )
 
-internal val Locale.layoutDirection: LayoutDirection
+// todo move?
+val Locale.layoutDirection: LayoutDirection
     get() = ComponentOrientation.getOrientation(this).layoutDirection
 
-internal val ComponentOrientation.layoutDirection: LayoutDirection
+// todo move?
+val ComponentOrientation.layoutDirection: LayoutDirection
     get() = when {
         isLeftToRight -> LayoutDirection.Ltr
         isHorizontal -> LayoutDirection.Rtl
         else -> LayoutDirection.Ltr
     }
 
-internal fun layoutDirectionFor(component: Component): LayoutDirection {
+// todo move?
+fun layoutDirectionFor(component: Component): LayoutDirection {
     val orientation = component.componentOrientation
     return if (orientation != ComponentOrientation.UNKNOWN) {
         orientation.layoutDirection
@@ -334,8 +388,10 @@ internal fun layoutDirectionFor(component: Component): LayoutDirection {
     }
 }
 
+// todo move?
 val Component.density: Density get() = graphicsConfiguration.density
 
+// todo move?
 fun Window.setIcon(painter: Painter?) {
     setIconImage(
         painter?.toAwtImage(
@@ -346,6 +402,7 @@ fun Window.setIcon(painter: Painter?) {
     )
 }
 
+// todo move?
 fun Frame.setUndecoratedSafely(value: Boolean) {
     if (this.isUndecorated != value) {
         this.isUndecorated = value
@@ -394,12 +451,14 @@ fun Window(
     )
 }
 
+// todo move?
 val LayoutDirection.componentOrientation: ComponentOrientation
     get() = when (this) {
         LayoutDirection.Ltr -> ComponentOrientation.LEFT_TO_RIGHT
         LayoutDirection.Rtl -> ComponentOrientation.RIGHT_TO_LEFT
     }
 
+// todo move?
 class WindowEventsAdapter(
     val window: ComposeWindow,
     val currentOnCloseRequest: () -> Unit,

+ 15 - 0
src/main/kotlin/eu/mjdev/desktop/windows/WindowCloseAction.kt

@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.windows
+
+@Suppress("unused")
+enum class WindowCloseAction {
+    CLOSE,
+    MOVE
+}

+ 0 - 50
src/main/kotlin/eu/mjdev/desktop/windows/WindowFocusHelper.kt

@@ -1,50 +0,0 @@
-package eu.mjdev.desktop.windows
-
-import androidx.compose.ui.awt.ComposeWindow
-import java.awt.event.WindowEvent
-import java.awt.event.WindowEvent.*
-import java.awt.event.WindowFocusListener
-import java.awt.event.WindowStateListener
-
-@Suppress("MemberVisibilityCanBePrivate", "unused")
-class WindowFocusHelper(
-    val onFocusChange: WindowFocusHelper.(window: ComposeWindow?, focus: Boolean) -> Unit
-) : WindowFocusListener, WindowStateListener {
-
-    var window: ComposeWindow? = null
-
-    fun register(window: ComposeWindow) {
-        this.window = window
-        window.addWindowFocusListener(this)
-        window.addWindowStateListener(this)
-    }
-
-    fun unregister(window: ComposeWindow) {
-        window.removeWindowFocusListener(this)
-        window.removeWindowStateListener(this)
-    }
-
-    override fun windowGainedFocus(e: WindowEvent?) {
-        onFocusChange(window, true)
-    }
-
-    override fun windowLostFocus(e: WindowEvent?) {
-        onFocusChange(window, false)
-    }
-
-    override fun windowStateChanged(e: WindowEvent?) {
-        when (e?.newState) {
-            null -> Unit
-            WINDOW_CLOSED -> window?.let {
-                onFocusChange(window, false)
-                unregister(it)
-            }
-
-            WINDOW_GAINED_FOCUS -> onFocusChange(window, true)
-            WINDOW_ICONIFIED -> onFocusChange(window, false)
-            WINDOW_DEICONIFIED -> onFocusChange(window, true)
-            WINDOW_ACTIVATED -> onFocusChange(window, true)
-            WINDOW_DEACTIVATED -> onFocusChange(window, false)
-        }
-    }
-}

+ 46 - 0
src/main/kotlin/eu/mjdev/desktop/windows/WindowFocusListener.kt

@@ -0,0 +1,46 @@
+package eu.mjdev.desktop.windows
+
+import java.awt.Window
+import java.awt.event.WindowEvent
+import java.awt.event.WindowEvent.*
+import java.awt.event.WindowFocusListener
+import java.awt.event.WindowStateListener
+
+@Suppress("MemberVisibilityCanBePrivate", "unused")
+class WindowFocusListener(
+    val onFocusChange: (window: Window, focus: Boolean) -> Unit
+) : WindowFocusListener, WindowStateListener {
+
+    fun register(window: Window) {
+        window.addWindowFocusListener(this)
+        window.addWindowStateListener(this)
+    }
+
+    fun unregister(window: Window) {
+        window.removeWindowFocusListener(this)
+        window.removeWindowStateListener(this)
+    }
+
+    override fun windowGainedFocus(e: WindowEvent) {
+        onFocusChange(e.window, true)
+    }
+
+    override fun windowLostFocus(e: WindowEvent) {
+        onFocusChange(e.window, false)
+    }
+
+    override fun windowStateChanged(e: WindowEvent) {
+        when (e.newState) {
+            WINDOW_CLOSED -> {
+                onFocusChange(e.window, false)
+                unregister(e.window)
+            }
+
+            WINDOW_GAINED_FOCUS -> onFocusChange(e.window, true)
+            WINDOW_ICONIFIED -> onFocusChange(e.window, false)
+            WINDOW_DEICONIFIED -> onFocusChange(e.window, true)
+            WINDOW_ACTIVATED -> onFocusChange(e.window, true)
+            WINDOW_DEACTIVATED -> onFocusChange(e.window, false)
+        }
+    }
+}

+ 0 - 89
src/main/kotlin/eu/mjdev/desktop/windows/WindowStateHelper.kt

@@ -1,89 +0,0 @@
-package eu.mjdev.desktop.windows
-
-import androidx.compose.ui.awt.ComposeWindow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.DpOffset
-import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.WindowPosition
-import java.awt.event.WindowEvent
-import java.awt.event.WindowEvent.WINDOW_CLOSED
-import java.awt.event.WindowEvent.WINDOW_OPENED
-import java.awt.event.WindowStateListener
-
-@Suppress("MemberVisibilityCanBePrivate")
-class WindowStateHelper(
-    val onOpened: WindowStateHelper.(window: ComposeWindow?) -> Unit = {},
-    val onClosed: WindowStateHelper.(window: ComposeWindow?) -> Unit = {},
-) : WindowStateListener {
-
-    val x
-        get() = (window?.x ?: 0).dp
-
-    val y
-        get() = (window?.y ?: 0).dp
-
-    val width
-        get() = (window?.width ?: 0).dp
-
-    val height
-        get() = (window?.height ?: 0).dp
-
-    var window: ComposeWindow? = null
-
-    fun register(window: ComposeWindow) {
-        this.window = window
-        window.addWindowStateListener(this)
-        onOpened(window)
-    }
-
-    fun unregister(window: ComposeWindow) {
-        window.removeWindowStateListener(this)
-    }
-
-    fun setBounds(
-        x: Dp = this.x,
-        y: Dp = this.y,
-        width: Dp = this.width,
-        height: Dp = this.height
-    ) {
-        window?.setBounds(
-            x.value.toInt(),
-            y.value.toInt(),
-            width.value.toInt(),
-            height.value.toInt()
-        )
-    }
-
-    fun setOffset(offset: DpOffset) = setBounds(x = offset.x, y = offset.y)
-
-    fun setSize(size: DpSize) = setBounds(width = size.width, height = size.height)
-
-    override fun windowStateChanged(e: WindowEvent?) {
-        when (e?.newState) {
-            null -> Unit
-            WINDOW_OPENED -> {
-                onOpened(window)
-            }
-
-            WINDOW_CLOSED -> {
-                onClosed(window)
-                window?.let { unregister(it) }
-            }
-        }
-    }
-
-    fun setPosition(position: WindowPosition) {
-        when (position) {
-            is WindowPosition.Absolute -> setOffset(position.let { DpOffset(it.x, it.y) })
-            else -> {
-                // todo
-            }
-        }
-    }
-
-    fun requestFocus() {
-        window?.toFront()
-        window?.requestFocus()
-    }
-}

+ 38 - 0
src/main/kotlin/eu/mjdev/desktop/windows/WindowStateListener.kt

@@ -0,0 +1,38 @@
+package eu.mjdev.desktop.windows
+
+import java.awt.Window
+import java.awt.event.WindowEvent
+import java.awt.event.WindowEvent.WINDOW_CLOSED
+import java.awt.event.WindowEvent.WINDOW_OPENED
+import java.awt.event.WindowStateListener
+
+// todo more listeners and improved type
+@Suppress("MemberVisibilityCanBePrivate")
+class WindowStateListener(
+    val onOpened: eu.mjdev.desktop.windows.WindowStateListener.(window: Window) -> Unit = {},
+    val onClosed: eu.mjdev.desktop.windows.WindowStateListener.(window: Window) -> Unit = {},
+) : WindowStateListener {
+
+    fun register(window: Window) {
+        window.addWindowStateListener(this)
+        onOpened(window)
+    }
+
+    fun unregister(window: Window) {
+        window.removeWindowStateListener(this)
+    }
+
+    override fun windowStateChanged(e: WindowEvent?) {
+        when (e?.newState) {
+            null -> Unit
+            WINDOW_OPENED -> {
+                onOpened(e.window)
+            }
+
+            WINDOW_CLOSED -> {
+                onClosed(e.window)
+                e.window?.let { unregister(it) }
+            }
+        }
+    }
+}

+ 3 - 3
src/main/kotlin/eu/mjdev/desktop/windows/blur/base/HackedContentPane.kt

@@ -17,7 +17,7 @@ class HackedContentPane(
 
     fun repaintBlur() {
 //        if (window != null && window.width > 0 && window.height > 0) {
-//            println("creating blur image")
+//            Log.i("creating blur image")
 //            BufferedImage(
 //                window.width,
 //                window.height,
@@ -43,9 +43,9 @@ class HackedContentPane(
 //        }
 //
 //        override fun paintComponent(g: Graphics?) {
-//            println("paint called")
+//            Log.i("paint called")
 //            if (window != null && blurImage != null && width > 1 && height > 1) {
-//                println("painting blur")
+//                Log.i("painting blur")
 //                g?.color = Color.WHITE
 //                g?.fillRect(0,0,window!!.width, window!!.height)
 //                g?.drawImage(blurImage, 0, 0, window!!.width, window!!.height, null)