buninu-browser 0.3.0 → 0.4.0
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.
- package/LICENSES/jsmdcui.txt +22 -0
- package/README.md +19 -4
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +123 -0
- package/frontends/wayland/casty/LICENSE +21 -0
- package/frontends/wayland/casty/bookmarks.js +32 -0
- package/frontends/wayland/casty/click.js +96 -0
- package/frontends/wayland/casty/commands.js +61 -0
- package/frontends/wayland/casty/config.js +45 -0
- package/frontends/wayland/casty/download-directory.js +28 -0
- package/frontends/wayland/casty/download.js +49 -0
- package/frontends/wayland/casty/hints.js +334 -0
- package/frontends/wayland/casty/keys.js +56 -0
- package/frontends/wayland/casty/page-commands.js +80 -0
- package/frontends/wayland/casty/prompt-width.js +148 -0
- package/frontends/wayland/casty/urlbar.js +606 -0
- package/frontends/wayland/client.js +88 -0
- package/frontends/wayland/clipboard.js +80 -0
- package/frontends/wayland/index.js +116 -0
- package/frontends/wayland/input.js +56 -0
- package/frontends/wayland/protocol.js +55 -0
- package/frontends/wayland/protocols/wayland.xml +3248 -0
- package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
- package/frontends/wayland/transport.js +120 -0
- package/frontends/wayland/urlbar.js +158 -0
- package/index.js +28 -1
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +37 -2
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +47 -8
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +5 -1
- package/lib/qml/README.md +971 -0
- package/lib/qml/builtins.js +700 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/document.js +1176 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +324 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +517 -0
- package/lib/qml/media-renderer.js +49 -0
- package/lib/qml/media.js +86 -0
- package/lib/qml/models.js +622 -0
- package/lib/qml/parser.js +381 -0
- package/lib/qml/qs +5 -0
- package/lib/qml/qscasty +3 -0
- package/lib/qml/qsheadless +3 -0
- package/lib/qml/qswayland +3 -0
- package/lib/qml/reactive.js +159 -0
- package/lib/qml/runtime.js +3381 -0
- package/lib/qml/shapes.js +243 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +123 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +11 -0
- package/lib/renderer/canvas-2d.js +13 -0
- package/lib/renderer/host.js +31 -6
- package/lib/renderer/main.js +19 -6
- package/lib/renderer/page-realm.js +16 -1
- package/lib/renderer/page-renderer.js +230 -13
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +2 -2
- package/package.json +1 -1
- package/test/clipboard.test.js +19 -0
- package/test/fixtures/flea-helpers/Dockerfile +36 -0
- package/test/fixtures/flea-helpers/bat.mjs +43 -0
- package/test/fixtures/flea-helpers/bin/bat +3 -0
- package/test/fixtures/flea-helpers/bin/bwrap +10 -0
- package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
- package/test/fixtures/flea-helpers/bin/glow +3 -0
- package/test/fixtures/flea-helpers/bin/qs +3 -0
- package/test/fixtures/flea-helpers/bin/unsupported +3 -0
- package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
- package/test/fixtures/flea-helpers/check-media.mjs +29 -0
- package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
- package/test/fixtures/flea-helpers/check.mjs +36 -0
- package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
- package/test/fixtures/flea-helpers/source-mime.xml +28 -0
- package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
- package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
- package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
- package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea/LICENSE +21 -0
- package/test/fixtures/qml/flea/README.md +25 -0
- package/test/fixtures/qml/flea/Theme.qml +8 -0
- package/test/fixtures/qml/flea/demo.qml +24 -0
- package/test/fixtures/qml/flea/qmldir +3 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
- package/test/fixtures/qml/flea-theme/README.md +70 -0
- package/test/fixtures/qml/flea-theme/button.qml +17 -0
- package/test/fixtures/qml/flea-theme/demo.qml +27 -0
- package/test/fixtures/qml/flea-theme/field.qml +21 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
- package/test/fixtures/qml/flea-theme/segment.qml +17 -0
- package/test/fixtures/qml/flea-theme/settings.qml +58 -0
- package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
- package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
- package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
- package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
- package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
- package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
- package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
- package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
- package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
- package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
- package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
- package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
- package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
- package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
- package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
- package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
- package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
- package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
- package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
- package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
- package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
- package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
- package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
- package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
- package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
- package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
- package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
- package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
- package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
- package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
- package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
- package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
- package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
- package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
- package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
- package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
- package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
- package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
- package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
- package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
- package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
- package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
- package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
- package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
- package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
- package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
- package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
- package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
- package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
- package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
- package/test/fixtures/qml/hello.qml +26 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
- package/test/fixtures/qml/imports/components/Badge.qml +15 -0
- package/test/fixtures/qml/imports/logic/base.js +2 -0
- package/test/fixtures/qml/imports/logic/counter.js +4 -0
- package/test/fixtures/qml/imports/logic/math.js +3 -0
- package/test/fixtures/qml/imports/logic/shared.js +3 -0
- package/test/fixtures/qml/imports/main.qml +21 -0
- package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
- package/test/fixtures/qml/imports/theme/qmldir +2 -0
- package/test/fixtures/qml/multiply.qml +61 -0
- package/test/fixtures/qml-canvas-dom.js +90 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/helpers/flea-sandbox.js +48 -0
- package/test/helpers/quickshell-fixture.js +125 -0
- package/test/html-clipboard-wayland.test.js +47 -0
- package/test/index.test.js +13 -0
- package/test/manual-casty-without-qml.js +87 -0
- package/test/manual-flea-application.js +49 -0
- package/test/manual-flea-binary.js +62 -0
- package/test/manual-flea-qml.js +9 -0
- package/test/manual-flea-settings.js +40 -0
- package/test/manual-flea-theme.js +40 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-clipboard-bridge.test.js +25 -0
- package/test/qml-clipboard-wayland.test.js +40 -0
- package/test/qml-connections-timing.test.js +73 -0
- package/test/qml-dynamic.test.js +401 -0
- package/test/qml-flea-app.test.js +146 -0
- package/test/qml-flea-backend.test.js +78 -0
- package/test/qml-flea-columns-backend.test.js +38 -0
- package/test/qml-flea-compile.test.js +78 -0
- package/test/qml-flea-launcher.test.js +88 -0
- package/test/qml-flea-markdown-backend.test.js +31 -0
- package/test/qml-flea-media.test.js +28 -0
- package/test/qml-flea-navigation-backend.test.js +35 -0
- package/test/qml-flea-navigation-timing.test.js +147 -0
- package/test/qml-flea-preview-backend.test.js +53 -0
- package/test/qml-flea-previews.test.js +163 -0
- package/test/qml-flea-rows.test.js +101 -0
- package/test/qml-flea-textures.test.js +59 -0
- package/test/qml-flea-view-switch.test.js +33 -0
- package/test/qml-flea-wayland.test.js +68 -0
- package/test/qml-flea.test.js +34 -0
- package/test/qml-host.test.js +238 -0
- package/test/qml-imports.test.js +102 -0
- package/test/qml-inherited-handlers.test.js +51 -0
- package/test/qml-input-selection.test.js +66 -0
- package/test/qml-interactions.test.js +309 -0
- package/test/qml-language.test.js +167 -0
- package/test/qml-launcher.test.js +54 -0
- package/test/qml-layout-text.test.js +205 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-persisted-delegates.test.js +45 -0
- package/test/qml-quickshell-controls.test.js +65 -0
- package/test/qml-quickshell-settings.test.js +62 -0
- package/test/qml-repeater.test.js +72 -0
- package/test/qml-style-updates.test.js +124 -0
- package/test/qml-textures.test.js +66 -0
- package/test/qml-views.test.js +332 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/renderer-host.test.js +11 -0
- package/test/text-input-pointer.test.js +31 -0
- package/test/text-input-scroll.test.js +29 -0
- package/test/text-input.test.js +47 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-urlbar.test.js +38 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
pragma Singleton
|
|
2
|
+
import QtQuick
|
|
3
|
+
import Quickshell
|
|
4
|
+
import Quickshell.Io
|
|
5
|
+
import qs.Commons
|
|
6
|
+
import "js/Density.js" as Density
|
|
7
|
+
import "js/Format.js" as Format
|
|
8
|
+
import "js/Keymap.js" as Keymap
|
|
9
|
+
import "js/Settings.js" as Settings
|
|
10
|
+
import "js/TextSize.js" as TextSize
|
|
11
|
+
import "js/UiState.js" as UiState
|
|
12
|
+
|
|
13
|
+
// The per-user state that outlives a window, `~/.local/state/flea/ui.json`. Read once here with a
|
|
14
|
+
// blocking FileView so the first paint already has it, and never written from QML: every change
|
|
15
|
+
// goes back out through `flea --ui-state`, the one Rust path that takes the lock, validates each
|
|
16
|
+
// key, merges the caller's and renames a temp into place. main() settles this file before the
|
|
17
|
+
// window, so whenever that settle succeeded on a document it could read, what is read here has
|
|
18
|
+
// already been through that same validation; one it could not read is left alone and lands in the
|
|
19
|
+
// default shape UiState.fromFile answers with. There is no second settings file: the settings panel
|
|
20
|
+
// writes these same keys through the same patch. See AGENTS.md "The state file".
|
|
21
|
+
QtObject {
|
|
22
|
+
id: root
|
|
23
|
+
|
|
24
|
+
// The whole document, so a later section reads its own key without a second file read.
|
|
25
|
+
property var state: ({})
|
|
26
|
+
|
|
27
|
+
// Mirrors "columns" in src/uischema.rs, and is the only default this front end needs before the
|
|
28
|
+
// first frame: it is what a first launch draws, when there is no file to settle and none to read.
|
|
29
|
+
readonly property var defaultColumns: ["name", "size", "date"]
|
|
30
|
+
|
|
31
|
+
// Mirrors "menu"."hidden" in src/uischema.rs, for the same first launch: six ids this release's
|
|
32
|
+
// menu cannot build, plus Copy as and Open in terminal, which the SettingsMenus board ships
|
|
33
|
+
// switched off. Every id here is an action ui/js/Menu.js gives a row, or will give one.
|
|
34
|
+
// Mirrors src/uischema.rs DEFAULTS menu.hidden exactly; the two drifted once and a fresh
|
|
35
|
+
// ui.json then hid a row the shipped schema shows.
|
|
36
|
+
readonly property var defaultMenuHidden: ["delete", "openTerminal", "placeMenu", "runScript",
|
|
37
|
+
"moveto", "copyto", "properties", "permissions", "copyAs",
|
|
38
|
+
"pasteAs", "invertSelection", "extThumbs", "run", "openAsText"]
|
|
39
|
+
|
|
40
|
+
// ui.json names what is SHOWN. ui/Header.qml, ui/Row.qml and ui/ContextMenu.qml all ask the
|
|
41
|
+
// opposite question, so the inversion lives here once rather than at each of them.
|
|
42
|
+
readonly property var columns: Array.isArray(root.state.columns) ? root.state.columns : root.defaultColumns
|
|
43
|
+
readonly property var hiddenCols: {
|
|
44
|
+
var out = []
|
|
45
|
+
var optional = ["mode", "size", "date", "kind"]
|
|
46
|
+
for (var i = 0; i < optional.length; i++) {
|
|
47
|
+
if (root.columns.indexOf(optional[i]) < 0)
|
|
48
|
+
out.push(optional[i])
|
|
49
|
+
}
|
|
50
|
+
return out
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The Display section's text size, `display.textSize` in src/uischema.rs: {"mode":"system"}
|
|
54
|
+
// follows Omarchy and is the default, and an override pins one of TextSize.js's seven stops as
|
|
55
|
+
// {"mode":N}. ui/Theme.qml is the only consumer, and the monitor scale beside it in the panel is
|
|
56
|
+
// the compositor's alone.
|
|
57
|
+
readonly property var display: root.state.display || ({})
|
|
58
|
+
readonly property var textSize: TextSize.parse(root.display.textSize)
|
|
59
|
+
|
|
60
|
+
// Omarchy's own size, held here so the Ctrl+Shift chords and the panel's own rows step away
|
|
61
|
+
// from and back to one anchor rather than each resolving their own.
|
|
62
|
+
readonly property int omarchyBase: Style.font.baseSize
|
|
63
|
+
|
|
64
|
+
// The context-menu actions switched off in the settings panel's Menus section, by action id, and
|
|
65
|
+
// the section's only state: ui/js/Menu.js applyHidden is the consumer and ui/ContextMenu.qml the
|
|
66
|
+
// one caller that passes it in, and the panel's master row is derived from this set, not stored.
|
|
67
|
+
readonly property var menu: root.state.menu || ({})
|
|
68
|
+
// "Enable shelf" is one switch, so the Menus row is not a stored menu id at all.
|
|
69
|
+
// It reads the shelf's own boolean, which is what src/shelfplugin.rs installs the bar plugin on.
|
|
70
|
+
readonly property bool shelfEnabled: (root.state.shelf || ({})).enabled === true
|
|
71
|
+
readonly property var menuHidden: (Array.isArray(root.menu.hidden) ? root.menu.hidden
|
|
72
|
+
: root.defaultMenuHidden)
|
|
73
|
+
.filter(function (id) { return id !== "shelf" })
|
|
74
|
+
.concat(root.shelfEnabled ? [] : ["shelf"])
|
|
75
|
+
|
|
76
|
+
// The Menus section's "Show keyboard hints" row, `keyHints` in src/uischema.rs. It draws the key
|
|
77
|
+
// beside every menu row and the tip under an empty directory, and it binds no key of its own:
|
|
78
|
+
// every chord answers whether this is on or off.
|
|
79
|
+
// === true, not !== false: an absent key is off, which is what src/uischema.rs stores and what
|
|
80
|
+
// driveSize and trashCount beside it already read. The other way round drew hints from a state
|
|
81
|
+
// file that never named them.
|
|
82
|
+
readonly property bool keyHints: root.state.keyHints === true
|
|
83
|
+
|
|
84
|
+
// Code icons default on; only a saved boolean false disables them, matching schema healing.
|
|
85
|
+
readonly property bool langMarks: root.state.langMarks !== false
|
|
86
|
+
function toggleLangMarks() { root.changeKey("langMarks", !root.langMarks) }
|
|
87
|
+
|
|
88
|
+
// Absent reads off, the way keyHints beside it does.
|
|
89
|
+
readonly property bool highlightToday: root.state.highlightToday === true
|
|
90
|
+
|
|
91
|
+
// Settings > View > Open folders while dragging: absent reads off, like highlightToday.
|
|
92
|
+
readonly property bool openFoldersWhileDragging: root.state.openFoldersWhileDragging === true
|
|
93
|
+
// Rows compare one number and never build a Date.
|
|
94
|
+
property double todayStart: Format.dayStart(Date.now())
|
|
95
|
+
|
|
96
|
+
// One timer steps to the next local midnight while the switch is on, in minute steps so a suspend still lands the day within a minute of resume; rows never tick.
|
|
97
|
+
property var midnightTimer: Timer {
|
|
98
|
+
repeat: true
|
|
99
|
+
running: root.highlightToday
|
|
100
|
+
interval: Math.min(Format.MINUTE_MS, Format.msUntilMidnight(Date.now()))
|
|
101
|
+
onTriggered: {
|
|
102
|
+
root.todayStart = Format.dayStart(Date.now())
|
|
103
|
+
root.midnightTimer.interval = Math.min(Format.MINUTE_MS, Format.msUntilMidnight(Date.now()))
|
|
104
|
+
root.midnightTimer.running = root.highlightToday
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
onHighlightTodayChanged: {
|
|
108
|
+
root.todayStart = Format.dayStart(Date.now())
|
|
109
|
+
root.midnightTimer.interval = Math.min(Format.MINUTE_MS, Format.msUntilMidnight(Date.now()))
|
|
110
|
+
root.midnightTimer.running = root.highlightToday
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The listing's view, "view" in src/uischema.rs, which ui/Pane.qml draws for the first frame so
|
|
114
|
+
// the view the window was left on is the view the next launch opens on. A word this build cannot
|
|
115
|
+
// draw reads as the list, which covers both a hand edit made while the window is up and the
|
|
116
|
+
// stored "dual", a window shape and not a view a pane can be in. PR 97, DouglasdeMoura.
|
|
117
|
+
readonly property string view: Settings.contains(["list", "columns", "grid"], root.state.view)
|
|
118
|
+
? root.state.view : "list"
|
|
119
|
+
// Per-folder views stay separate from the fallback above and off until opted in, so an existing ui.json keeps its global view.
|
|
120
|
+
readonly property bool rememberViewPerFolder: root.state.rememberViewPerFolder === true
|
|
121
|
+
readonly property var folderViews: root.state.folderViews || ({})
|
|
122
|
+
// The chooser's own last-used view (`pickerView` in src/uischema.rs); an undrawable word reads as the list.
|
|
123
|
+
readonly property string pickerView: Settings.contains(["list", "grid"], root.state.pickerView)
|
|
124
|
+
? root.state.pickerView : "list"
|
|
125
|
+
|
|
126
|
+
// The Keys section's four-value chooser over the one generated key table, falling back to its
|
|
127
|
+
// first value, Default, which is what SettingsKeys.html says a missing or unknown name means.
|
|
128
|
+
readonly property string keysPreset: Settings.contains(Settings.PRESETS, root.state.keys)
|
|
129
|
+
? root.state.keys : Settings.PRESETS[0]
|
|
130
|
+
|
|
131
|
+
// The generated table holds the live preset, so this is the whole wire between the stored value
|
|
132
|
+
// and every lookup: it fires on load and on a settings change alike, and rebinds in this process.
|
|
133
|
+
onKeysPresetChanged: Keymap.setPreset(root.keysPreset)
|
|
134
|
+
|
|
135
|
+
// A whole top-level ui.json key this window changed: the same value goes into the document and
|
|
136
|
+
// into the patch, because nothing else lives under it.
|
|
137
|
+
function changeKey(key, value) {
|
|
138
|
+
root.owe(key, UiState.withKey(root.state, key, value), UiState.withKey(root.unsaved, key, value))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// One leaf inside a group. The leaf merges into whatever else the group holds so the document
|
|
142
|
+
// keeps a sub-key a newer Flea left there, and the patch carries the leaf ALONE: a sub-key this
|
|
143
|
+
// Flea has no rule for is one src/uistate.rs refuses, and it refuses the whole patch with it.
|
|
144
|
+
function changeLeaf(key, leaf) {
|
|
145
|
+
root.owe(key, UiState.withGroup(root.state, key, leaf), UiState.withGroup(root.unsaved, key, leaf))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Changed entries of a map key: the document draws the whole map, the patch owes those entries alone.
|
|
149
|
+
function changeMapEntries(key, entries, full) {
|
|
150
|
+
root.owe(key, UiState.withKey(root.state, key, full), UiState.withGroup(root.unsaved, key, entries))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Both writers' last move: hold the new document, and when the named key is not the value it
|
|
154
|
+
// already held, owe the state file what changed and ask for a write. A setter that lands the
|
|
155
|
+
// value already on screen owes nothing, so a chord clamped at the end of its range writes nothing.
|
|
156
|
+
function owe(key, next, owed) {
|
|
157
|
+
var before = JSON.stringify(root.state[key])
|
|
158
|
+
if (JSON.stringify(next[key]) === before)
|
|
159
|
+
return
|
|
160
|
+
root.state = next
|
|
161
|
+
root.unsaved = owed
|
|
162
|
+
root.saveStatus = "Saving…"
|
|
163
|
+
root.save()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// RailAdditions rule 4: ctrl-b remembers the rail the way the view is remembered, so this is a
|
|
167
|
+
// state and not a setting; the width rule in ui/Pane.qml hides it without writing anything.
|
|
168
|
+
readonly property bool railHidden: (root.state.places || ({})).rail === "hidden"
|
|
169
|
+
// Directive 74: hiding on a narrow window is its own switch and it ships off, so the rail stays
|
|
170
|
+
// where it is at every width until somebody asks for the other behaviour.
|
|
171
|
+
readonly property bool railAutoHide: (root.state.places || ({})).autoHide === true
|
|
172
|
+
function toggleRail() { root.changeLeaf("places", { rail: root.railHidden ? "shown" : "hidden" }) }
|
|
173
|
+
|
|
174
|
+
readonly property var preview: root.state.preview || ({})
|
|
175
|
+
readonly property bool previewColumn: root.preview.column !== false
|
|
176
|
+
readonly property bool previewAutomatic: root.preview.loadOn !== "manual"
|
|
177
|
+
// `previewFacts` in src/uischema.rs: the tall kinds' one compact line, or the full table `i` opens.
|
|
178
|
+
readonly property string previewFacts: root.state.previewFacts === "full" ? "full" : "compact"
|
|
179
|
+
readonly property string thumbnailMode: root.preview.thumbnails || "media"
|
|
180
|
+
readonly property string thumbnailSize: root.preview.thumbSize || "medium"
|
|
181
|
+
// Huge 192 and Largest 256 decode nothing new: the shared cache already holds 256.
|
|
182
|
+
readonly property int thumbnailPixels: Density.thumbPixelsFor(root.thumbnailSize)
|
|
183
|
+
readonly property bool ctrlZoom: root.preview.ctrlZoom !== false
|
|
184
|
+
// `preview.volume` in src/uischema.rs, 0 to 100: the level ui/MediaSound.qml plays every player at, 100 being full scale.
|
|
185
|
+
readonly property int mediaVolume: root.preview.volume !== undefined ? root.preview.volume : 100
|
|
186
|
+
function setMediaVolume(level) { root.changeLeaf("preview", { volume: level }) }
|
|
187
|
+
// `preview.width` in src/uischema.rs: the Columns view's last column as a whole percent of the area, 0 for the default share.
|
|
188
|
+
readonly property int previewWidth: root.preview.width || 0
|
|
189
|
+
function setPreviewWidth(percent) { root.changeLeaf("preview", { width: percent }) }
|
|
190
|
+
// Quick Look's w: long lines wrap in a code or text file; off until pressed, and remembered.
|
|
191
|
+
readonly property bool previewWrap: root.preview.wrap === true
|
|
192
|
+
readonly property string density: root.state.density || "compact"
|
|
193
|
+
readonly property string addressBar: root.state.addressBar || "breadcrumb"
|
|
194
|
+
readonly property bool hyprlandIcons: root.display.hyprlandIcons === true
|
|
195
|
+
property string saveStatus: "Saved"
|
|
196
|
+
|
|
197
|
+
// Settings > View > Opening. ui/js/Startup.js is what turns these into a path; nothing else reads
|
|
198
|
+
// them, so the panel and the two openers can never disagree about where a window or a tab begins.
|
|
199
|
+
readonly property string startFolder: root.state.startFolder || ""
|
|
200
|
+
|
|
201
|
+
// Settings > Places > Trash. The sweep reads both; ui/TrashHost.qml is its only driver.
|
|
202
|
+
readonly property bool trashAutoEmpty: root.state.trashAutoEmpty === true
|
|
203
|
+
readonly property int trashSweptOn: root.state.trashSweptOn || 0
|
|
204
|
+
|
|
205
|
+
// Settings > About's "Check automatically", `updates.autoCheck` in src/uischema.rs, on until switched off.
|
|
206
|
+
readonly property bool updateAutoCheck: (root.state.updates || ({})).autoCheck !== false
|
|
207
|
+
|
|
208
|
+
// Only a successful sweep records its day, so failures retry on the next launch.
|
|
209
|
+
function recordTrashSweep(day) {
|
|
210
|
+
root.changeKey("trashSweptOn", day)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Choosing the panel's current folder also selects the folder start mode.
|
|
214
|
+
function setStartFolder(path) {
|
|
215
|
+
root.changeKey("startIn", "folder")
|
|
216
|
+
root.changeKey("startFolder", String(path || ""))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Navigation owes only changed keys, then writes the last folder and ordered tab strip together.
|
|
220
|
+
function rememberNavigation(path, tabs) {
|
|
221
|
+
var next = root.state
|
|
222
|
+
var owed = root.unsaved
|
|
223
|
+
var values = { lastPath: String(path || ""), lastTabs: tabs }
|
|
224
|
+
for (var key in values) {
|
|
225
|
+
if (JSON.stringify(next[key]) === JSON.stringify(values[key])) continue
|
|
226
|
+
next = UiState.withKey(next, key, values[key])
|
|
227
|
+
owed = UiState.withKey(owed, key, values[key])
|
|
228
|
+
}
|
|
229
|
+
if (next === root.state) return
|
|
230
|
+
root.state = next
|
|
231
|
+
root.unsaved = owed
|
|
232
|
+
root.saveStatus = "Saving…"
|
|
233
|
+
root.save()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Setting ids name either one top-level key or one leaf of an existing group.
|
|
237
|
+
function changeSetting(id, value) {
|
|
238
|
+
// The rail is remembered as a word, so its own Places row writes that word and not a boolean.
|
|
239
|
+
if (id === "places.rail") { root.changeLeaf("places", { rail: value ? "shown" : "hidden" }); return }
|
|
240
|
+
var parts = id.split(".")
|
|
241
|
+
if (parts.length === 1) {
|
|
242
|
+
root.changeKey(id, value)
|
|
243
|
+
} else {
|
|
244
|
+
var leaf = {}
|
|
245
|
+
leaf[parts[1]] = value
|
|
246
|
+
root.changeLeaf(parts[0], leaf)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// The Display controls and text-size chords share these writers.
|
|
251
|
+
function setTextSize(next) {
|
|
252
|
+
root.changeLeaf("display", { textSize: TextSize.parse(next) })
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function followTextSize() {
|
|
256
|
+
root.setTextSize(TextSize.follow())
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function stepTextSize(direction) {
|
|
260
|
+
root.setTextSize(TextSize.stepped(root.textSize, root.omarchyBase, direction))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function toggleTextFollow() {
|
|
264
|
+
root.setTextSize(TextSize.following(root.textSize)
|
|
265
|
+
? TextSize.pin(root.textSize, root.omarchyBase) : TextSize.follow())
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// The Menus section's own two writers. Both write the hidden set alone, because a group's master is Settings.masterState of that set rather than a value of its own.
|
|
269
|
+
function setMenuHidden(hidden) {
|
|
270
|
+
root.changeLeaf("menu", { hidden: hidden.filter(function (id) { return id !== "shelf" }) })
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function toggleMenuAction(id) {
|
|
274
|
+
if (id === "shelf") { root.changeLeaf("shelf", { enabled: !root.shelfEnabled }); return }
|
|
275
|
+
root.setMenuHidden(Settings.toggleId(root.menuHidden, id))
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// A master that turns its whole group on turns the shelf on with it, and the same the other way.
|
|
279
|
+
function toggleMenuGroup(ids) {
|
|
280
|
+
if (ids.indexOf("shelf") >= 0)
|
|
281
|
+
root.changeLeaf("shelf", { enabled: Settings.masterState(root.menuHidden, ids) !== "all" })
|
|
282
|
+
root.setMenuHidden(Settings.toggleMaster(root.menuHidden, ids))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function toggleKeyHints() {
|
|
286
|
+
root.changeKey("keyHints", !root.keyHints)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function setKeysPreset(name) {
|
|
290
|
+
root.changeKey("keys", name)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Flipped by ui/Pane.qml's onChosen, when a header-menu row answers "col:<key>".
|
|
294
|
+
function toggleColumn(key) {
|
|
295
|
+
var shown = root.columns.slice()
|
|
296
|
+
var at = shown.indexOf(key)
|
|
297
|
+
if (at >= 0)
|
|
298
|
+
shown.splice(at, 1)
|
|
299
|
+
else
|
|
300
|
+
shown.push(key)
|
|
301
|
+
root.changeKey("columns", shown)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// A patch flea refused, or a state file it could not write. The pane turns it into the status
|
|
305
|
+
// bar's one sentence: the change is on screen and the file does not have it.
|
|
306
|
+
signal saveFailed()
|
|
307
|
+
signal favouritesReadFailed(string message)
|
|
308
|
+
property string favouritesReadError: ""
|
|
309
|
+
|
|
310
|
+
// The settings on screen are not the ones on disk: main() left a ui.json it cannot read as a
|
|
311
|
+
// JSON object exactly as the operator wrote it, or the read below failed outright. Either way
|
|
312
|
+
// what is drawn is the shipped defaults, and ui/PaneWire.qml is where that is said once. Only
|
|
313
|
+
// ever set true, because the read that would clear it is the one that could not be taken.
|
|
314
|
+
property bool unreadable: false
|
|
315
|
+
property bool initialReadComplete: false
|
|
316
|
+
|
|
317
|
+
// What this window has changed and no write has landed for yet, in the shape of a ui.json patch.
|
|
318
|
+
// A write that lands takes its own settings out of it leaf by leaf, so a refused one keeps its
|
|
319
|
+
// settings for the next patch to carry and a stored one is never carried again.
|
|
320
|
+
property var unsaved: ({})
|
|
321
|
+
|
|
322
|
+
// ui/js/UiState.js's book: the newest patch a writer landed, what the running writer carries,
|
|
323
|
+
// what waits behind it. A save that would change nothing writes nothing.
|
|
324
|
+
property var writeBook: UiState.book()
|
|
325
|
+
|
|
326
|
+
// Only what this window has changed, because src/uistate.rs merges a patch key by key: a key
|
|
327
|
+
// left out is one the file keeps, so saving a text size here cannot write this window's own read
|
|
328
|
+
// of `keys` over a change another window or the CLI made after that read. It is every change no
|
|
329
|
+
// writer has stored yet rather than the newest one alone, so a refused patch's settings ride out
|
|
330
|
+
// again on the next one; a stored patch's are gone from it before the next one is built.
|
|
331
|
+
function patch() {
|
|
332
|
+
return JSON.stringify(root.unsaved)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function save() {
|
|
336
|
+
root.writeRevision += 1
|
|
337
|
+
var next = UiState.asked(root.writeBook, root.patch())
|
|
338
|
+
root.writeBook = next
|
|
339
|
+
if (next.start.length > 0)
|
|
340
|
+
root.run(next.start)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function run(patch) {
|
|
344
|
+
patcher.command = [Quickshell.env("FLEA_BIN") || "flea", "--ui-state", patch]
|
|
345
|
+
patcher.running = true
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// The read is taken here and not in the FileView's onLoaded, which was measured on the box
|
|
349
|
+
// arriving after the first property read; blockLoading is what makes text() answer inside this
|
|
350
|
+
// call, so the stored columns are in the first frame instead of replacing it.
|
|
351
|
+
Component.onCompleted: { root.load(stateFile.text()); root.initialReadComplete = true }
|
|
352
|
+
|
|
353
|
+
function load(text) {
|
|
354
|
+
var read = UiState.fromFile(text)
|
|
355
|
+
root.state = read.state
|
|
356
|
+
if (read.unreadable)
|
|
357
|
+
root.unreadable = true
|
|
358
|
+
}
|
|
359
|
+
function syncFavourites(text) {
|
|
360
|
+
var read = UiState.refreshedFavourites(root.state, text)
|
|
361
|
+
if (read.error && read.error !== root.favouritesReadError) root.favouritesReadFailed(read.error)
|
|
362
|
+
root.favouritesReadError = read.error
|
|
363
|
+
if (read.state !== root.state) root.state = read.state
|
|
364
|
+
return read.error.length === 0
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// A change another window saved: the settled document with no argument, merged and validated, writes nothing.
|
|
368
|
+
property string settleMode: ""
|
|
369
|
+
// Local saves and acknowledgements invalidate older snapshots, even after their owed keys clear.
|
|
370
|
+
property double writeRevision: 0
|
|
371
|
+
property double settleRevision: 0
|
|
372
|
+
property bool settleDirty: false
|
|
373
|
+
property string pruneInflight: ""
|
|
374
|
+
property string pruneQueued: ""
|
|
375
|
+
property var settleAnswer: ({})
|
|
376
|
+
function applyShared(text) {
|
|
377
|
+
if (!UiState.parsesAsObject(text))
|
|
378
|
+
return
|
|
379
|
+
if (UiState.settleBusy(settler.running, root.settleMode, root.settleAnswer)) {
|
|
380
|
+
root.settleDirty = true
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
root.settleStart("apply")
|
|
384
|
+
}
|
|
385
|
+
// One starter for both settle kinds, so a prune never inherits a stale exit or text.
|
|
386
|
+
function settleStart(mode) {
|
|
387
|
+
// A half-landed settle is still pending, so starting over it would lose the half already here.
|
|
388
|
+
if (UiState.settleBusy(settler.running, root.settleMode, root.settleAnswer))
|
|
389
|
+
return
|
|
390
|
+
root.settleMode = mode
|
|
391
|
+
root.settleRevision = root.writeRevision
|
|
392
|
+
root.settleAnswer = {}
|
|
393
|
+
settler.command = [Quickshell.env("FLEA_BIN") || "flea", "--ui-state"]
|
|
394
|
+
settler.running = true
|
|
395
|
+
}
|
|
396
|
+
// A settler's exit and its collected text land in either order, so only a whole answer reaches settleDone.
|
|
397
|
+
function settleLanded(half) {
|
|
398
|
+
if (UiState.whole(root.settleAnswer))
|
|
399
|
+
return
|
|
400
|
+
root.settleAnswer = UiState.landed(root.settleAnswer, half)
|
|
401
|
+
if (!UiState.whole(root.settleAnswer))
|
|
402
|
+
return
|
|
403
|
+
root.settleDone(root.settleAnswer.code, root.settleAnswer.text)
|
|
404
|
+
}
|
|
405
|
+
// A settle's end spends a queued prune before a dirty re-read; the dirty flag survives the prune so the re-read still runs after it.
|
|
406
|
+
function settleNext() {
|
|
407
|
+
// Keep the prune queued until the process is idle and both reply halves have landed.
|
|
408
|
+
if (UiState.settleBusy(settler.running, root.settleMode, root.settleAnswer))
|
|
409
|
+
return
|
|
410
|
+
var next = UiState.settleNext(root.settleDirty, root.pruneQueued)
|
|
411
|
+
if (next === "prune") {
|
|
412
|
+
var queued = root.pruneQueued
|
|
413
|
+
root.pruneQueued = ""
|
|
414
|
+
root.pruneInflight = queued
|
|
415
|
+
root.settleStart("prune")
|
|
416
|
+
} else if (next === "apply") {
|
|
417
|
+
root.settleDirty = false
|
|
418
|
+
root.applyShared(stateFile.text())
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function settleDone(exitCode, out) {
|
|
422
|
+
var mode = root.settleMode
|
|
423
|
+
root.settleMode = ""
|
|
424
|
+
if (mode === "prune") {
|
|
425
|
+
if (exitCode !== 0) { root.pruneFailed(); return }
|
|
426
|
+
var settled = null
|
|
427
|
+
try {
|
|
428
|
+
settled = JSON.parse(out)
|
|
429
|
+
} catch (e) {
|
|
430
|
+
root.pruneFailed()
|
|
431
|
+
return
|
|
432
|
+
}
|
|
433
|
+
if (!UiState.parsesAsObject(stateFile.text())) { root.pruneFailed(); return }
|
|
434
|
+
root.unsaved = UiState.dropInvalid(root.unsaved, root.pruneInflight)
|
|
435
|
+
root.state = UiState.revertedState(root.state, root.pruneInflight, settled)
|
|
436
|
+
root.finishPrune(false)
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
if (root.settleRevision !== root.writeRevision) {
|
|
440
|
+
root.settleDirty = true
|
|
441
|
+
root.settleNext()
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
if (exitCode !== 0) { root.settleNext(); return }
|
|
445
|
+
if (!UiState.parsesAsObject(out)) { root.settleNext(); return }
|
|
446
|
+
if (!UiState.parsesAsObject(stateFile.text())) { root.settleNext(); return }
|
|
447
|
+
var merged = UiState.applyExternal(root.state, root.unsaved, out)
|
|
448
|
+
if (merged.changed)
|
|
449
|
+
root.state = merged.state
|
|
450
|
+
root.syncFavourites(stateFile.text())
|
|
451
|
+
root.settleNext()
|
|
452
|
+
}
|
|
453
|
+
// One tail for both prune endings: end the settle, then save what is still owed.
|
|
454
|
+
function finishPrune(failed) {
|
|
455
|
+
root.pruneInflight = ""
|
|
456
|
+
root.writeBook = { saved: root.writeBook.saved, inflight: "", pending: "", start: "" }
|
|
457
|
+
if (failed)
|
|
458
|
+
root.saveFailed()
|
|
459
|
+
root.settleNext()
|
|
460
|
+
if (JSON.stringify(root.unsaved) !== "{}") {
|
|
461
|
+
root.saveStatus = "Saving…"
|
|
462
|
+
root.save()
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// A prune settle that never answered still drops what the schema refused, so no save strands behind a phantom writer.
|
|
466
|
+
function pruneFailed() {
|
|
467
|
+
root.unsaved = UiState.dropInvalid(root.unsaved, root.pruneInflight)
|
|
468
|
+
root.finishPrune(true)
|
|
469
|
+
}
|
|
470
|
+
function refreshFavourites() {
|
|
471
|
+
stateFile.reload()
|
|
472
|
+
// blockLoading covers only the first read; a reload otherwise returns the previous document.
|
|
473
|
+
stateFile.waitForJob()
|
|
474
|
+
if (!stateFile.loaded || root.favouritesReadError.length > 0) return false
|
|
475
|
+
var text = stateFile.text()
|
|
476
|
+
return root.syncFavourites(text)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// blockLoading, because the first list draws from this: an async read would paint one column
|
|
480
|
+
// set and correct it. printErrors off, because a missing file is what a first launch looks like.
|
|
481
|
+
property var store: FileView {
|
|
482
|
+
id: stateFile
|
|
483
|
+
path: (Quickshell.env("XDG_STATE_HOME") && Quickshell.env("XDG_STATE_HOME").length > 0
|
|
484
|
+
? Quickshell.env("XDG_STATE_HOME") : Quickshell.env("HOME") + "/.local/state") + "/flea/ui.json"
|
|
485
|
+
blockLoading: true
|
|
486
|
+
watchChanges: true
|
|
487
|
+
onFileChanged: reload()
|
|
488
|
+
onLoaded: {
|
|
489
|
+
root.favouritesReadError = ""
|
|
490
|
+
if (root.initialReadComplete) {
|
|
491
|
+
root.applyShared(text())
|
|
492
|
+
}
|
|
493
|
+
root.syncFavourites(text())
|
|
494
|
+
}
|
|
495
|
+
printErrors: false
|
|
496
|
+
// A file that is not there is a first launch and says nothing; anything else is a file this
|
|
497
|
+
// window could not read and is about to draw the defaults over, which is the unchecked-read
|
|
498
|
+
// defect ui/NetworkDialog.qml already carried once and must not be repeated here.
|
|
499
|
+
onLoadFailed: function (error) {
|
|
500
|
+
if (error === FileViewError.FileNotFound && !root.initialReadComplete) return
|
|
501
|
+
if (!root.initialReadComplete) root.unreadable = true
|
|
502
|
+
root.favouritesReadError = UiState.FAVOURITES_UNREAD
|
|
503
|
+
root.favouritesReadFailed(root.favouritesReadError)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// The writer answered, with its own status or with 2 for one that never started: the same refusal
|
|
508
|
+
// to the pane and the same retry to the book, because neither reached the file. A refusal never
|
|
509
|
+
// leaves a key owed forever: the owed keys the settled document disagrees with are dropped once,
|
|
510
|
+
// said once in the status line, so a later valid write applies again in the same process.
|
|
511
|
+
function wrote(exitCode) {
|
|
512
|
+
// Taken out before the patch below is built, so a writer queued behind this one launches with
|
|
513
|
+
// what is still owed and not with the settings this one has just stored.
|
|
514
|
+
root.saveStatus = exitCode === 0 ? "Saved"
|
|
515
|
+
: "Could not save settings · changes apply to this session only"
|
|
516
|
+
if (exitCode === 0) {
|
|
517
|
+
root.writeRevision += 1
|
|
518
|
+
root.unsaved = UiState.acknowledged(root.unsaved, root.writeBook.inflight)
|
|
519
|
+
}
|
|
520
|
+
var failedPatch = root.writeBook.inflight
|
|
521
|
+
var next = UiState.exited(root.writeBook, exitCode, root.patch())
|
|
522
|
+
root.writeBook = next
|
|
523
|
+
if (next.failed)
|
|
524
|
+
root.saveFailed()
|
|
525
|
+
if (exitCode !== 0 && UiState.isPatchInvalid(failedPatch)) {
|
|
526
|
+
// A refused patch prunes behind a pending settle instead of hijacking that settle's mode.
|
|
527
|
+
var ask = UiState.pruneAsk(UiState.settleBusy(settler.running, root.settleMode, root.settleAnswer), failedPatch)
|
|
528
|
+
if (ask.queue.length > 0) {
|
|
529
|
+
root.pruneQueued = ask.queue
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
root.pruneInflight = ask.start
|
|
533
|
+
root.settleStart("prune")
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
if (next.start.length > 0)
|
|
537
|
+
root.run(next.start)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
property var writer: Process {
|
|
541
|
+
id: patcher
|
|
542
|
+
onExited: function (exitCode, exitStatus) { root.wrote(exitCode) }
|
|
543
|
+
// Measured on Quickshell 0.3.1: a Process that cannot start its program emits no exited at
|
|
544
|
+
// all, only running going false, and a real exit clears the book before its own false
|
|
545
|
+
// arrives, so this fires for the writer that never ran and never for one that did.
|
|
546
|
+
onRunningChanged: {
|
|
547
|
+
if (!patcher.running && root.writeBook.inflight.length > 0)
|
|
548
|
+
root.wrote(2)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
property var settler: Process {
|
|
553
|
+
id: settler
|
|
554
|
+
stdout: StdioCollector { id: settleOut; waitForEnd: true; onStreamFinished: root.settleLanded({ text: settleOut.text }) }
|
|
555
|
+
onExited: function (exitCode, exitStatus) { root.settleLanded({ code: exitCode }) }
|
|
556
|
+
// A settler that never started raises no exited, only running going false; the joined halves tell it from a real exit the way the writer's own rule does.
|
|
557
|
+
onRunningChanged: {
|
|
558
|
+
if (root.settleMode.length > 0 && UiState.neverRan(root.settleAnswer, settler.running))
|
|
559
|
+
root.settleLanded(UiState.NEVER_RAN)
|
|
560
|
+
// onExited may land both halves while running is true; the idle transition resumes its queued work.
|
|
561
|
+
if (!settler.running && root.settleMode.length === 0)
|
|
562
|
+
root.settleNext()
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|