gitpane 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. gitpane/__init__.py +0 -0
  2. gitpane/account/__init__.py +0 -0
  3. gitpane/account/account_choice.py +297 -0
  4. gitpane/account/address_owner.py +67 -0
  5. gitpane/account/authentication.py +1811 -0
  6. gitpane/account/authentication_worker.py +836 -0
  7. gitpane/account/credential_manager.py +480 -0
  8. gitpane/account/credential_manager_install.py +171 -0
  9. gitpane/account/keyring_reader.py +135 -0
  10. gitpane/account/keyring_state.py +158 -0
  11. gitpane/account/ssh.py +312 -0
  12. gitpane/account/ssh_askpass.py +216 -0
  13. gitpane/account/ssh_prompt.py +222 -0
  14. gitpane/application.py +684 -0
  15. gitpane/commit/__init__.py +0 -0
  16. gitpane/commit/commit_metadata.py +88 -0
  17. gitpane/commit/commit_push.py +276 -0
  18. gitpane/commit/commit_write.py +830 -0
  19. gitpane/config.py +1066 -0
  20. gitpane/controller/__init__.py +0 -0
  21. gitpane/controller/account_controller.py +677 -0
  22. gitpane/controller/clone_controller.py +776 -0
  23. gitpane/controller/close_readiness.py +212 -0
  24. gitpane/controller/commit_timing_controller.py +566 -0
  25. gitpane/controller/conflict_controller.py +885 -0
  26. gitpane/controller/console_registry.py +38 -0
  27. gitpane/controller/emoji_window_controller.py +95 -0
  28. gitpane/controller/file_action_controller.py +479 -0
  29. gitpane/controller/history_read_controller.py +671 -0
  30. gitpane/controller/history_rewrite_controller.py +739 -0
  31. gitpane/controller/navigation_controller.py +658 -0
  32. gitpane/controller/opening_controller.py +375 -0
  33. gitpane/controller/operation_policy.py +76 -0
  34. gitpane/controller/rebase_controller.py +797 -0
  35. gitpane/controller/rebase_editor_controller.py +309 -0
  36. gitpane/controller/reference_form_controller.py +1677 -0
  37. gitpane/controller/reference_read_controller.py +242 -0
  38. gitpane/controller/refresh_controller.py +477 -0
  39. gitpane/controller/replay_controller.py +805 -0
  40. gitpane/controller/session_registry.py +337 -0
  41. gitpane/controller/setting_controller.py +592 -0
  42. gitpane/controller/shutdown_controller.py +345 -0
  43. gitpane/controller/staging_controller.py +199 -0
  44. gitpane/controller/staging_session_registry.py +47 -0
  45. gitpane/controller/stash_controller.py +367 -0
  46. gitpane/controller/transport_controller.py +813 -0
  47. gitpane/controller/welcome_controller.py +102 -0
  48. gitpane/controller/window_activation.py +116 -0
  49. gitpane/controller/worktree_state_controller.py +169 -0
  50. gitpane/emoji/__init__.py +0 -0
  51. gitpane/emoji/emoji_catalogue.py +156 -0
  52. gitpane/git_config/__init__.py +0 -0
  53. gitpane/git_config/git_setting.py +441 -0
  54. gitpane/history/__init__.py +0 -0
  55. gitpane/history/avatar.py +241 -0
  56. gitpane/history/file_history.py +359 -0
  57. gitpane/history/graph.py +145 -0
  58. gitpane/history/history_filter.py +31 -0
  59. gitpane/history/history_parse.py +605 -0
  60. gitpane/history/repository.py +1600 -0
  61. gitpane/history/repository_value.py +341 -0
  62. gitpane/history/revision.py +180 -0
  63. gitpane/history/revision_file.py +182 -0
  64. gitpane/history_rewrite/__init__.py +0 -0
  65. gitpane/history_rewrite/graph.py +210 -0
  66. gitpane/history_rewrite/guard.py +288 -0
  67. gitpane/history_rewrite/index.py +125 -0
  68. gitpane/history_rewrite/prepare.py +378 -0
  69. gitpane/history_rewrite/protocol.py +320 -0
  70. gitpane/history_rewrite/publish.py +444 -0
  71. gitpane/history_rewrite/recovery.py +481 -0
  72. gitpane/history_rewrite/value.py +229 -0
  73. gitpane/operation/__init__.py +0 -0
  74. gitpane/operation/console_line.py +313 -0
  75. gitpane/operation/console_tone.py +76 -0
  76. gitpane/operation/diagnostic.py +954 -0
  77. gitpane/operation/launch_signature.py +254 -0
  78. gitpane/operation/operation_status.py +87 -0
  79. gitpane/operation/operation_trace.py +766 -0
  80. gitpane/operation/repository_operation.py +781 -0
  81. gitpane/preference/__init__.py +0 -0
  82. gitpane/preference/preference_location.py +40 -0
  83. gitpane/preference/preference_store.py +590 -0
  84. gitpane/process/__init__.py +0 -0
  85. gitpane/process/exact_arguments.py +77 -0
  86. gitpane/process/git_output_text.py +20 -0
  87. gitpane/process/process_group.py +851 -0
  88. gitpane/process/process_log.py +114 -0
  89. gitpane/process/process_runner.py +1033 -0
  90. gitpane/process/released_child_watch.py +138 -0
  91. gitpane/recent/__init__.py +0 -0
  92. gitpane/recent/mount_table.py +422 -0
  93. gitpane/recent/recent_availability.py +414 -0
  94. gitpane/recent/recent_name.py +11 -0
  95. gitpane/recent/recent_search.py +114 -0
  96. gitpane/reference/__init__.py +0 -0
  97. gitpane/reference/reference_edit.py +2030 -0
  98. gitpane/reference/reference_snapshot.py +196 -0
  99. gitpane/reference/stash.py +730 -0
  100. gitpane/reference/stash_list.py +164 -0
  101. gitpane/sequencer/__init__.py +0 -0
  102. gitpane/sequencer/commit_message_rule.py +154 -0
  103. gitpane/sequencer/conflict.py +506 -0
  104. gitpane/sequencer/merge_recovery.py +163 -0
  105. gitpane/sequencer/merge_state.py +166 -0
  106. gitpane/sequencer/mergetool.py +856 -0
  107. gitpane/sequencer/rebase.py +750 -0
  108. gitpane/sequencer/rebase_edit_file.py +224 -0
  109. gitpane/sequencer/rebase_state.py +523 -0
  110. gitpane/sequencer/replay.py +1435 -0
  111. gitpane/sequencer/replay_legacy.py +253 -0
  112. gitpane/sequencer/replay_state.py +747 -0
  113. gitpane/submodule/__init__.py +0 -0
  114. gitpane/submodule/checkout_refusal.py +102 -0
  115. gitpane/submodule/checkout_refusal_notice.py +111 -0
  116. gitpane/submodule/cloned_without_checkout.py +65 -0
  117. gitpane/submodule/submodule_discovery.py +1180 -0
  118. gitpane/submodule/submodule_loss.py +218 -0
  119. gitpane/system/__init__.py +0 -0
  120. gitpane/system/appearance_portal.py +125 -0
  121. gitpane/system/application_identity.py +48 -0
  122. gitpane/system/command_line.py +36 -0
  123. gitpane/system/desktop_icon_fact.py +128 -0
  124. gitpane/system/desktop_icon_support.py +270 -0
  125. gitpane/system/desktop_shortcut.py +536 -0
  126. gitpane/system/icns_image.py +35 -0
  127. gitpane/system/installed_launcher.py +136 -0
  128. gitpane/system/linux_distribution.py +151 -0
  129. gitpane/system/network_folder.py +46 -0
  130. gitpane/system/path_identity.py +95 -0
  131. gitpane/system/private_tree_removal.py +47 -0
  132. gitpane/system/process_program.py +63 -0
  133. gitpane/system/qt_folder.py +38 -0
  134. gitpane/system/ready_channel_watch.py +67 -0
  135. gitpane/system/startup_activation.py +65 -0
  136. gitpane/system/terminal_detach.py +384 -0
  137. gitpane/system/xdg_session.py +85 -0
  138. gitpane/text/__init__.py +0 -0
  139. gitpane/text/text_query.py +97 -0
  140. gitpane/text/typed_path.py +75 -0
  141. gitpane/text/typed_text.py +17 -0
  142. gitpane/thread/__init__.py +0 -0
  143. gitpane/thread/weak_signal.py +93 -0
  144. gitpane/tool/__init__.py +0 -0
  145. gitpane/tool/app_bundle.py +164 -0
  146. gitpane/tool/desktop_text_editor.py +156 -0
  147. gitpane/tool/difftool.py +429 -0
  148. gitpane/tool/editor.py +445 -0
  149. gitpane/tool/editor_bridge.py +193 -0
  150. gitpane/tool/editor_recipe.py +481 -0
  151. gitpane/tool/file_reveal.py +94 -0
  152. gitpane/tool/git_executable.py +264 -0
  153. gitpane/tool/git_version.py +62 -0
  154. gitpane/tool/kdiff3.py +41 -0
  155. gitpane/tool/kdiff3_install.py +162 -0
  156. gitpane/tool/launch_services_text_editor.py +104 -0
  157. gitpane/tool/mergetool_recipe.py +84 -0
  158. gitpane/tool/system_editor_default.py +80 -0
  159. gitpane/tool/system_text_editor.py +44 -0
  160. gitpane/tool/system_tool.py +816 -0
  161. gitpane/tool/terminal_recipe.py +390 -0
  162. gitpane/transport/__init__.py +0 -0
  163. gitpane/transport/branch_exchange.py +1690 -0
  164. gitpane/transport/clone.py +1273 -0
  165. gitpane/transport/credential_read_retry.py +35 -0
  166. gitpane/transport/git_refusal.py +207 -0
  167. gitpane/transport/push_tracking.py +121 -0
  168. gitpane/transport/refusal_notice.py +225 -0
  169. gitpane/transport/remote.py +747 -0
  170. gitpane/transport/remote_config.py +544 -0
  171. gitpane/ui/__init__.py +0 -0
  172. gitpane/ui/console/__init__.py +0 -0
  173. gitpane/ui/console/error_dialog.py +332 -0
  174. gitpane/ui/console/operation_dialog.py +408 -0
  175. gitpane/ui/console/operation_output.py +91 -0
  176. gitpane/ui/console/operation_output_view.py +373 -0
  177. gitpane/ui/emoji/__init__.py +0 -0
  178. gitpane/ui/emoji/emoji_font.py +257 -0
  179. gitpane/ui/emoji/emoji_grid.py +125 -0
  180. gitpane/ui/emoji/emoji_window.py +154 -0
  181. gitpane/ui/emoji/file_action_icon.py +131 -0
  182. gitpane/ui/emoji/opentype_family_name.py +182 -0
  183. gitpane/ui/history_rewrite/__init__.py +0 -0
  184. gitpane/ui/history_rewrite/details.py +78 -0
  185. gitpane/ui/history_rewrite/preview.py +565 -0
  186. gitpane/ui/history_rewrite/preview_model.py +291 -0
  187. gitpane/ui/history_rewrite/question.py +87 -0
  188. gitpane/ui/history_rewrite/recovery_dialog.py +262 -0
  189. gitpane/ui/look/__init__.py +0 -0
  190. gitpane/ui/look/action_icon.py +504 -0
  191. gitpane/ui/look/application_icon.py +78 -0
  192. gitpane/ui/look/application_style.py +317 -0
  193. gitpane/ui/look/avatar_label.py +53 -0
  194. gitpane/ui/look/color_contrast.py +43 -0
  195. gitpane/ui/look/desktop_appearance_follower.py +118 -0
  196. gitpane/ui/look/fixed_font.py +21 -0
  197. gitpane/ui/look/gtk_settings.py +116 -0
  198. gitpane/ui/look/icon_density.py +16 -0
  199. gitpane/ui/look/reference_font.py +19 -0
  200. gitpane/ui/look/theme_color.py +63 -0
  201. gitpane/ui/look/theme_look.py +95 -0
  202. gitpane/ui/look/theme_palette.py +173 -0
  203. gitpane/ui/look/warning_icon.py +86 -0
  204. gitpane/ui/look/windows_control_look.py +250 -0
  205. gitpane/ui/reader/__init__.py +0 -0
  206. gitpane/ui/reader/commit_message.py +129 -0
  207. gitpane/ui/reader/commit_panel.py +378 -0
  208. gitpane/ui/reader/diff_panel.py +626 -0
  209. gitpane/ui/reader/file_action.py +138 -0
  210. gitpane/ui/reader/file_tree_panel.py +235 -0
  211. gitpane/ui/reader/reader_toolbar.py +56 -0
  212. gitpane/ui/reader/revision_tree.py +80 -0
  213. gitpane/ui/reader/search_result.py +165 -0
  214. gitpane/ui/reader/source_text.py +134 -0
  215. gitpane/ui/reader/source_view.py +151 -0
  216. gitpane/ui/reader/syntax_span.py +67 -0
  217. gitpane/ui/reference/__init__.py +0 -0
  218. gitpane/ui/reference/branch_selector.py +106 -0
  219. gitpane/ui/reference/checkout_dialog.py +419 -0
  220. gitpane/ui/reference/checkout_revision_dialog.py +182 -0
  221. gitpane/ui/reference/commit_summary.py +339 -0
  222. gitpane/ui/reference/create_branch_dialog.py +346 -0
  223. gitpane/ui/reference/create_tag_dialog.py +283 -0
  224. gitpane/ui/reference/delete_local_branch_dialog.py +323 -0
  225. gitpane/ui/reference/delete_tag_dialog.py +394 -0
  226. gitpane/ui/reference/form_opening.py +53 -0
  227. gitpane/ui/reference/reference_dialog.py +417 -0
  228. gitpane/ui/reference/reference_feedback.py +62 -0
  229. gitpane/ui/reference/reference_form.py +139 -0
  230. gitpane/ui/reference/rename_branch_dialog.py +179 -0
  231. gitpane/ui/reference/reset_branch_dialog.py +430 -0
  232. gitpane/ui/reference/reset_dialog.py +263 -0
  233. gitpane/ui/reference/revision_dialog.py +121 -0
  234. gitpane/ui/reference/revision_picker.py +227 -0
  235. gitpane/ui/reference/select_branches_dialog.py +199 -0
  236. gitpane/ui/reference/stash_dialog.py +171 -0
  237. gitpane/ui/repository/__init__.py +0 -0
  238. gitpane/ui/repository/graph_delegate.py +298 -0
  239. gitpane/ui/repository/history_avatar.py +130 -0
  240. gitpane/ui/repository/history_model.py +179 -0
  241. gitpane/ui/repository/repository_list_popup.py +319 -0
  242. gitpane/ui/repository/repository_window.py +2580 -0
  243. gitpane/ui/resource/ArrowDown.png +0 -0
  244. gitpane/ui/resource/ArrowUp.png +0 -0
  245. gitpane/ui/resource/BranchCheckout.png +0 -0
  246. gitpane/ui/resource/BranchCreate.png +0 -0
  247. gitpane/ui/resource/BranchDelete.png +0 -0
  248. gitpane/ui/resource/CopyToClipboard.png +0 -0
  249. gitpane/ui/resource/GitPane-16.png +0 -0
  250. gitpane/ui/resource/GitPane-24.png +0 -0
  251. gitpane/ui/resource/GitPane-256.png +0 -0
  252. gitpane/ui/resource/GitPane-32.png +0 -0
  253. gitpane/ui/resource/GitPane-48.png +0 -0
  254. gitpane/ui/resource/GitPane-64.png +0 -0
  255. gitpane/ui/resource/GitPane.ico +0 -0
  256. gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
  257. gitpane/ui/resource/NotoColorEmoji_WindowsCompatible.ttf +0 -0
  258. gitpane/ui/resource/Renamed.png +0 -0
  259. gitpane/ui/resource/ResetAnotherBranchToHere.png +0 -0
  260. gitpane/ui/resource/ResetCurrentBranchToHere.png +0 -0
  261. gitpane/ui/resource/Stash.png +0 -0
  262. gitpane/ui/resource/Tag.png +0 -0
  263. gitpane/ui/resource/TagCreate.png +0 -0
  264. gitpane/ui/resource/TagDelete.png +0 -0
  265. gitpane/ui/resource/Unmerged.png +0 -0
  266. gitpane/ui/resource/checkout.png +0 -0
  267. gitpane/ui/sequencer/__init__.py +0 -0
  268. gitpane/ui/sequencer/conflict_dialog.py +532 -0
  269. gitpane/ui/sequencer/merge_recovery_dialog.py +67 -0
  270. gitpane/ui/sequencer/rebase_dialog.py +461 -0
  271. gitpane/ui/sequencer/rebase_editor_window.py +220 -0
  272. gitpane/ui/sequencer/replay_conflict_prompt.py +105 -0
  273. gitpane/ui/sequencer/replay_dialog.py +501 -0
  274. gitpane/ui/setting/__init__.py +0 -0
  275. gitpane/ui/setting/account_page.py +724 -0
  276. gitpane/ui/setting/add_account_dialog.py +1005 -0
  277. gitpane/ui/setting/credential_manager_dialog.py +151 -0
  278. gitpane/ui/setting/git_setting_widget.py +388 -0
  279. gitpane/ui/setting/setting_block.py +19 -0
  280. gitpane/ui/setting/setting_dialog.py +750 -0
  281. gitpane/ui/setting/ssh_widget.py +232 -0
  282. gitpane/ui/setting/utility_page.py +240 -0
  283. gitpane/ui/staging/__init__.py +0 -0
  284. gitpane/ui/staging/staging_action.py +356 -0
  285. gitpane/ui/staging/staging_window.py +745 -0
  286. gitpane/ui/transport/__init__.py +0 -0
  287. gitpane/ui/transport/clone_dialog.py +879 -0
  288. gitpane/ui/transport/push_dialog.py +580 -0
  289. gitpane/ui/transport/push_rejection_prompt.py +100 -0
  290. gitpane/ui/transport/push_tracking_question.py +88 -0
  291. gitpane/ui/transport/remote_dialog.py +943 -0
  292. gitpane/ui/welcome/__init__.py +0 -0
  293. gitpane/ui/welcome/invalid_repository_prompt.py +114 -0
  294. gitpane/ui/welcome/welcome_window.py +1137 -0
  295. gitpane/ui/widget/__init__.py +0 -0
  296. gitpane/ui/widget/busy_indicator.py +106 -0
  297. gitpane/ui/widget/commit_date_text.py +55 -0
  298. gitpane/ui/widget/content_growth.py +262 -0
  299. gitpane/ui/widget/content_measure.py +99 -0
  300. gitpane/ui/widget/dialog_layout.py +668 -0
  301. gitpane/ui/widget/drop_down_wheel_filter.py +32 -0
  302. gitpane/ui/widget/flow_layout.py +110 -0
  303. gitpane/ui/widget/long_text.py +542 -0
  304. gitpane/ui/widget/mnemonic.py +13 -0
  305. gitpane/ui/widget/numbered_text_edit.py +127 -0
  306. gitpane/ui/widget/plain_label.py +15 -0
  307. gitpane/ui/widget/plain_tooltip.py +19 -0
  308. gitpane/ui/widget/refresh_advice.py +25 -0
  309. gitpane/ui/widget/result_label.py +227 -0
  310. gitpane/ui/widget/result_status_bar.py +53 -0
  311. gitpane/ui/widget/scrolled_text.py +69 -0
  312. gitpane/ui/widget/shortcut_text.py +31 -0
  313. gitpane/ui/widget/standard_icon_label.py +55 -0
  314. gitpane/ui/widget/system_shortcut.py +75 -0
  315. gitpane/ui/widget/unavailable_reason.py +29 -0
  316. gitpane/ui/widget/wheel_safe_drop_down.py +24 -0
  317. gitpane/ui/window_control/__init__.py +0 -0
  318. gitpane/ui/window_control/button_focus.py +42 -0
  319. gitpane/ui/window_control/compositor_answer.py +152 -0
  320. gitpane/ui/window_control/compositor_growth_cap.py +73 -0
  321. gitpane/ui/window_control/desktop_activation.py +203 -0
  322. gitpane/ui/window_control/dialog_modality.py +122 -0
  323. gitpane/ui/window_control/item_activation.py +42 -0
  324. gitpane/ui/window_control/modal_focus_relay.py +124 -0
  325. gitpane/ui/window_control/wayland_activation.py +695 -0
  326. gitpane/ui/window_control/window_placement.py +305 -0
  327. gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
  328. gitpane/vendor/w32.exe +0 -0
  329. gitpane/vendor/w64-arm.exe +0 -0
  330. gitpane/vendor/w64.exe +0 -0
  331. gitpane/worktree/__init__.py +0 -0
  332. gitpane/worktree/command_sequence.py +110 -0
  333. gitpane/worktree/file_action_target.py +61 -0
  334. gitpane/worktree/file_menu_action.py +52 -0
  335. gitpane/worktree/historical_restore.py +361 -0
  336. gitpane/worktree/ignore_rule.py +266 -0
  337. gitpane/worktree/mutation.py +1539 -0
  338. gitpane/worktree/partial_patch.py +435 -0
  339. gitpane/worktree/staged_reset.py +447 -0
  340. gitpane/worktree/staging.py +1418 -0
  341. gitpane/worktree/trash.py +302 -0
  342. gitpane/worktree/worktree_file.py +406 -0
  343. gitpane/worktree/worktree_service.py +584 -0
  344. gitpane/worktree/worktree_snapshot.py +630 -0
  345. gitpane-1.0.0.dist-info/METADATA +141 -0
  346. gitpane-1.0.0.dist-info/RECORD +353 -0
  347. gitpane-1.0.0.dist-info/WHEEL +5 -0
  348. gitpane-1.0.0.dist-info/entry_points.txt +2 -0
  349. gitpane-1.0.0.dist-info/licenses/LICENSE +21 -0
  350. gitpane-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +109 -0
  351. gitpane-1.0.0.dist-info/licenses/src/gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
  352. gitpane-1.0.0.dist-info/licenses/src/gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
  353. gitpane-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,270 @@
1
+ """Decide whether the user's desktop can show a desktop icon, with the steps that would let it.
2
+
3
+ Every function is pure: it receives the facts it judges (system, desktop, distribution, extension state, desktop
4
+ folder), read elsewhere, so each case can be checked from any system. A desktop that needs an extension to show icons
5
+ is one row of the desktop table, and the command that installs that extension on a distribution is one row of the
6
+ distribution table: adding a desktop or a distribution adds a row, without touching Settings or the shortcut writer.
7
+ The commands the note shows come only from these tables, and the desktop folder only through the system's quoting;
8
+ nothing read from the machine ever becomes a command.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import shlex
14
+ from dataclasses import dataclass, replace
15
+ from enum import Enum
16
+ from typing import TYPE_CHECKING
17
+
18
+ from gitpane.config import LINUX_PLATFORM
19
+ from gitpane.system.desktop_shortcut import ShortcutOutcome, ShortcutReport, ShortcutStatus
20
+ from gitpane.system.installed_launcher import command_line
21
+ from gitpane.system.linux_distribution import DistributionFamily, distribution_family
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Mapping
25
+
26
+ DESKTOP_ICONS_EXTENSION_UUID = "ding@rastersoft.com"
27
+ DESKTOP_ICONS_EXTENSION_NAME = "Desktop Icons NG"
28
+ GNOME_EXTENSIONS_PROGRAM = "gnome-extensions"
29
+ GNOME_EXTENSIONS_ENABLE_ARGUMENT = "enable"
30
+ GNOME_DESKTOP_NAME = "gnome"
31
+ MKDIR_PROGRAM = "mkdir"
32
+ NOTE_LINE_END = "\n"
33
+ # The note's layout: each step indented under the introduction, its command alone on the next line, aligned under the
34
+ # step's text, so a beginner copies exactly one command.
35
+ NOTE_STEP_INDENT = " "
36
+ NOTE_COMMAND_INDENT = " "
37
+ NOTE_STEP_TEMPLATE = "{indent}{number}. {text}"
38
+ FIRST_STEP_NUMBER = 1
39
+ APPLICATIONS_LIST_PLACE_TEMPLATE = " ({place})"
40
+
41
+
42
+ class ExtensionState(Enum):
43
+ """What GitPane knows of a GNOME Shell extension; UNKNOWN when it could not read it."""
44
+
45
+ UNKNOWN = "unknown"
46
+ NOT_INSTALLED = "not installed"
47
+ INSTALLED_NOT_LOADED = "installed, not loaded"
48
+ """Its files are on the disk, but the Shell only reads its extension folders when the session starts."""
49
+ INACTIVE = "inactive"
50
+ ACTIVE = "active"
51
+
52
+
53
+ # The states in which an installed or missing extension keeps desktop icons from showing; UNKNOWN is never one of
54
+ # them, so a state GitPane could not read never gives a note.
55
+ ICON_HIDING_EXTENSION_STATE_SET = frozenset({ExtensionState.NOT_INSTALLED, ExtensionState.INSTALLED_NOT_LOADED, ExtensionState.INACTIVE})
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class DesktopIconRule:
60
+ """How one desktop shows icons from the desktop folder: through an extension to install then enable, and where it lists applications."""
61
+
62
+ desktop_label: str
63
+ extension_uuid: str
64
+ extension_name: str
65
+ enable_command: str
66
+ applications_list_place: str
67
+
68
+
69
+ # A desktop missing from this table shows its desktop icons itself or is not known: it gets no extension note. The
70
+ # name is the first, most specific one of XDG_CURRENT_DESKTOP, lowercased: Ubuntu's GNOME declares "ubuntu:GNOME" and
71
+ # runs the extension through its own session mode, so it has no row.
72
+ DESKTOP_ICON_RULE_BY_DESKTOP_NAME = {
73
+ GNOME_DESKTOP_NAME: DesktopIconRule("GNOME", DESKTOP_ICONS_EXTENSION_UUID, DESKTOP_ICONS_EXTENSION_NAME,
74
+ shlex.join((GNOME_EXTENSIONS_PROGRAM, GNOME_EXTENSIONS_ENABLE_ARGUMENT, DESKTOP_ICONS_EXTENSION_UUID)), "the search of Activities"),
75
+ }
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class DistributionRule:
80
+ """The command that installs each extension from one distribution's packages; an extension it does not list is installed in words."""
81
+
82
+ install_command_by_extension_uuid: Mapping[str, str]
83
+
84
+
85
+ # One row per family of distributions, the family os-release describes; a family without a row gets the default row.
86
+ DISTRIBUTION_RULE_BY_FAMILY: dict[DistributionFamily, DistributionRule] = {
87
+ DistributionFamily.DEBIAN: DistributionRule({DESKTOP_ICONS_EXTENSION_UUID: "sudo apt install gnome-shell-extension-desktop-icons-ng"}),
88
+ }
89
+ DEFAULT_DISTRIBUTION_RULE = DistributionRule({})
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class DesktopIconFacts:
94
+ """What the desktop icon decision judges, read from the session: system, first desktop name, os-release, extension state and desktop folder.
95
+
96
+ ``desktop_name`` is empty outside Linux or when the session declares no desktop, and ``desktop_dir`` is empty when
97
+ the system names no desktop folder.
98
+ """
99
+
100
+ platform: str
101
+ desktop_name: str
102
+ os_release_by_key: Mapping[str, str]
103
+ extension_state: ExtensionState
104
+ desktop_dir: str
105
+ desktop_dir_exists: bool
106
+
107
+
108
+ class DesktopIconNoteText(Enum):
109
+ """Texts of the note that says why no desktop icon can show and how to get one; ``{desktop}``, ``{extension}``, ``{folder}`` and ``{place}`` are filled in."""
110
+
111
+ NOT_INSTALLED_REASON = "{desktop} shows no icons on the desktop without the {extension} extension, which is not installed."
112
+ NOT_LOADED_REASON = "{desktop} shows no icons on the desktop without the {extension} extension, which is installed but not loaded yet."
113
+ INACTIVE_REASON = "{desktop} shows no icons on the desktop without the {extension} extension, which is installed but not enabled."
114
+ DESKTOP_FOLDER_MISSING_REASON = "The desktop folder {folder} does not exist, and GitPane never creates it."
115
+ DESKTOP_FOLDER_UNNAMED_REASON = "The system names no desktop folder."
116
+ STEPS_INTRO = "To get a GitPane icon on the desktop:"
117
+ RUN_IN_TERMINAL = "In a terminal, run:"
118
+ LOG_OUT_AND_BACK_IN = "Log out, then log back in."
119
+ INSTALL_FROM_DISTRIBUTION = "Install the {extension} extension of your distribution."
120
+ CLICK_AGAIN = "Click the button above again."
121
+ FALLBACK_AFTER_STEPS = "Without these steps, the button above still adds GitPane to the applications list{place}."
122
+ FALLBACK_WITHOUT_STEPS = "The button above still adds GitPane to the applications list{place}."
123
+ CANNOT_SHOW_ICONS = "{desktop} cannot show desktop icons without the {extension} extension"
124
+ """The detail of the orange line of a shortcut written that the desktop will not show yet."""
125
+
126
+
127
+ @dataclass(frozen=True)
128
+ class NoteStep:
129
+ """One numbered step of the note: what to do, and the command to type alone on the next line when there is one."""
130
+
131
+ text: str
132
+ command: str = ""
133
+
134
+
135
+ def distribution_rule(os_release_by_key: Mapping[str, str]) -> DistributionRule:
136
+ """Return the row of the family of distributions os-release describes, otherwise the default row.
137
+
138
+ The family comes from ``gitpane.system.linux_distribution``, which reads ID then each word of ID_LIKE: Ubuntu,
139
+ Linux Mint and a derivative that only says ``ID_LIKE=ubuntu`` all find Debian's row.
140
+ """
141
+ return DISTRIBUTION_RULE_BY_FAMILY.get(distribution_family(os_release_by_key), DEFAULT_DISTRIBUTION_RULE)
142
+
143
+
144
+ def desktop_icon_rule(platform: str, desktop_name: str) -> DesktopIconRule | None:
145
+ """Return the row of a Linux desktop that needs an extension to show desktop icons, or None for any other desktop or system."""
146
+ if platform != LINUX_PLATFORM:
147
+ return None
148
+ return DESKTOP_ICON_RULE_BY_DESKTOP_NAME.get(desktop_name)
149
+
150
+
151
+ def extension_keeps_icons_hidden(facts: DesktopIconFacts) -> bool:
152
+ """Tell whether the desktop of ``facts`` needs an extension that is missing, not loaded yet or not enabled, so it shows no desktop icon."""
153
+ return desktop_icon_rule(facts.platform, facts.desktop_name) is not None and facts.extension_state in ICON_HIDING_EXTENSION_STATE_SET
154
+
155
+
156
+ def extension_reason_text(extension_state: ExtensionState) -> DesktopIconNoteText:
157
+ """Return the sentence that says why the extension shows no icons, for a state that keeps them hidden."""
158
+ if extension_state is ExtensionState.INACTIVE:
159
+ return DesktopIconNoteText.INACTIVE_REASON
160
+ if extension_state is ExtensionState.INSTALLED_NOT_LOADED:
161
+ return DesktopIconNoteText.NOT_LOADED_REASON
162
+ return DesktopIconNoteText.NOT_INSTALLED_REASON
163
+
164
+
165
+ def extension_install_step(rule: DesktopIconRule, distribution: DistributionRule) -> NoteStep:
166
+ """Return the step that installs the extension: the distribution's command, or the same request in words when its row has none."""
167
+ install_command = distribution.install_command_by_extension_uuid.get(rule.extension_uuid, "")
168
+ if install_command:
169
+ return NoteStep(DesktopIconNoteText.RUN_IN_TERMINAL.value, install_command)
170
+ return NoteStep(DesktopIconNoteText.INSTALL_FROM_DISTRIBUTION.value.format(extension=rule.extension_name))
171
+
172
+
173
+ def extension_steps(extension_state: ExtensionState, rule: DesktopIconRule, distribution: DistributionRule) -> list[NoteStep]:
174
+ """Return the steps left before the extension shows icons: install, log out and back in, then enable, keeping only what its state still needs.
175
+
176
+ The Shell reads its extension folders only when the session starts, so an extension installed during the session
177
+ can only be enabled after logging out and back in; once enabled, the Shell runs it at once.
178
+ """
179
+ enable_step = NoteStep(DesktopIconNoteText.RUN_IN_TERMINAL.value, rule.enable_command)
180
+ if extension_state is ExtensionState.INACTIVE:
181
+ return [enable_step]
182
+ log_out_step = NoteStep(DesktopIconNoteText.LOG_OUT_AND_BACK_IN.value)
183
+ if extension_state is ExtensionState.INSTALLED_NOT_LOADED:
184
+ return [log_out_step, enable_step]
185
+ return [extension_install_step(rule, distribution), log_out_step, enable_step]
186
+
187
+
188
+ def desktop_folder_reason_and_steps(facts: DesktopIconFacts) -> tuple[str, list[NoteStep]] | None:
189
+ """Return why the desktop folder keeps a shortcut from being written, with the steps that would let it, or None when the folder is there.
190
+
191
+ The folder is quoted the way the system's own shell reads it back, so a path with spaces or quotes stays one
192
+ command; GitPane only shows that command and never runs it.
193
+ """
194
+ if not facts.desktop_dir:
195
+ return DesktopIconNoteText.DESKTOP_FOLDER_UNNAMED_REASON.value, []
196
+ if facts.desktop_dir_exists:
197
+ return None
198
+ create_step = NoteStep(DesktopIconNoteText.RUN_IN_TERMINAL.value, command_line((MKDIR_PROGRAM, facts.desktop_dir), facts.platform))
199
+ return DesktopIconNoteText.DESKTOP_FOLDER_MISSING_REASON.value.format(folder=facts.desktop_dir), [create_step, NoteStep(DesktopIconNoteText.CLICK_AGAIN.value)]
200
+
201
+
202
+ def numbered_step_lines(steps: list[NoteStep]) -> list[str]:
203
+ """Write the steps as one numbered list, each command alone on the line under its step."""
204
+ lines = []
205
+ for number, step in enumerate(steps, start=FIRST_STEP_NUMBER):
206
+ lines.append(NOTE_STEP_TEMPLATE.format(indent=NOTE_STEP_INDENT, number=number, text=step.text))
207
+ if step.command:
208
+ lines.append(NOTE_COMMAND_INDENT + step.command)
209
+ return lines
210
+
211
+
212
+ def desktop_icon_note(facts: DesktopIconFacts) -> str:
213
+ """Write the note that says why no desktop icon can show and how to get one, or an empty text when nothing keeps one from showing.
214
+
215
+ One sentence per cause, the extension first and the desktop folder next, then the steps of both in one numbered
216
+ list. On Linux, a last sentence says that the button still adds GitPane to the applications list. The note and the
217
+ orange line of the report ask the same rule, ``extension_keeps_icons_hidden``, so they never disagree.
218
+ """
219
+ reasons = []
220
+ steps: list[NoteStep] = []
221
+ rule = desktop_icon_rule(facts.platform, facts.desktop_name)
222
+ # Whether the extension hides the icons is the question the orange line asks too; ``rule is not None`` stays only so
223
+ # that the rule's labels can be read below.
224
+ if rule is not None and extension_keeps_icons_hidden(facts):
225
+ reasons.append(extension_reason_text(facts.extension_state).value.format(desktop=rule.desktop_label, extension=rule.extension_name))
226
+ steps.extend(extension_steps(facts.extension_state, rule, distribution_rule(facts.os_release_by_key)))
227
+ folder_reason_and_steps = desktop_folder_reason_and_steps(facts)
228
+ if folder_reason_and_steps is not None:
229
+ folder_reason, folder_steps = folder_reason_and_steps
230
+ reasons.append(folder_reason)
231
+ steps.extend(folder_steps)
232
+ if not reasons:
233
+ return ""
234
+ lines = list(reasons)
235
+ if steps:
236
+ lines.append(DesktopIconNoteText.STEPS_INTRO.value)
237
+ lines.extend(numbered_step_lines(steps))
238
+ if facts.platform == LINUX_PLATFORM:
239
+ place = APPLICATIONS_LIST_PLACE_TEMPLATE.format(place=rule.applications_list_place) if rule is not None else ""
240
+ fallback = DesktopIconNoteText.FALLBACK_AFTER_STEPS if steps else DesktopIconNoteText.FALLBACK_WITHOUT_STEPS
241
+ lines.append(fallback.value.format(place=place))
242
+ return NOTE_LINE_END.join(lines)
243
+
244
+
245
+ def report_for_desktop(report: ShortcutReport, facts: DesktopIconFacts | None) -> ShortcutReport:
246
+ """Return the shortcut report as the desktop will show it: a desktop shortcut written that the desktop keeps hidden becomes an orange line pointing at the note.
247
+
248
+ The orange line says only what is true of this click: it says GitPane is added to the applications list only when
249
+ the same report holds the line of that entry written, so it never contradicts a red entry line under it. Every
250
+ other line and the command stay as the writer gave them; with no facts read, or facts that hide nothing, the
251
+ report is returned unchanged.
252
+ """
253
+ if facts is None or not extension_keeps_icons_hidden(facts):
254
+ return report
255
+ rule = desktop_icon_rule(facts.platform, facts.desktop_name)
256
+ if rule is None:
257
+ return report
258
+ hidden_detail = DesktopIconNoteText.CANNOT_SHOW_ICONS.value.format(desktop=rule.desktop_label, extension=rule.extension_name)
259
+ entry_added = False
260
+ for outcome in report.file_outcomes:
261
+ if outcome.status is ShortcutStatus.APPLICATION_ENTRY_ADDED:
262
+ entry_added = True
263
+ hidden_status = ShortcutStatus.CREATED_NOT_SHOWN if entry_added else ShortcutStatus.CREATED_NOT_SHOWN_ENTRY_FAILED
264
+ file_outcomes = []
265
+ for outcome in report.file_outcomes:
266
+ if outcome.status is ShortcutStatus.CREATED:
267
+ file_outcomes.append(ShortcutOutcome(hidden_status, hidden_detail))
268
+ else:
269
+ file_outcomes.append(outcome)
270
+ return replace(report, file_outcomes=tuple(file_outcomes))