milan jurkulák преди 2 години
родител
ревизия
2de48e5d1f

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

@@ -49,14 +49,6 @@ class App(
 
     var process: Shell? = null
 
-    var subprocessId: Long? = null
-    val subProcess
-        get() = subprocessId?.let { spId ->
-            process?.allProcesses?.firstOrNull { p ->
-                p.pid() == spId
-            }
-        }
-
     var isStartingState = mutableStateOf(false)
     var isStarting: Boolean
         get() = isStartingState.value
@@ -78,9 +70,6 @@ class App(
     val command
         get() = process?.command
 
-    val pids: List<Long>
-        get() = process?.pids ?: emptyList()
-
     fun start(
         api: DesktopProvider,
         scope: CoroutineScope = api.scope
@@ -90,18 +79,12 @@ class App(
             triggerStart()
             process = Shell(api)
                 .writeCommand("export DBUS_SESSION_BUS_ADDRESS=\"unix:path=\$XDG_RUNTIME_DIR/bus\"")
-//                .writeCommand("gtk-launch", fileName, "&")
                 .writeCommand("$cmd  &")
-                .writeCommand( "echo \$\$")
                 .writeCommand( "exit")
                 .apply {
                     triggerStarted()
                     println("app started.")
-                    val subProcessString = readOutput().trim()
-                    println("shell out: $subProcessString")
-                    subprocessId = runCatching { subProcessString.toLong() }.getOrNull()
-                    println("subprocess id : $subprocessId")
-                    while (subProcess?.isAlive == true) {
+                    while (this.isRunning) {
                         delay(100)
                     }
                     triggerStop()

+ 124 - 49
src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt

@@ -1,60 +1,133 @@
+@file:Suppress("DEPRECATION")
+
 package eu.mjdev.desktop.data
 
 import eu.mjdev.desktop.extensions.Custom.ParsedBoolean
 import eu.mjdev.desktop.extensions.Custom.ParsedList
+import eu.mjdev.desktop.extensions.Custom.ParsedString
 import org.ini4j.Ini
+import org.ini4j.Profile
+import org.ini4j.Wini
 import java.io.File
+import java.io.InputStream
+import java.io.StringBufferInputStream
 
-@Suppress("ConstPropertyName", "unused")
+@Suppress("ConstPropertyName", "unused", "CanBeParameter", "MemberVisibilityCanBePrivate", "KotlinConstantConditions")
 class DesktopFile(
-    val file: File
+    val file: File,
+    private val content: Ini? = runCatching { if (file.exists()) Wini(file) else null }.getOrNull(),
+    private val props: Profile.Section? = content?.get(DesktopFileType.DesktopEntry.text)
 ) {
-    private val content
-        get() = runCatching { if (file.exists()) Ini(file) else null }.getOrNull()
-
-    private val props
-        get() = content?.get("Desktop Entry")
-
-    val fileName: String
-        get() = file.name
-
-    val type
-        get() = DesktopFileType(props?.get(Prop_Type))
-
-    val version
-        get() = props?.get(Prop_Version).orEmpty()
-
-    val name
-        get() = props?.get(Prop_Name).orEmpty()
-
-    val comment
-        get() = props?.get(Prop_Comment).orEmpty()
-
-    val path
-        get() = props?.get(Prop_Path).orEmpty()
-
-    val exec
-        get() = props?.get(Prop_Exec).orEmpty()
-
-    val icon
-        get() = props?.get(Prop_Icon).orEmpty()
-
-    val categories
-        get() = ParsedList(props?.get(Prop_Categories))
-
-    val notifyOnStart
-        get() = ParsedBoolean(props?.get(Prop_StartupNotify))
-
-    val runInTerminal
-        get() = ParsedBoolean(props?.get(Prop_Terminal))
+    var fileName: String = file.name
+    val absolutePath: String = file.absolutePath
+
+    val actions: MutableList<Action> = parseActions()
+
+    var type: DesktopFileType = DesktopFileType(props?.get(Prop_Type))
+    var version: String = ParsedString(props?.get(Prop_Version))
+    var name: String = ParsedString(props?.get(Prop_Name))
+    var comment: String = ParsedString(props?.get(Prop_Comment))
+    var path: String = ParsedString(props?.get(Prop_Path))
+    var exec: String = ParsedString(props?.get(Prop_Exec))
+    var icon: String = ParsedString(props?.get(Prop_Icon))
+    var categories: MutableList<String> = ParsedList(props?.get(Prop_Categories))
+    var notifyOnStart: Boolean = ParsedBoolean(props?.get(Prop_StartupNotify))
+    var runInTerminal: Boolean = ParsedBoolean(props?.get(Prop_Terminal))
+    var encoding: String = ParsedString(props?.get(Prop_Encoding))
+
+    // todo
+    private fun parseActions(): MutableList<Action> = mutableListOf()
+
+    fun action(
+        header: DesktopFileType,
+        block: Action.() -> Unit
+    ) = actions.add(Action("[${header.text}]").apply(block))
+
+    fun write() {
+        Wini().also { ini ->
+            ini.add(DesktopFileType.DesktopEntry.text).also { section ->
+                section.add(Prop_Type, type.text)
+                if (name.isNotEmpty()) section.add(Prop_Name, name)
+                if (comment.isNotEmpty()) section.add(Prop_Comment, comment)
+                if (version.isNotEmpty()) section.add(Prop_Version, version)
+                if (encoding.isNotEmpty()) section.add(Prop_Encoding, encoding)
+                if (type == DesktopFileType.Application) {
+                    if (path.isNotEmpty()) section.add(Prop_Path, path)
+                    if (exec.isNotEmpty()) section.add(Prop_Exec, exec)
+                    if (icon.isNotEmpty()) section.add(Prop_Icon, icon)
+                    if (categories.isNotEmpty()) section.add(Prop_Categories, categories.joinToString { "$it;" })
+                    if (notifyOnStart) section.add(Prop_StartupNotify, notifyOnStart)
+                    if (runInTerminal) section.add(Prop_Terminal, runInTerminal)
+                }
+                actions.forEach { action ->
+                    section.addChild(action.type.text).also { subsection ->
+                        if (type == DesktopFileType.Application) {
+                            subsection.add(Prop_Type, type.text)
+                            if (version.isNotEmpty()) subsection.add(Prop_Type, version)
+                            if (name.isNotEmpty()) subsection.add(Prop_Name, name)
+                            if (comment.isNotEmpty()) subsection.add(Prop_Comment, comment)
+                            if (path.isNotEmpty()) subsection.add(Prop_Path, path)
+                            if (exec.isNotEmpty()) subsection.add(Prop_Exec, exec)
+                            if (icon.isNotEmpty()) subsection.add(Prop_Icon, icon)
+                            if (categories.isNotEmpty()) subsection.add(
+                                Prop_Categories,
+                                categories.joinToString { "$it;" })
+                            if (notifyOnStart) subsection.add(Prop_StartupNotify, notifyOnStart)
+                            if (runInTerminal) subsection.add(Prop_Terminal, runInTerminal)
+                        }
+                        if (type == DesktopFileType.Theme) {
+                            if (action.gtkTheme.isNotEmpty()) subsection.add(Prop_GtkTheme, action.gtkTheme)
+                            if (action.metacityTheme.isNotEmpty()) subsection.add(
+                                Prop_MetacityTheme,
+                                action.metacityTheme
+                            )
+                            if (action.iconTheme.isNotEmpty()) subsection.add(Prop_IconTheme, action.iconTheme)
+                            if (action.cursorTheme.isNotEmpty()) subsection.add(Prop_CursorTheme, action.cursorTheme)
+                            if (action.buttonLayout.isNotEmpty()) subsection.add(Prop_ButtonLayout, action.buttonLayout)
+//                            subsection.add(Prop_X_Ubuntu_UseOverlayScrollbars, action.useOverlayScrollbars)
+                        }
+                        if (encoding.isNotEmpty()) section.add(Prop_Encoding, encoding)
+                    }
+                }
+            }
+            if (file.exists()) {
+                file.delete()
+            }
+            file.createNewFile()
+            ini.store(file)
+        }
+    }
 
-    val absolutePath: String
-        get() = file.absolutePath
+    class Action(
+        val text: String = "",
+        private val data: InputStream = StringBufferInputStream(text),
+        private val content: Ini? = runCatching { Ini(data) }.getOrNull(),
+        private val props: Profile.Section? = content?.get("Desktop Entry")
+    ) {
+        var type: DesktopFileType = DesktopFileType(props?.get(Prop_Type))
+        var version: String = ParsedString(props?.get(Prop_Version))
+        var name: String = ParsedString(props?.get(Prop_Name))
+        var comment: String = ParsedString(props?.get(Prop_Comment))
+        var path: String = ParsedString(props?.get(Prop_Path))
+        var exec: String = ParsedString(props?.get(Prop_Exec))
+        var icon: String = ParsedString(props?.get(Prop_Icon))
+        var categories: MutableList<String> = ParsedList(props?.get(Prop_Categories))
+        var notifyOnStart: Boolean = ParsedBoolean(props?.get(Prop_StartupNotify))
+        var runInTerminal: Boolean = ParsedBoolean(props?.get(Prop_Terminal))
+        var encoding: String = ParsedString(props?.get(Prop_Encoding))
+        var gtkTheme: String = ParsedString(props?.get(Prop_GtkTheme))
+        var metacityTheme: String = ParsedString(props?.get(Prop_MetacityTheme))
+        var iconTheme: String = ParsedString(props?.get(Prop_IconTheme))
+        var cursorTheme: String = ParsedString(props?.get(Prop_CursorTheme))
+        var buttonLayout: String = ParsedString(props?.get(Prop_ButtonLayout))
+//        var useOverlayScrollbars: Boolean = ParsedBoolean(props?.get(Prop_X_Ubuntu_UseOverlayScrollbars))
+    }
 
     enum class DesktopFileType(
-        val header: String
+        val text: String
     ) {
         Unknown(""),
+        DesktopEntry("Desktop Entry"),
         Application("Application"),
         Theme("X-GNOME-Metatheme");
 
@@ -62,7 +135,7 @@ class DesktopFile(
             operator fun invoke(
                 value: String?
             ): DesktopFileType = value.orEmpty().trim().let { v ->
-                entries.firstOrNull { e -> e.header.contentEquals(v, true) } ?: Unknown
+                entries.firstOrNull { e -> e.text.contentEquals(v, true) } ?: Unknown
             }
         }
     }
@@ -82,17 +155,19 @@ class DesktopFile(
         const val Prop_MimeType = "MimeType" // string delimited ;
         const val Prop_Actions = "Actions"
         const val Prop_DBusActivatable = "DBusActivatable"
+        const val Prop_Encoding = "Encoding"
 
         const val Prop_X_GNOME_UsesNotifications = "X-GNOME-UsesNotifications"
         const val Prop_X_Purism_FormFactor = "X-Purism-FormFactor"
         const val Prop_X_Unity_IconBackgroundColor = "X-Unity-IconBackgroundColor"
         const val Prop_X_Ubuntu_Gettext_Domain = "X-Ubuntu-Gettext-Domain"
 
-        private fun String.asBoolean(): Boolean = when (this.lowercase()) {
-            "true" -> true
-            "1" -> true
-            else -> false
-        }
+        const val Prop_GtkTheme = "GtkTheme"
+        const val Prop_MetacityTheme = "MetacityTheme"
+        const val Prop_IconTheme = "IconTheme"
+        const val Prop_CursorTheme = "CursorTheme"
+        const val Prop_ButtonLayout = "ButtonLayout"
+        const val Prop_X_Ubuntu_UseOverlayScrollbars = "X-Ubuntu-UseOverlayScrollbars"
 
         fun create(path: String): DesktopFile = create(File(path).apply {
             if (!exists()) createNewFile()

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

@@ -16,9 +16,29 @@ import java.text.DateFormat
 import java.util.*
 import kotlin.jvm.optionals.getOrNull
 
-@Suppress("FunctionName")
+@Suppress("FunctionName", "MemberVisibilityCanBePrivate", "unused")
 object Custom {
 
+    val Process.parentPid: Long?
+        get() = toHandle().parentPid
+
+    val Process.childs
+        get() = toHandle().childs
+
+    val ProcessHandle.parentPid: Long?
+        get() = parent().getOrNull()?.pid()
+
+    val ProcessHandle.childs: List<ProcessHandle>
+        get() = getProcessChilds()
+
+    fun ProcessHandle.getProcessChilds(): List<ProcessHandle> = ProcessHandle.allProcesses().filter { ph ->
+        ph.parentPid == pid()
+    }.toList().toMutableList().apply {
+        forEach { ph ->
+            addAll(ph.getProcessChilds())
+        }
+    }
+
     fun BufferedReader.readAvailable(): String = StringBuilder().apply {
         var ch = read()
         while (ch != -1) {
@@ -30,7 +50,7 @@ object Custom {
     fun ParsedList(
         value: String?,
         delimiter: String = ";"
-    ): List<String> = value?.split(delimiter)?.toList() ?: emptyList()
+    ): MutableList<String> = value?.split(delimiter)?.toMutableList() ?: mutableListOf()
 
     fun ParsedBoolean(
         value: String?,
@@ -43,6 +63,11 @@ object Custom {
         }
     }
 
+    fun ParsedString(
+        value: String?,
+        defaultValue: String = ""
+    ) = value ?: defaultValue
+
     fun NativePaint.setMaskFilter(
         blurRadius: Float
     ) {

+ 13 - 10
src/main/kotlin/eu/mjdev/desktop/helpers/internal/Palette.kt

@@ -8,6 +8,7 @@ import eu.mjdev.desktop.extensions.ColorUtils.darker
 import eu.mjdev.desktop.extensions.ColorUtils.isLightColor
 import eu.mjdev.desktop.extensions.ColorUtils.lighter
 import eu.mjdev.desktop.extensions.ColorUtils.nonAlphaValue
+import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 import eu.mjdev.desktop.extensions.Image.loadPicture
 import eu.mjdev.desktop.provider.DesktopProvider
 import kotlinx.coroutines.CoroutineScope
@@ -15,8 +16,9 @@ import kotlinx.coroutines.launch
 
 @Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
 class Palette(
-    val scope: CoroutineScope,
-    val baseColor: Color = Color.Transparent,
+    val api:DesktopProvider,
+    val scope: CoroutineScope = api.scope,
+    val baseColor: Color = Color.SuperDarkGray,
     val borderFactor: Float = 0.1f,
     val textFactor: Float = 0.6f,
 ) {
@@ -62,6 +64,7 @@ class Palette(
             backgroundColorState.value = background
             textColorState.value = text
         }
+//        api.gtkTheme.createFromPalette()
     }
 
     companion object {
@@ -76,15 +79,15 @@ class Palette(
             api: DesktopProvider,
         ) = rememberPalette(api) { api.currentUser.theme.backgroundColorState }
 
-        @Composable
-        fun rememberTextColor(
-            api: DesktopProvider,
-        ) = rememberPalette(api) { derivedStateOf { api.palette.textColor } }
+//        @Composable
+//        fun rememberTextColor(
+//            api: DesktopProvider,
+//        ) = rememberPalette(api) { derivedStateOf { api.palette.textColor } }
 
-        @Composable
-        fun rememberBorderColor(
-            api: DesktopProvider,
-        ) = rememberPalette(api) { derivedStateOf { api.palette.borderColor } }
+//        @Composable
+//        fun rememberBorderColor(
+//            api: DesktopProvider,
+//        ) = rememberPalette(api) { derivedStateOf { api.palette.borderColor } }
 
         @Composable
         fun rememberIconTintColor(

+ 13 - 12
src/main/kotlin/eu/mjdev/desktop/helpers/system/Shell.kt

@@ -1,7 +1,7 @@
 package eu.mjdev.desktop.helpers.system
 
+import eu.mjdev.desktop.extensions.Custom.childs
 import eu.mjdev.desktop.extensions.Custom.command
-import eu.mjdev.desktop.extensions.Custom.readAvailable
 import eu.mjdev.desktop.provider.DesktopProvider
 import java.io.BufferedReader
 import java.io.BufferedWriter
@@ -19,15 +19,21 @@ class Shell(
     val input: BufferedReader = process.inputReader()
     val output: BufferedWriter = process.outputWriter()
 
+//    val processInfo
+//        get() = process.info()
+
     val isAlive: Boolean
         get() = process.isAlive
 
+    val isRunning:Boolean
+        get() = false // todo : implement process is running monitor
+
+    val hasChilds
+        get() = childProcesses.isNotEmpty()
+
     val pid
         get() = process.pid()
 
-    val pids
-        get() = getProcessPids()
-
     val command: String
         get() = process.command ?: ""
 
@@ -38,12 +44,7 @@ class Shell(
         get() = allProcesses.map { it.pid() }
 
     val childProcesses: List<ProcessHandle>
-        get() = process.descendants().toList()
-
-    fun getProcessPids(): List<Long> = mutableListOf<Long>().apply {
-        add(process.pid())
-        addAll(process.descendants().map { it.pid() }.toList())
-    }.distinct()
+        get() = process.descendants().toList() + process.childs
 
     fun toHandle(): ProcessHandle =
         process.toHandle()
@@ -60,8 +61,8 @@ class Shell(
         return this
     }
 
-    fun readOutput(): String =
-        input.readAvailable()
+    fun readOutputLine(): String =
+        input.readLine()
 
     fun readOutputLines(): List<String> = input.readLines().filter {
         it.isNotEmpty()

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

@@ -62,7 +62,7 @@ class DesktopProvider(
         }
     }
     val gtkTheme: GtkTheme by lazy { GtkTheme(this) }
-    val palette: Palette by lazy { Palette(scope, currentUser.theme.backgroundColor) }
+    val palette: Palette by lazy { Palette(this) }
     val homeDir: File
         get() = currentUser.homeDir
     val allUsers: List<User>

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

@@ -11,9 +11,27 @@ class GtkTheme(
     private val themesDir: File = File(api.homeDir, ".themes"),
     private val themeDir: File = File(themesDir, themeName),
     private val themeDesktopFile: File = File(themeDir, "index.theme"),
-    private val themeCssFile: File = File(themeDir, "gtk.css")
+    private val themeCssFile: File = File(themeDir, "gtk.css"),
+    private val desktopFile: DesktopFile = DesktopFile(themeDesktopFile)
 ) {
-    val desktopFile: DesktopFile = DesktopFile.create(themeDesktopFile) // todo
+    fun createFromPalette() {
+        themeDir.mkdirs()
+        with(desktopFile) {
+            type = DesktopFile.DesktopFileType.Theme
+            name = "mjdev"
+            comment = "dynamic mjdev theme"
+            encoding = "UTF-8" // todo
+            action(DesktopFile.DesktopFileType.Theme) {
+                gtkTheme = "mjdev"
+                metacityTheme = "mjdev"
+                iconTheme = "mjdev"
+                cursorTheme = "DMZ-White" // todo
+                buttonLayout = "close,minimize,maximize:"
+//                useOverlayScrollbars = true
+            }
+            write()
+        }
+    }
 
     /*
     [Desktop Entry]