All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.os.OS.kt Maven / Gradle / Ivy

Go to download

Information about the system, Java runtime, OS, Window Manager, and Desktop Environment.

The newest version!
/*
 * Copyright 2023 dorkbox, llc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
@file:Suppress("unused", "SameParameterValue", "MemberVisibilityCanBePrivate", "LocalVariableName")

package dorkbox.os

import java.io.BufferedReader
import java.io.File
import java.io.FileReader
import java.util.*
import java.util.concurrent.*

object OS {
    /**
     * Gets the version number.
     */
    const val version = "1.11"

    init {
        // Add this project to the updates system, which verifies this class + UUID + version information
        dorkbox.updates.Updates.add(OS::class.java, "a2afbd7d98084a9eb6eb663570dbec77", version)
    }

    // make the default unix
    val LINE_SEPARATOR = getProperty("line.separator", "\n")

    const val LINE_SEPARATOR_UNIX = "\n"
    const val LINE_SEPARATOR_MACOS = "\r"
    const val LINE_SEPARATOR_WINDOWS = "\r\n"

    val TEMP_DIR = File(getProperty("java.io.tmpdir", "temp")).absoluteFile

    /**
     * The currently running MAJOR java version as a NUMBER. For example, "Java version 1.7u45", and converts it into 7, uses JEP 223 for java > 9
     */
    val javaVersion: Int by lazy {
        // We are >= java 10, use JEP 223 to get the version (early releases of 9 might not have JEP 223, so 10 is guaranteed to have it)
        var fullJavaVersion = getProperty("java.version", "9")
        if (fullJavaVersion.startsWith("1.")) {
            when (fullJavaVersion[2]) {
                '4' -> 4
                '5' -> 5
                '6' -> 6
                '7' -> 7
                '8' -> 8
                '9' -> 9
                else -> 8
            }
        } else {
            fullJavaVersion = getProperty("java.specification.version", "10")

            try {
                // it will ALWAYS be the major release version as an integer. See http://openjdk.java.net/jeps/223
                fullJavaVersion.toInt()
            } catch (ignored: Exception) {
                // the last valid guess we have, since the current Java implementation, whatever it is, decided not to cooperate with JEP 223.
                8
            }
        }
    }

    /**
     * Returns true if the currently running JVM is using the classpath or modules (JPMS)
     */
    val usesJpms = JVM.usesJpms

    /**
     * Returns the *ORIGINAL* system time zone, before (*IF*) it was changed to UTC
     */
    val originalTimeZone = TimeZone.getDefault().id!!

    /**
     * JVM reported osName, the default (if there is none detected) is 'linux'
     */
    val osName = getProperty("os.name", "linux").lowercase()

    /**
     * JVM reported osArch, the default (if there is none detected) is 'amd64'
     */
    val osArch = getProperty("os.arch", "amd64").lowercase()

    /**
     * @return the optimum number of threads for a given task. Makes certain not to take ALL the threads, always returns at least one
     * thread.
     */
    val optimumNumberOfThreads = (Runtime.getRuntime().availableProcessors() - 2).coerceAtLeast(1)

    /**
     * The determined OS type
     */
    val type: OSType by lazy {
        if (osName.startsWith("linux")) {
            // best way to determine if it's android.
            // Sometimes java binaries include Android classes on the classpath, even if it isn't actually Android, so we check the VM
            val isAndroid = "Dalvik" == getProperty("java.vm.name", "")
            if (isAndroid) {
                // android check from https://stackoverflow.com/questions/14859954/android-os-arch-output-for-arm-mips-x86
                when (osArch) {
                    "armeabi" -> {
                        OSType.AndroidArm56 // old/low-end non-hf 32bit cpu
                    }
                    "armeabi-v7a" -> {
                        OSType.AndroidArm7 // 32bit hf cpu
                    }
                    "arm64-v8a" -> {
                        OSType.AndroidArm8  // 64bit hf cpu
                    }
                    "x86" -> {
                        OSType.AndroidX86 // 32bit x86 (usually emulator)
                    }
                    "x86_64" -> {
                        OSType.AndroidX86_64 // 64bit x86 (usually emulator)
                    }
                    "mips" -> {
                        OSType.AndroidMips // 32bit mips
                    }
                    "mips64" -> {
                        OSType.AndroidMips64  // 64bit mips
                    }
                    else -> {
                        throw java.lang.RuntimeException("Unable to determine OS type for $osName $osArch")
                    }
                }
            } else {
                // http://mail.openjdk.java.net/pipermail/jigsaw-dev/2017-April/012107.html
                when(osArch) {
                    "i386", "x86" -> {
                        OSType.Linux32
                    }
                    "arm" -> {
                        OSType.LinuxArm32
                    }

                    "x86_64", "amd64" -> {
                        OSType.Linux64
                    }
                    "aarch64" -> {
                        OSType.LinuxArm64
                    }
                    else -> {
                        when {
                            // oddballs (android usually)
                            osArch.startsWith("arm64") -> {
                                OSType.LinuxArm64
                            }
                            osArch.startsWith("arm") -> {
                                if (osArch.contains("v8")) {
                                    OSType.LinuxArm64
                                } else {
                                    OSType.LinuxArm32
                                }
                            }
                            else -> {
                                throw java.lang.RuntimeException("Unable to determine OS type for $osName $osArch")
                            }
                        }
                    }
                }
            }
        } else if (osName.startsWith("windows")) {
            if ("amd64" == osArch) {
                OSType.Windows64
            } else {
                OSType.Windows32
            }
        } else if (osName.startsWith("mac") || osName.startsWith("darwin")) {
            when (osArch) {
                "x86_64" -> {
                    OSType.MacOsX64
                }
                "aarch64" -> {
                    OSType.MacOsArm
                }
                else -> {
                    OSType.MacOsX32  // new macOS is no longer 32 bit, but just in case.
                }
            }
        } else if (osName.startsWith("freebsd") ||
            osName.contains("nix") ||
            osName.contains("nux") ||
            osName.startsWith("aix")) {
            when (osArch) {
                "x86", "i386" -> {
                    OSType.Unix32
                }
                "arm" -> {
                    OSType.UnixArm
                }
                else -> {
                    OSType.Unix64
                }
            }
        } else if (osName.startsWith("solaris") ||
            osName.startsWith("sunos")) {
            OSType.Solaris
        } else {
            throw java.lang.RuntimeException("Unable to determine OS type for $osName $osArch")
        }
    }

    init {
        if (!TEMP_DIR.isDirectory) {
            // create the temp dir if necessary because the TEMP dir doesn't exist.
            TEMP_DIR.mkdirs()
        }

        /*
         * By default, the timer resolution on Windows ARE NOT high-resolution (16ms vs 1ms)
         *
         * 'Thread.sleep(1)' will not really sleep for 1ms, but will really sleep for ~16ms. This long-running sleep will trick Windows
         *  into using higher resolution timers.
         *
         * See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6435126
         */
        if (type.isWindows) {
            // only necessary on windows
            val timerAccuracyThread = Thread(
            {
                 while (true) {
                     try {
                         Thread.sleep(Long.MAX_VALUE)
                     } catch (ignored: Exception) {
                     }
                 }
            }, "FixWindowsHighResTimer")
            timerAccuracyThread.isDaemon = true
            timerAccuracyThread.start()
        }
    }

    /**
     * Clears/removes the property from the system properties.
     */
    fun clearProperty(property: String) {
        System.clearProperty(property)
    }

    /**
     * @return the previous value of the system property, or 'null' if it did not have one.
     */
    fun setProperty(property: String, value: String): String? {
        return System.setProperty(property, value)
    }

    /**
     * @return the previous value of the system property, or the [defaultValue] if it did not have one.
     */
    fun setProperty(property: String, value: String, defaultValue: String): String {
        return System.setProperty(property, value) ?: defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, or null if it does not exist.
     */
    fun getProperty(property: String): String? {
        return System.getProperty(property, null)
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getProperty(property: String, defaultValue: String): String {
        return System.getProperty(property, defaultValue)
    }

    /**
     * @return the Java system properties in a safe way.
     */
    fun getProperties(): Map {
        @Suppress("UNCHECKED_CAST")
        return System.getProperties().toMap() as Map
    }

    /**
     * @return the System Environment property in a safe way for a given property, or null if it does not exist.
     */
    fun getEnv(): Map {
        return System.getenv()
    }

    /**
     * @return the System Environment property in a safe way for a given property, or null if it does not exist.
     */
    fun getEnv(property: String): String? {
        return System.getenv(property)
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getEnv(property: String, defaultValue: String): String {
        return getEnv(property, defaultValue)
    }


    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getBoolean(property: String, defaultValue: Boolean): Boolean {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim().lowercase(Locale.getDefault())
        if (value.isEmpty()) {
            return defaultValue
        }

        if ("false" == value || "no" == value || "0" == value) {
            return false
        }

        return if ("true" == value || "yes" == value || "1" == value) {
            true
        } else defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getInt(property: String, defaultValue: Int): Int {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim()

        try {
            return value.toInt()
        } catch (ignored: Exception) {
        }
        return defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getLong(property: String, defaultValue: Long): Long {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim()

        try {
            return value.toLong()
        } catch (ignored: Exception) {
        }
        return defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getFloat(property: String, defaultValue: Float): Float {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim()

        try {
            return value.toFloat()
        } catch (ignored: Exception) {
        }
        return defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getDouble(property: String, defaultValue: Double): Double {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim()

        try {
            return value.toDouble()
        } catch (ignored: Exception) {
        }
        return defaultValue
    }



    val is32bit = type.is32bit
    val is64bit = type.is64bit

    /**
     * @return true if this is x86/x64/arm architecture (intel/amd/etc) processor.
     */
    val isX86 = type.isX86
    val isMips = type.isMips
    val isArm = type.isArm


    val isLinux = type.isLinux
    val isUnix = type.isUnix
    val isSolaris = type.isSolaris
    val isWindows = type.isWindows
    val isMacOsX = type.isMacOsX
    val isAndroid = type.isAndroid

    /**
     * Set our system to UTC time zone. Retrieve the **original** time zone via [.getOriginalTimeZone]
     */
    fun setUTC() {
        // have to set our default timezone to UTC. EVERYTHING will be UTC, and if we want local, we must explicitly ask for it.
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
    }

    /**
     * @return the first line of the exception message from 'throwable', or the type if there was no message.
     */
    fun getExceptionMessage(throwable: Throwable): String? {
        var message = throwable.message
        if (message != null) {
            val index = message.indexOf(LINE_SEPARATOR)
            if (index > -1) {
                message = message.substring(0, index)
            }
        } else {
            message = throwable.javaClass.simpleName
        }
        return message
    }

    /**
     * Executes the given command and returns its output.
     *
     * This is based on an aggregate of the answers provided here: [https://stackoverflow.com/questions/35421699/how-to-invoke-external-command-from-within-kotlin-code]
     */
    private fun execute(vararg args: String, timeout: Long = 60): String {
        val process = ProcessBuilder(args.toList())
            .redirectOutput(ProcessBuilder.Redirect.PIPE)
            .redirectError(ProcessBuilder.Redirect.PIPE)
            .start()
        val text = process.inputStream.bufferedReader().readText().trim()
        process.waitFor(timeout, TimeUnit.SECONDS)
        return text
    }

    // true if the exit code is 0 (meaning standard exit)
    private fun executeStatus(vararg args: String, timeout: Long = 60): Boolean {
        return ProcessBuilder(args.toList())
            .redirectOutput(ProcessBuilder.Redirect.PIPE)
            .redirectError(ProcessBuilder.Redirect.PIPE)
            .start()
            .apply { waitFor(timeout, TimeUnit.SECONDS) }
            .exitValue() == 0
    }

    object Windows {
        /**
         * Version info at release.
         ```
          https://en.wikipedia.org/wiki/Comparison_of_Microsoft_Windows_versions

          Windows XP                    5.1.2600  (2001-10-25)
          Windows Server 2003           5.2.3790  (2003-04-24)

          Windows Home Server           5.2.3790  (2007-06-16)

          -------------------------------------------------

          Windows Vista                 6.0.6000  (2006-11-08)
          Windows Server 2008 SP1       6.0.6001  (2008-02-27)
          Windows Server 2008 SP2	    6.0.6002  (2009-04-28)

          -------------------------------------------------

          Windows 7                     6.1.7600  (2009-10-22)
          Windows Server 2008 R2        6.1.7600  (2009-10-22)
          Windows Server 2008 R2 SP1    6.1.7601  (?)

          Windows Home Server 2011      6.1.8400  (2011-04-05)

          -------------------------------------------------

          Windows 8                     6.2.9200  (2012-10-26)
          Windows Server 2012	        6.2.9200  (2012-09-04)

          -------------------------------------------------

          Windows 8.1                   6.3.9600  (2013-10-18)
          Windows Server 2012 R2        6.3.9600  (2013-10-18)

          -------------------------------------------------

          Windows 10	                10.0.10240  (2015-07-29)
          Windows 10	                10.0.10586  (2015-11-12)
          Windows 10	                10.0.14393  (2016-07-18)

          Windows Server 2016           10.0.14393  (2016-10-12)
          Windows Server 2019 	      	10.0.17763  (2018-10-02)
          Windows Server 2022 	        10.0.20348  (2021-08-18)

          Windows 11 Original Release 	10.0.22000  (2021-10-05)
          Windows 11 2022 Update        10.0.22621  (2022-09-20)
        ```
         * @return the {major}{minor} version of windows, ie: Windows Version 10.0.10586 -> {10}{0}
         */
        val version: IntArray by lazy {
            if (!isWindows) {
                intArrayOf(0, 0, 0)
            } else {
                val version = IntArray(3)

                try {
                    val output = execute("cmd.exe", "/c", "ver")
                    if (output.isNotEmpty()) {
                        // OF NOTE: It is possible to have a different encoding of windows, where the word "Version" doesn't exist.
                        //  in this case, we'll just take the next set of numbers available
                        // Microsoft Windows [Version 10.0.22000.2600]
                        // Microsoft Windows [??? 10.0.22621.2283]  (different encoding)

                        // slice out the [] because we don't want to include windows product names! (like Windows 2012) in the name!
                        // I don't specifically have this version to test, however it is easy enough to guard against.
                        val shortenedOutput = output.substring(output.indexOf("[") + 1, output.indexOf("]"))
                        val index = shortenedOutput.indexOfFirst { it.isDigit() }
                        val versionInfoOnly = shortenedOutput.substring(index, shortenedOutput.length)

                        val split = versionInfoOnly.split(".").toTypedArray()
                        if (split.size == 4) {
                            version[0] = split[0].toInt()
                            version[1] = split[1].toInt()
                            version[2] = split[2].toInt()
                        }
                    }
                } catch (ignored: Throwable) {
                }
                version
            }
        }

        /**
         * @return is Windows XP or equivalent
         */
        val isWindowsXP = version[0] == 5

        /**
         * @return is Windows Vista or equivalent
         */
        val isWindowsVista = version[0] == 6 && version[1] == 0

        /**
         * @return is Windows 7 or equivalent
         */
        val isWindows7 = version[0] == 6 && version[1] == 1

        /**
         * @return is Windows 8 or equivalent
         */
        val isWindows8 = version[0] == 6 && version[1] == 2

        /**
         * @return is Windows 8.1 or equivalent
         */
        val isWindows8_1 = version[0] == 6 && version[1] == 3

        /**
         * @return is greater than or equal to Windows 8.1 or equivalent
         */
        val isWindows8_1_plus: Boolean by lazy {
            val version = version
            if (version[0] == 6 && version[1] >= 3) {
                true
            } else {
                version[0] > 6
            }
        }

        /**
         * @return is Windows 10 or equivalent
         */
        val isWindows10 = version[0] == 10

        /**
         * @return is Windows 10 or greater
         */
        val isWindows10_plus = version[0] >= 10

        /**
         * @return is Windows 11 (original release was 21H2)
         */
        val isWindows11 = version[0] == 10 && version[1] == 0 && version[2] >= 22000

        /**
         * @return is Windows 11 update 22H2
         */
        val isWindows11_22H2 = version[0] == 10 && version[1] == 0 && version[2] >= 22621
    }

    object Unix {
        // uname
        val isFreeBSD: Boolean by lazy {
            if (!isUnix) {
                false
            } else {
                try {
                    // uname
                    execute("uname").startsWith("FreeBSD")
                } catch (ignored: Throwable) {
                    false
                }
            }
        }
    }

    object Linux {
        // NAME="Arch Linux"
        // PRETTY_NAME="Arch Linux"
        // ID=arch
        // ID_LIKE=archlinux
        // ANSI_COLOR="0;36"
        // HOME_URL="https://www.archlinux.org/"
        // SUPPORT_URL="https://bbs.archlinux.org/"
        // BUG_REPORT_URL="https://bugs.archlinux.org/"

        // similar on other distro's.  ID is always the "key" to the distro
        // this is likely a file we are interested in.// looking for files like /etc/os-release

        /**
         * @return os release info or ""
         */
        val info: String by lazy {
            if (!isLinux) {
                ""
            } else {
                var data = ""
                try {
                    val releaseFiles: MutableList = LinkedList()
                    var totalLength = 0

                    // looking for files like /etc/os-release
                    val file = File("/etc")
                    if (file.isDirectory) {
                        val list = file.listFiles()
                        if (list != null) {
                            for (f in list) {
                                if (f.isFile && f.name.contains("release")) {
                                    // this is likely a file we are interested in.
                                    releaseFiles.add(f)
                                    totalLength += file.length().toInt()
                                }
                            }
                        }
                    }

                    if (totalLength > 0) {
                        val fileContents = StringBuilder(totalLength)
                        for (releaseFile in releaseFiles) {
                            BufferedReader(FileReader(releaseFile)).use { reader ->
                                var currentLine: String?

                                // NAME="Arch Linux"
                                // PRETTY_NAME="Arch Linux"
                                // ID=arch
                                // ID_LIKE=archlinux
                                // ANSI_COLOR="0;36"
                                // HOME_URL="https://www.archlinux.org/"
                                // SUPPORT_URL="https://bbs.archlinux.org/"
                                // BUG_REPORT_URL="https://bugs.archlinux.org/"

                                // similar on other distro's.  ID is always the "key" to the distro
                                while (reader.readLine().also { currentLine = it } != null) {
                                    fileContents.append(currentLine).append(LINE_SEPARATOR_UNIX)
                                }
                            }
                        }
                        data = fileContents.toString()
                    }
                } catch (ignored: Throwable) {
                }

                data
            }
        }


        /**
         * @param id the info ID to check, ie: ubuntu, arch, debian, etc... This is what the OS vendor uses to ID their OS.
         *
         * @return true if this OS is identified as the specified ID.
         */
        fun isReleaseType(id: String): Boolean {
            // also matches on 'DISTRIB_ID' and 'VERSION_ID'
            // ID=linuxmint/fedora/arch/ubuntu/etc
            return info.contains("ID=$id\n")
        }

        val isArch: Boolean by lazy {
            isReleaseType("arch")
        }

        val isDebian: Boolean by lazy {
            isReleaseType("debian")
        }

        val isElementaryOS: Boolean by lazy {
            try {
                // ID="elementary"  (notice the extra quotes)
                info.contains("ID=\"elementary\"\n") || info.contains("ID=elementary\n") ||

                        // this is specific to eOS < 0.3.2
                        info.contains("ID=\"elementary OS\"\n")
            } catch (ignored: Throwable) {
                false
            }
        }

        val isFedora: Boolean by lazy {
            isReleaseType("fedora")
        }

        val fedoraVersion: Int by lazy {
            if (!isFedora) {
                0
            } else {
                try {
                    // ID=fedora
                    if (info.contains("ID=fedora\n")) {
                        // should be: VERSION_ID=23\n  or something
                        val beginIndex = info.indexOf("VERSION_ID=") + 11
                        val fedoraVersion_ = info.substring(beginIndex, info.indexOf(LINE_SEPARATOR_UNIX, beginIndex))
                        fedoraVersion_.toInt()
                    } else {
                        0
                    }
                } catch (ignored: Throwable) {
                    0
                }
            }
        }

        val isLinuxMint: Boolean by lazy {
            isReleaseType("linuxmint")
        }

        val isUbuntu: Boolean by lazy {
            isReleaseType("ubuntu")
        }

        val ubuntuVersion: IntArray by lazy {
            @Suppress("DuplicatedCode")
            if (!isUbuntu) {
                intArrayOf(0, 0)
            } else if (distribReleaseInfo != null) {
                val split = distribReleaseInfo!!.split(".").toTypedArray()
                intArrayOf(split[0].toInt(), split[1].toInt())
            } else {
                intArrayOf(0, 0)
            }
        }

        val elementaryOSVersion: IntArray by lazy {
            // 0.1 Jupiter. The first stable version of elementary OS was Jupiter, published on 31 March 2011 and based on Ubuntu 10.10. ...
            // 0.2 Luna. elementary OS 0.2 "Luna" ...
            // 0.3 Freya. elementary OS 0.3 "Freya" ...
            // 0.4 Loki. elementary OS 0.4, known by its codename, "Loki", was released on 9 September 2016. ...
            // 5.0 Juno

            @Suppress("DuplicatedCode")
            if (!isElementaryOS) {
                intArrayOf(0, 0)
            } else if (distribReleaseInfo != null) {
                val split = distribReleaseInfo!!.split(".").toTypedArray()
                intArrayOf(split[0].toInt(), split[1].toInt())
            } else {
                intArrayOf(0, 0)
            }
        }

        val isKali: Boolean by lazy {
            isReleaseType("kali")
        }

        val isPop: Boolean by lazy {
            isReleaseType("pop")
        }

        val isIgel: Boolean by lazy {
            isReleaseType("IGEL")
        }

        /**
         * @return the `DISTRIB_RELEASE` info as a String, if possible. Otherwise NULL
         */
        val distribReleaseInfo: String? by lazy {
            val releaseString = "DISTRIB_RELEASE="
            var index = info.indexOf(releaseString)
            var data: String? = null

            try {
                if (index > -1) {
                    index += releaseString.length
                    val newLine = info.indexOf(LINE_SEPARATOR_UNIX, index)
                    if (newLine > index) {
                        data = info.substring(index, newLine)
                    }
                }
            } catch (ignored: Throwable) {
            }

            data
        }


        val isWSL: Boolean by lazy {
            try {
                // looking for /proc/version
                val file = File("/proc/version")
                var data: Boolean? = null
                if (file.canRead()) {
                    try {
                        val msString: Boolean
                        BufferedReader(FileReader(file)).use { reader ->
                            // Linux version 4.4.0-19041-Microsoft ([email protected]) (gcc version 5.4.0 (GCC) ) #488-Microsoft Mon Sep 01 13:43:00 PST 2020
                            msString = reader.readLine().contains("-Microsoft")
                        }

                        data = msString
                    } catch (ignored: Throwable) {
                    }
                }

                if (data == null) {
                    // reading the file didn't work for whatever reason...
                    // uname -v
                    data = execute("/usr/bin/uname", "-v").contains("-Microsoft")
                }

                if (data == true) {
                    data
                } else {
                    false
                }
            } catch (ignored: Throwable) {
                false
            }
        }

        val isRoot: Boolean by lazy {
            // this means we are running as sudo
            var isSudoOrRoot = System.getenv("SUDO_USER") != null

            if (!isSudoOrRoot) {
                // running as root (also can be "sudo" user). A lot slower that checking a sys env, but this is guaranteed to work
                try {
                    // id -u
                    isSudoOrRoot = "0" == execute("/usr/bin/id", "-u")
                } catch (ignored: Throwable) {
                }
            }
            isSudoOrRoot
        }

        object PackageManager {
            enum class Type(val installString: String) {
                APT("apt install"),
                APTGET("apt-get install"),
                YUM("yum install"),
                PACMAN("pacman -S ");
            }

            val type: Type by lazy {
                if (File("/usr/bin/apt").canExecute()) {
                    Type.APT
                } else if (File("/usr/bin/apt-get").canExecute()) {
                    Type.APTGET
                } else if (File("/usr/bin/yum").canExecute()) {
                    Type.YUM
                } else if (File("/usr/bin/pacman").canExecute()) {
                    Type.PACMAN
                } else {
                    Type.APTGET
                }

                // default is apt-get, even if it isn't correct
            }

            /**
             * @return true if the package is installed
             */
            fun isPackageInstalled(packageName: String): Boolean {
                // dpkg
                // dpkg -L libappindicator3
                // dpkg-query: package 'libappindicator3' is not installed
                val is_dpkg = File("/usr/bin/dpkg").canExecute()
                if (is_dpkg) {
                    return !execute("/usr/bin/dpkg", "-L", packageName).contains("is not installed")
                }

                // rpm
                // rpm -q libappindicator234
                // package libappindicator234 is not installed
                val is_rpm = File("/usr/bin/rpm").canExecute()
                if (is_rpm) {
                    return !execute("/usr/bin/rpm", "-q", packageName).contains("is not installed")
                }


                // pacman
                // pacman -Qi 
                val is_pacmac = File("/usr/bin/pacman").canExecute()
                if (is_pacmac) {
                    try {
                        // use the exit code to determine if the packages exists on the system
                        // 0 the package exists, 1 it doesn't
                        return executeStatus("/usr/bin/pacman", "-Qi", packageName)

                        //return start == 0
                    } catch (ignored: Exception) {
                    }
                }

                return false
            }
        }
    }

    object DesktopEnv {
        enum class Env {
            Gnome, KDE, Unity, Unity7, XFCE, LXDE, MATE, Pantheon, ChromeOS, Unknown
        }

        enum class EnvType {
            X11, WAYLAND, Unknown
        }

        private fun isValidCommand(partialExpectationInOutput: String, commandOutput: String): Boolean {
            return (commandOutput.contains(partialExpectationInOutput) &&
                    !commandOutput.contains("not installed") &&
                    !commandOutput.contains("No such file or directory"))
        }

        // have no idea how this can happen....
        val type: EnvType by lazy {
            when (getEnv("XDG_SESSION_TYPE")) {
                "x11" -> {
                    EnvType.X11
                }
                "wayland" -> {
                    EnvType.WAYLAND
                }
                else -> {
                    EnvType.Unknown
                }
            }
        }

        val isX11 = type == EnvType.X11
        val isWayland = type == EnvType.WAYLAND


        val isMATE: Boolean by lazy {
            if (!isLinux && !isUnix) {
                false
            } else {
                try {
                    File("/usr/bin/mate-about").exists()
                } catch (ignored: Throwable) {
                    false
                }
            }
        }

        val isGnome: Boolean by lazy {
            if (!isLinux && !isUnix) {
                false
            } else {
                try {
                    // note: some versions of linux can ONLY access "ps a"; FreeBSD and most linux is "ps x"
                    // we try "x" first

                    // ps x | grep gnome-shell
                    var contains = execute("/usr/bin/ps", "x").contains("gnome-shell")
                    if (!contains && isLinux) {
                        // only try again if we are linux

                        // ps a | grep gnome-shell
                        contains = execute("/usr/bin/ps", "a").contains("gnome-shell")
                    }
                    contains
                } catch (ignored: Throwable) {
                    false
                }
            }
        }

        /**
         * @return a string representing the current gnome-shell version, or NULL if it could not be found
         */
        val gnomeVersion: String? by lazy {
            if (!isLinux && !isUnix) {
                null
            } else {
                try {
                    // gnome-shell --version
                    val versionString = execute("/usr/bin/gnome-shell", "--version")
                    if (versionString.isNotEmpty()) {
                        // GNOME Shell 3.14.1
                        val version = versionString.replace("[^\\d.]".toRegex(), "")
                        if (version.isNotEmpty() && version.indexOf('.') > 0) {
                            // should just be 3.14.1 or 3.20 or similar
                            version
                        } else {
                            null
                        }
                    } else {
                        null
                    }
                } catch (ignored: Throwable) {
                    null
                }
            }
        }

        // Check if plasmashell is running, if it is -- then we are most likely KDE
        val isKDE: Boolean by lazy {
            val XDG = getEnv("XDG_CURRENT_DESKTOP")
            if (XDG == null) {
                // Check if plasmashell is running, if it is -- then we are most likely KDE
                plasmaVersionFull != null && !plasmaVersionFull!!.startsWith("0")
            } else {
                "kde".equals(XDG, ignoreCase = true)
            }
        }

        /**
         * The full version number of plasma shell (if running) as a String.
         *
         * @return cannot represent '5.6.5' as a number, so we return a String instead or NULL if unknown
         */
        val plasmaVersionFull: String? by lazy {
            if (!isLinux && !isUnix) {
                null
            } else {
                try {
                    // plasma-desktop -v
                    // plasmashell --version
                    val output = execute("/usr/bin/plasmashell", "--version")
                    if (output.isNotEmpty()) {
                        // DEFAULT icon size is 16. KDE is bananas on what they did with tray icon scale
                        // should be: plasmashell 5.6.5   or something
                        val s = "plasmashell "
                        if (isValidCommand(s, output)) {
                            output.substring(output.indexOf(s) + s.length)
                        } else {
                            null
                        }
                    } else {
                        null
                    }
                } catch (ignored: Throwable) {
                    null
                }
            }
        }

        val isXfce: Boolean by lazy {
            if (!isLinux && !isUnix) {
                false
            } else {
                try {
                    // note: some versions of linux can ONLY access "ps a"; FreeBSD and most linux is "ps x"
                    // we try "x" first

                    // ps x | grep xfce
                    var contains = execute("/usr/bin/ps", "x").contains("xfce")
                    if (!contains && isLinux) {
                        // only try again if we are linux

                        // ps a | grep gnome-shell
                        contains = execute("/usr/bin/ps", "a").contains("xfce")
                    }
                    contains
                } catch (ignored: Throwable) {
                    false
                }
            }
        }

        /**
         * There are sometimes problems with nautilus (the file browser) and some GTK methods. It is ridiculous for me to have to
         * work around their bugs like this.
         *
         * see: https://askubuntu.com/questions/788182/nautilus-not-opening-up-showing-glib-error
         */
        val isNautilus: Boolean by lazy {
            if (!isLinux && !isUnix) {
                false
            } else {
                try {
                    // nautilus --version
                    val output = execute("/usr/bin/nautilus", "--version")
                    if (output.isNotEmpty()) {
                        // should be: GNOME nautilus 3.14.3   or something
                        val s = "GNOME nautilus "
                        isValidCommand(s, output)
                    } else {
                        false
                    }
                } catch (ignored: Throwable) {
                    false
                }
            }
        }

        val isChromeOS: Boolean by lazy {
            if (!isLinux) {
                false
            } else {
                try {
                    // ps aux | grep chromeos
                    execute("/usr/bin/ps", "aux").contains("chromeos")
                } catch (ignored: Throwable) {
                    false
                }
            }
        }

        /**
         * @param channel which XFCE channel to query. Cannot be null
         * @param property which property (in the channel) to query. Null will list all properties in the channel
         *
         * @return the property value or "".
         */
        fun queryXfce(channel: String, property: String?): String {
            if (!isLinux && !isUnix) {
                return ""
            }
            try {
                // xfconf-query -c xfce4-panel -l
                val commands: MutableList = ArrayList()
                commands.add("/usr/bin/xfconf-query")
                commands.add("-c")
                commands.add(channel)
                if (property != null) {
                    // get property for channel
                    commands.add("-p")
                    commands.add(property)
                } else {
                    // list all properties for the channel
                    commands.add("-l")
                }

                return execute(*commands.toTypedArray())
            } catch (ignored: Throwable) {
                return ""
            }
        }

        val env: Env by lazy {
            // if we are running as ROOT, we *** WILL NOT *** have access to  'XDG_CURRENT_DESKTOP'
            //   *unless env's are preserved, but they are not guaranteed to be
            // see:  http://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
            var XDG = getEnv("XDG_CURRENT_DESKTOP")
            if (XDG == null) {
                // maybe we are running as root???
                XDG = "unknown" // try to autodetect if we should use app indicator or gtkstatusicon
            }

            // Ubuntu 17.10+ is special ... this is ubuntu:GNOME (it now uses wayland instead of x11, so many things have changed...)
            // So it's gnome, and gnome-shell, but with some caveats
            // see: https://bugs.launchpad.net/ubuntu/+source/gnome-shell/+bug/1700465

            // BLEH. if gnome-shell is running, IT'S REALLY GNOME!
            // we must ALWAYS do this check!!
            if (isGnome) {
                XDG = "gnome"
            } else if (isKDE) {
                // same thing with plasmashell!
                XDG = "kde"
            } else if (isXfce) {
                // https://github.com/dorkbox/SystemTray/issues/100
                // IGEL linux doesn't say what it is... but we know it's XFCE ... EVEN THOUGH it reports X11!!
                XDG = "xfce"
            }

            if ("unity".equals(XDG, ignoreCase = true)) {
                // Ubuntu Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell".
                Env.Unity
            } else if ("unity:unity7".equals(XDG, ignoreCase = true)) {
                // Ubuntu Unity7 is a weird combination. It's "Gnome", but it's not "Gnome Shell".
                Env.Unity7
            } else if ("xfce".equals(XDG, ignoreCase = true)) {
                Env.XFCE
            } else if ("lxde".equals(XDG, ignoreCase = true)) {
                Env.LXDE
            } else if ("kde".equals(XDG, ignoreCase = true)) {
                Env.KDE
            } else if ("pantheon".equals(XDG, ignoreCase = true)) {
                Env.Pantheon
            } else if ("gnome".equals(XDG, ignoreCase = true)) {
                Env.Gnome
            } else if (isChromeOS) {
                // maybe it's chromeOS?
                Env.ChromeOS
            } else if (isMATE) {
                Env.MATE
            } else {
                Env.Unknown
            }
        }

        val isUnity = isUnity(env)

        fun isUnity(env: Env): Boolean {
            return env == Env.Unity || env == Env.Unity7
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy