DesktopFile.kt 9.2 KB

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