DesktopFile.kt 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. package eu.mjdev.desktop.data
  2. import eu.mjdev.desktop.extensions.Custom.ParsedBoolean
  3. import eu.mjdev.desktop.extensions.Custom.ParsedList
  4. import eu.mjdev.desktop.extensions.Custom.ParsedString
  5. import eu.mjdev.desktop.helpers.system.LinuxIni
  6. import org.ini4j.Ini
  7. import org.ini4j.Profile.Section
  8. import org.ini4j.Wini
  9. import java.io.File
  10. @Suppress("unused", "MemberVisibilityCanBePrivate", "ConstPropertyName", "PropertyName")
  11. class DesktopFile(
  12. var file: File,
  13. val correctDir: String = "/tmp/.corrected-desktop-files/",
  14. val correctedFile: File = File(File(correctDir), file.name),
  15. var fileData: String = runCatching {
  16. (if (correctedFile.exists()) correctedFile else file).readText()
  17. }.getOrNull().orEmpty(),
  18. var content: Ini = (runCatching { LinuxIni(fileData) }.getOrNull() ?: LinuxIni())
  19. ) {
  20. var fileName: String = if (correctedFile.exists()) correctedFile.name else file.name
  21. val absolutePath: String = if (correctedFile.exists()) correctedFile.absolutePath else file.absolutePath
  22. val fullAppName
  23. get() = if (correctedFile.exists()) correctedFile.nameWithoutExtension else file.nameWithoutExtension
  24. val sections: Map<String, Section>
  25. get() = content.map {
  26. if (it is Section) it else null
  27. }.filterNotNull().associateBy {
  28. it.value.toString()
  29. }
  30. val desktopSection
  31. get() = content[DesktopFileType.DesktopEntry.text]?.let { DesktopSectionScope(it) }
  32. val themeSection
  33. get() = content[DesktopFileType.Theme.text]?.let { ThemeSectionScope(it) }
  34. val isApp
  35. get() = desktopSection?.Type == DesktopFileType.Application
  36. val isCorrect
  37. get() = correctedFile.exists()
  38. init {
  39. if (isApp && !isCorrect) {
  40. runCatching {
  41. println("Correcting file: file:///$absolutePath")
  42. println("Corrected file: file:///${correctedFile.absolutePath}")
  43. correctedFile.let { f ->
  44. desktopSection?.StartupWMClass = fullAppName
  45. desktopSection?.OnlyShowIn = mutableListOf()
  46. writeTo(f)
  47. file = f
  48. fileData = file.readText()
  49. content = Wini(f)
  50. }
  51. }.onFailure { e ->
  52. e.printStackTrace()
  53. }
  54. }
  55. }
  56. fun section(
  57. type: DesktopFileType,
  58. block: Section.() -> Unit
  59. ) = (content[type.text] ?: content.add(type.text))?.apply(block)
  60. fun desktopSection(
  61. block: DesktopSectionScope.() -> Unit
  62. ) = section(DesktopFile.DesktopFileType.DesktopEntry) {
  63. DesktopSectionScope(this).apply(block)
  64. }
  65. fun themeSection(
  66. block: ThemeSectionScope.() -> Unit
  67. ) = section(DesktopFile.DesktopFileType.Theme) {
  68. ThemeSectionScope(this).apply(block)
  69. }
  70. fun mkDirs(): DesktopFile {
  71. if (!file.parentFile.exists()) file.parentFile.mkdirs()
  72. return this
  73. }
  74. fun deleteFile(): DesktopFile {
  75. if (file.exists()) file.delete()
  76. return this
  77. }
  78. fun createNewFile(): DesktopFile {
  79. file.createNewFile()
  80. return this
  81. }
  82. fun newFile(): DesktopFile {
  83. mkDirs()
  84. deleteFile()
  85. createNewFile()
  86. return this
  87. }
  88. fun writeTo(f: File) {
  89. if (!f.parentFile.exists()) {
  90. f.parentFile.mkdirs()
  91. }
  92. if (f.exists()) {
  93. f.delete()
  94. }
  95. f.createNewFile()
  96. content.store(f)
  97. }
  98. fun write() =
  99. writeTo(file)
  100. interface ISectionScope {
  101. val section: Section
  102. }
  103. class DesktopSectionScope(
  104. override val section: Section
  105. ) : ISectionScope {
  106. var Type: DesktopFileType
  107. get() = DesktopFileType(section[Prop_Type])
  108. set(value) {
  109. section[Prop_Type] = value.text
  110. }
  111. var Version: String
  112. get() = ParsedString(section[Prop_Version])
  113. set(value) {
  114. section[Prop_Version] = value
  115. }
  116. var Name: String
  117. get() = ParsedString(section[Prop_Name])
  118. set(value) {
  119. section[Prop_Name] = value
  120. }
  121. var Comment: String
  122. get() = ParsedString(section[Prop_Comment])
  123. set(value) {
  124. section[Prop_Comment] = value
  125. }
  126. var Path: String
  127. get() = ParsedString(section[Prop_Path])
  128. set(value) {
  129. section[Prop_Path] = value
  130. }
  131. var Exec: String
  132. get() = ParsedString(section[Prop_Exec])
  133. set(value) {
  134. section[Prop_Exec] = value
  135. }
  136. var Icon: String
  137. get() = ParsedString(section[Prop_Icon])
  138. set(value) {
  139. section[Prop_Icon] = value
  140. }
  141. var Encoding: String
  142. get() = ParsedString(section[Prop_Encoding])
  143. set(value) {
  144. section[Prop_Encoding] = value
  145. }
  146. var NotifyOnStart: Boolean
  147. get() = ParsedBoolean(section[Prop_StartupNotify])
  148. set(value) {
  149. section[Prop_StartupNotify] = value.toString()
  150. }
  151. var RunInTerminal: Boolean
  152. get() = ParsedBoolean(section[Prop_Terminal])
  153. set(value) {
  154. section[Prop_Terminal] = value.toString()
  155. }
  156. var Categories: MutableList<String>
  157. get() = ParsedList(section[Prop_Categories])
  158. set(value) {
  159. section[Prop_Categories] = value.joinToString { "$it;" }
  160. }
  161. var OnlyShowIn: MutableList<String>
  162. get() = ParsedList(section[Prop_OnlyShowIn])
  163. set(value) {
  164. section[Prop_OnlyShowIn] = value.joinToString { "$it;" }
  165. }
  166. var StartupWMClass: String
  167. get() = ParsedString(section[Prop_StartupWMClass]).trim()
  168. set(value) {
  169. println("setting [$Prop_StartupWMClass] = $StartupWMClass")
  170. section[Prop_StartupWMClass] = value
  171. }
  172. }
  173. class ThemeSectionScope(
  174. override val section: Section
  175. ) : ISectionScope {
  176. var GtkTheme: String
  177. get() = ParsedString(section[Prop_GtkTheme])
  178. set(value) {
  179. section[Prop_GtkTheme] = value
  180. }
  181. var MetacityTheme: String
  182. get() = ParsedString(section[Prop_MetacityTheme])
  183. set(value) {
  184. section[Prop_MetacityTheme] = value
  185. }
  186. var IconTheme: String
  187. get() = ParsedString(section[Prop_IconTheme])
  188. set(value) {
  189. section[Prop_IconTheme] = value
  190. }
  191. var CursorTheme: String
  192. get() = ParsedString(section[Prop_CursorTheme])
  193. set(value) {
  194. section[Prop_CursorTheme] = value
  195. }
  196. var ButtonLayout: String
  197. get() = ParsedString(section[Prop_ButtonLayout])
  198. set(value) {
  199. section[Prop_ButtonLayout] = value
  200. }
  201. var UseOverlayScrollbars: Boolean
  202. get() = ParsedBoolean(section[Prop_X_Ubuntu_UseOverlayScrollbars])
  203. set(value) {
  204. section[Prop_X_Ubuntu_UseOverlayScrollbars] = value.toString()
  205. }
  206. }
  207. enum class DesktopFileType(val text: String) {
  208. Unknown(""),
  209. DesktopEntry("Desktop Entry"),
  210. Application("Application"),
  211. Theme("X-GNOME-Metatheme");
  212. companion object {
  213. operator fun invoke(
  214. value: String?
  215. ): DesktopFileType = value.orEmpty().trim().let { v ->
  216. entries.firstOrNull { e -> e.text.contentEquals(v, true) } ?: Unknown
  217. }
  218. }
  219. }
  220. companion object {
  221. const val Prop_Type = "Type"
  222. const val Prop_Version = "Version"
  223. const val Prop_Path = "Path"
  224. const val Prop_Name = "Name"
  225. const val Prop_Comment = "Comment"
  226. const val Prop_Exec = "Exec"
  227. const val Prop_StartupNotify = "StartupNotify"
  228. const val Prop_Terminal = "Terminal"
  229. const val Prop_Icon = "Icon"
  230. const val Prop_Categories = "Categories"
  231. const val Prop_StartupWMClass = "StartupWMClass"
  232. const val Prop_OnlyShowIn = "OnlyShowIn"
  233. // todo
  234. const val Prop_GenericName = "GenericName"
  235. const val Prop_MimeType = "MimeType"
  236. const val Prop_Actions = "Actions"
  237. const val Prop_DBusActivatable = "DBusActivatable"
  238. const val Prop_Encoding = "Encoding"
  239. const val Prop_Keywords = "Keywords"
  240. const val Prop_X_ExecArg = "X-ExecArg"
  241. const val Prop_X_Ubuntu_Gettext_Domain = "X-Ubuntu-Gettext-Domain"
  242. const val Prop_NoDisplay = "NoDisplay" // boolean
  243. const val Prop_X_GNOME_UsesNotifications = "X-GNOME-UsesNotifications"
  244. const val Prop_X_Purism_FormFactor = "X-Purism-FormFactor"
  245. const val Prop_X_Unity_IconBackgroundColor = "X-Unity-IconBackgroundColor"
  246. const val Prop_SingleMainWindow = "SingleMainWindow"
  247. // theme
  248. const val Prop_GtkTheme = "GtkTheme"
  249. const val Prop_MetacityTheme = "MetacityTheme"
  250. const val Prop_IconTheme = "IconTheme"
  251. const val Prop_CursorTheme = "CursorTheme"
  252. const val Prop_ButtonLayout = "ButtonLayout"
  253. const val Prop_X_Ubuntu_UseOverlayScrollbars = "X-Ubuntu-UseOverlayScrollbars"
  254. fun desktopFile(
  255. file: File,
  256. block: DesktopFile.() -> Unit
  257. ) = DesktopFile(file).apply(block)
  258. }
  259. }