buninu-browser 0.3.0 → 0.5.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 +40 -4
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +152 -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 +130 -0
- package/frontends/wayland/input.js +75 -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 +162 -0
- package/index.js +28 -1
- package/lib/audio/shared-connection.js +31 -0
- package/lib/cdp-server.js +15 -2
- 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 +54 -12
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +6 -1
- package/lib/qml/README.md +1427 -0
- package/lib/qml/builtins.js +771 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/canvas.js +76 -0
- package/lib/qml/document.js +1199 -0
- package/lib/qml/fonts.js +69 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +352 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +521 -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 +182 -0
- package/lib/qml/runtime.js +3603 -0
- package/lib/qml/shader/fragment.js +83 -0
- package/lib/qml/shader/qsb.js +27 -0
- package/lib/qml/shader/renderer.js +39 -0
- package/lib/qml/shader/runtime.js +61 -0
- package/lib/qml/shapes.js +251 -0
- package/lib/qml/shortcuts.js +65 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +126 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/window-constraints.js +16 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +16 -1
- package/lib/renderer/canvas-2d.js +29 -0
- package/lib/renderer/document-selection.js +19 -0
- package/lib/renderer/host.js +45 -17
- package/lib/renderer/main.js +21 -6
- package/lib/renderer/page-realm.js +20 -10
- package/lib/renderer/page-renderer.js +497 -42
- package/lib/renderer/pointer-move.js +5 -3
- package/lib/renderer/render-interval.js +9 -0
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +10 -4
- package/package.json +1 -1
- package/test/audio-connection-lifetime.test.js +46 -0
- package/test/canvas-paint.test.js +8 -0
- package/test/clipboard.test.js +19 -0
- package/test/computed-style.test.js +12 -0
- package/test/document-selection.test.js +27 -0
- package/test/double-click.test.js +85 -0
- package/test/fixtures/canvas-damage.js +40 -0
- package/test/fixtures/fake-renderer.js +1 -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/omastorm-helpers/Dockerfile +35 -0
- package/test/fixtures/omastorm-helpers/check.mjs +56 -0
- package/test/fixtures/omastorm-helpers/launch.mjs +24 -0
- package/test/fixtures/omastorm-helpers/prepare.mjs +17 -0
- package/test/fixtures/omastorm-helpers/runtime.mjs +32 -0
- package/test/fixtures/pointer-paint-batching.js +23 -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-fonts/main.qml +10 -0
- package/test/fixtures/qml-locale/check.mjs +6 -0
- package/test/fixtures/qml-locale/main.qml +8 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/fixtures/qml-shader/grab.qml +14 -0
- package/test/fixtures/qml-shader/gradient.frag +5 -0
- package/test/fixtures/qml-shader/main.qml +14 -0
- package/test/fixtures/qml-shader/texture-grab.qml +25 -0
- package/test/fixtures/qml-shader/texture-size.frag +5 -0
- package/test/fixtures/qml-shader-check.mjs +24 -0
- package/test/fixtures/title-no-paint.js +20 -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-double-click.html +21 -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-mouse-buttons.html +18 -0
- package/test/manual-omastorm-engine.mjs +19 -0
- package/test/manual-omastorm-shaders.mjs +28 -0
- package/test/manual-omastorm-software.mjs +25 -0
- package/test/manual-pointer-scroll.html +43 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/pointer-drag.test.js +104 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-canvas.test.js +21 -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 +95 -0
- package/test/qml-controls-window.test.js +182 -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-fonts.test.js +31 -0
- package/test/qml-grid-multiline.test.js +48 -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-locale.test.js +45 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-nonvisual-visible.test.js +19 -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-shader-runtime.test.js +6 -0
- package/test/qml-shortcuts.test.js +39 -0
- package/test/qml-socket.test.js +60 -0
- package/test/qml-software-shader.test.js +55 -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-window-update.test.js +36 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/render-interval.test.js +33 -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/title-no-paint.test.js +6 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-scroll.test.js +40 -0
- package/test/wayland-urlbar.test.js +61 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The Omarchy theme palette in colors.toml, parsed and queried. Pure, with no QML imports, so
|
|
2
|
+
// tests/js/palette.js can run it under qml6 with no window.
|
|
3
|
+
|
|
4
|
+
// Sample input: cyan = "#27a6a2" # the ANSI ring
|
|
5
|
+
function parse(body) {
|
|
6
|
+
var found = {};
|
|
7
|
+
var lines = String(body || "").split("\n");
|
|
8
|
+
for (var i = 0; i < lines.length; i++) {
|
|
9
|
+
var kv = lines[i].match(/^\s*([A-Za-z0-9_-]+)\s*=\s*["']?(#[0-9A-Fa-f]{6})/);
|
|
10
|
+
if (kv)
|
|
11
|
+
found[kv[1]] = kv[2];
|
|
12
|
+
}
|
|
13
|
+
return found;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ThemeRoles.html's surface ladder: the chrome plane, then background as the neutral fallback when
|
|
17
|
+
// it is absent, then selection for the alacritty-derived file that emits neither ladder key at all.
|
|
18
|
+
var SURFACE_KEYS = ["dark_background", "background", "selection"];
|
|
19
|
+
|
|
20
|
+
// The first key the theme actually set wins, and a role no theme models keeps Flea's own colour.
|
|
21
|
+
function pick(found, keys, fallback) {
|
|
22
|
+
for (var i = 0; i < keys.length; i++) {
|
|
23
|
+
if (found[keys[i]])
|
|
24
|
+
return found[keys[i]];
|
|
25
|
+
}
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// A theme need not set every role and pick() already falls back per role, so the only question a
|
|
30
|
+
// readiness flag can answer honestly is whether the file yielded any colour at all.
|
|
31
|
+
function isPalette(found) {
|
|
32
|
+
return Object.keys(found).length > 0;
|
|
33
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "Mounts.js" as Mounts
|
|
4
|
+
.import "UserDirs.js" as UserDirs
|
|
5
|
+
|
|
6
|
+
// Sample input: XDG_DOWNLOAD_DIR="$HOME/Downloads"
|
|
7
|
+
function userDirs(body, home) {
|
|
8
|
+
return UserDirs.userDirs(body, home)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Sample input: file:///home/gm/Downloads Downloads
|
|
12
|
+
function bookmarks(body) {
|
|
13
|
+
var out = []
|
|
14
|
+
var lines = String(body || "").split("\n")
|
|
15
|
+
for (var i = 0; i < lines.length; i++) {
|
|
16
|
+
var line = lines[i].trim()
|
|
17
|
+
// corner: a bookmark may be smb:// or sftp://, which is a Location and not a Favorite.
|
|
18
|
+
if (line.indexOf("file://") !== 0)
|
|
19
|
+
continue
|
|
20
|
+
var space = line.indexOf(" ")
|
|
21
|
+
var uri = space < 0 ? line : line.substring(0, space)
|
|
22
|
+
var label = space < 0 ? "" : line.substring(space + 1).trim()
|
|
23
|
+
var path = Mounts.decodePath(uri.substring("file://".length))
|
|
24
|
+
out.push({ path: path, label: label.length > 0 ? label : leaf(path) })
|
|
25
|
+
}
|
|
26
|
+
return out
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The FAVORITES rail's rows, Home first and always, then XDG dirs and bookmarked places in file
|
|
30
|
+
// order, a path seen twice keeping its first position. glyphFor resolves each row's mark so the
|
|
31
|
+
// rail's Icons import stays the rail's own business; see ui/Sidebar.qml's rebuild.
|
|
32
|
+
function favorites(home, dirsText, marksText, glyphFor) {
|
|
33
|
+
var favs = [{ path: home, label: "Home", group: "favorite", kind: "favorite", glyph: glyphFor("Home") }]
|
|
34
|
+
var seen = {}
|
|
35
|
+
seen[home] = true
|
|
36
|
+
var groups = [userDirs(dirsText, home), bookmarks(marksText)]
|
|
37
|
+
for (var g = 0; g < groups.length; g++) {
|
|
38
|
+
for (var i = 0; i < groups[g].length; i++) {
|
|
39
|
+
if (seen[groups[g][i].path])
|
|
40
|
+
continue
|
|
41
|
+
var e = groups[g][i]
|
|
42
|
+
seen[e.path] = true
|
|
43
|
+
favs.push({ path: e.path, label: e.label, group: "favorite", kind: "favorite", glyph: glyphFor(e.label) })
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return favs
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function leaf(path) {
|
|
50
|
+
var cut = path.lastIndexOf("/")
|
|
51
|
+
return cut < 0 ? path : path.substring(cut + 1)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Sample input: "smb://192.168.1.10/data NAS"; see AGENTS.md "Places.relabel" for the matching, duplicate and control-character rules.
|
|
55
|
+
function relabel(body, path, name) {
|
|
56
|
+
// A trust boundary: an embedded newline could otherwise split one bookmark into two lines.
|
|
57
|
+
var trimmed = String(name || "").replace(/[\r\n]/g, "").trim()
|
|
58
|
+
if (trimmed.length === 0)
|
|
59
|
+
return String(body || "")
|
|
60
|
+
var target = Mounts.normalize(path)
|
|
61
|
+
var lines = String(body || "").split("\n")
|
|
62
|
+
var found = false
|
|
63
|
+
for (var i = 0; i < lines.length; i++) {
|
|
64
|
+
// Read off the trimmed line the way Mounts.removeBookmark reads it, or an indented bookmark
|
|
65
|
+
// is one Remove can drop and this could only ever duplicate.
|
|
66
|
+
var line = lines[i].trim()
|
|
67
|
+
if (line.length === 0)
|
|
68
|
+
continue
|
|
69
|
+
var space = line.indexOf(" ")
|
|
70
|
+
var uri = space < 0 ? line : line.substring(0, space)
|
|
71
|
+
if (Mounts.normalize(uri) === target) {
|
|
72
|
+
lines[i] = uri + " " + trimmed
|
|
73
|
+
found = true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (found)
|
|
77
|
+
return lines.join("\n")
|
|
78
|
+
var out = String(body || "")
|
|
79
|
+
if (out.length > 0 && out.charAt(out.length - 1) !== "\n")
|
|
80
|
+
out += "\n"
|
|
81
|
+
return out + target + " " + trimmed + "\n"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Sample input: body as relabel's, oldUri the saved line being rewritten, newUri the address the
|
|
85
|
+
// form's Mounts-as line showed. Matched the way relabel matches, so a live mount's trailing slash
|
|
86
|
+
// still finds the saved line, and an empty oldUri matches nothing and appends instead. PR 21.
|
|
87
|
+
function replace(body, oldUri, newUri, label) {
|
|
88
|
+
var next = Mounts.normalize(newUri)
|
|
89
|
+
if (next.length === 0)
|
|
90
|
+
return String(body || "")
|
|
91
|
+
// The same trust boundary relabel names: an embedded newline would split one bookmark into two.
|
|
92
|
+
var name = String(label || "").replace(/[\r\n]/g, "").trim()
|
|
93
|
+
if (name.length === 0)
|
|
94
|
+
name = leaf(next)
|
|
95
|
+
var target = Mounts.normalize(oldUri)
|
|
96
|
+
var lines = String(body || "").split("\n")
|
|
97
|
+
var found = false
|
|
98
|
+
for (var i = 0; i < lines.length; i++) {
|
|
99
|
+
var line = lines[i].trim()
|
|
100
|
+
if (line.length === 0)
|
|
101
|
+
continue
|
|
102
|
+
var space = line.indexOf(" ")
|
|
103
|
+
var uri = space < 0 ? line : line.substring(0, space)
|
|
104
|
+
if (target.length > 0 && Mounts.normalize(uri) === target) {
|
|
105
|
+
lines[i] = next + " " + name
|
|
106
|
+
found = true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (found)
|
|
110
|
+
return lines.join("\n")
|
|
111
|
+
var out = String(body || "")
|
|
112
|
+
if (out.length > 0 && out.charAt(out.length - 1) !== "\n")
|
|
113
|
+
out += "\n"
|
|
114
|
+
return out + next + " " + name + "\n"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var WIDTH_STOPS = [160, 192, 224, 256]
|
|
118
|
+
function sidebarWidth(value) {
|
|
119
|
+
if (typeof value !== "number" || !isFinite(value)) return 192
|
|
120
|
+
var nearest = WIDTH_STOPS[0]
|
|
121
|
+
for (var i = 1; i < WIDTH_STOPS.length; i++) {
|
|
122
|
+
if (Math.abs(WIDTH_STOPS[i] - value) < Math.abs(nearest - value)) nearest = WIDTH_STOPS[i]
|
|
123
|
+
}
|
|
124
|
+
return nearest
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function recordError(record) {
|
|
128
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) return "invalid favorite record"
|
|
129
|
+
if (typeof record.label !== "string" || record.label.trim().length === 0) return "label needs visible text"
|
|
130
|
+
if (typeof record.path !== "string" || /[\u0000-\u001f\u007f]/.test(record.path)) return "invalid path"
|
|
131
|
+
if (record.path.charAt(0) === "/" || record.path === "~" || record.path.indexOf("~/") === 0) return ""
|
|
132
|
+
var schemes = ["smb://", "sftp://", "ftp://", "dav://", "davs://", "afp://", "nfs://", "file://"]
|
|
133
|
+
for (var i = 0; i < schemes.length; i++) {
|
|
134
|
+
if (record.path.indexOf(schemes[i]) === 0 && record.path.length > schemes[i].length) return ""
|
|
135
|
+
}
|
|
136
|
+
return "use a full path, a path that starts with ~/, or a network address"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function storedEntries(records, home) {
|
|
140
|
+
var out = []
|
|
141
|
+
for (var i = 0; Array.isArray(records) && i < records.length; i++) {
|
|
142
|
+
var record = records[i]
|
|
143
|
+
var error = recordError(record)
|
|
144
|
+
var label = record && typeof record.label === "string" ? record.label : JSON.stringify(record)
|
|
145
|
+
var path = record && typeof record.path === "string" ? record.path : ""
|
|
146
|
+
var resolved = path === "~" ? home : path.indexOf("~/") === 0 ? home + path.substring(1) : path
|
|
147
|
+
out.push({ label: label || "Invalid favorite", path: resolved, storedPath: path,
|
|
148
|
+
error: error, original: record, favouriteIndex: i, group: "favourite", kind: "favourite",
|
|
149
|
+
glyph: path.indexOf("://") >= 0 ? "network" : "folder" })
|
|
150
|
+
}
|
|
151
|
+
return out
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function homeEntries(home, dirsText, glyphFor) {
|
|
155
|
+
var entries = [{ label: "Home", path: home }].concat(userDirs(dirsText, home))
|
|
156
|
+
return entries.map(function (entry) {
|
|
157
|
+
return { label: entry.label, path: entry.path, group: "home", kind: "home", glyph: glyphFor(entry.label) }
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// The rail and query use the same validation, URI decoding and mount-first open.
|
|
162
|
+
function openEntry(entry, ports) {
|
|
163
|
+
if (!entry) return
|
|
164
|
+
if (entry.kind === "trash") { ports.trash(); return }
|
|
165
|
+
var error = entry.kind === "favourite" ? recordError(entry.original) : ""
|
|
166
|
+
if (error) { ports.message("Could not open %1 · %2".replace(/%([12])/g, function (m, n) { return n === "1" ? entry.label : error }), true); return }
|
|
167
|
+
if (entry.path.indexOf("://") >= 0 && entry.path.indexOf("file://") !== 0) {
|
|
168
|
+
var host = ports.networkHost()
|
|
169
|
+
if (host) host.openChildShare(entry.path, entry.label, ports.pane)
|
|
170
|
+
} else {
|
|
171
|
+
ports.opened(entry.path.indexOf("file://") === 0 ? Mounts.decodePath(entry.path.substring(7)) : entry.path)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function railIdentity(entry) {
|
|
176
|
+
if (!entry) return ""
|
|
177
|
+
if (entry.kind === "favourite") return JSON.stringify([entry.kind, entry.original])
|
|
178
|
+
return JSON.stringify([entry.group, entry.kind, Mounts.railKey(entry) || entry.device || entry.uri || entry.path])
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Duplicate originals have no stored ID; a changed multiplicity cannot safely identify the selected occurrence.
|
|
182
|
+
function railCursorAfter(before, after, index) {
|
|
183
|
+
if (before.length === 0 && index === 0) return 0
|
|
184
|
+
if (index < 0 || index >= before.length) return -1
|
|
185
|
+
var key = railIdentity(before[index]), oldCount = 0, occurrence = 0, matches = []
|
|
186
|
+
for (var i = 0; i < before.length; i++) {
|
|
187
|
+
if (railIdentity(before[i]) !== key) continue
|
|
188
|
+
oldCount++
|
|
189
|
+
if (i < index) occurrence++
|
|
190
|
+
}
|
|
191
|
+
for (var j = 0; j < after.length; j++) {
|
|
192
|
+
if (railIdentity(after[j]) === key) matches.push(j)
|
|
193
|
+
}
|
|
194
|
+
return matches.length === oldCount ? matches[occurrence] : -1
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Under gvfs only the mounted row opened under view.shareKey lights, never a folder prefix or the disk.
|
|
198
|
+
function heldBy(entries, dir, view) {
|
|
199
|
+
var list = entries || []
|
|
200
|
+
var at = view || ({ kind: "dir" })
|
|
201
|
+
var path = String(dir || "")
|
|
202
|
+
var gvfs = Mounts.underGvfs(path)
|
|
203
|
+
var best = -1, depth = -1
|
|
204
|
+
for (var i = 0; i < list.length; i++) {
|
|
205
|
+
var e = list[i]
|
|
206
|
+
if (at.kind !== "dir") {
|
|
207
|
+
if (e.kind === at.kind && (at.kind !== "tag" || e.tag === at.tag))
|
|
208
|
+
return i
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
if (e.kind === "recent" || e.kind === "trash" || e.kind === "tag" || e.error || e.mounted === false)
|
|
212
|
+
continue
|
|
213
|
+
if (gvfs) {
|
|
214
|
+
var key = String(at.shareKey || "")
|
|
215
|
+
if (key.length > 0 && !e.path && (Mounts.normalize(e.uri) === key || Mounts.normalize(e.mountUri) === key))
|
|
216
|
+
return i
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
if (typeof e.path !== "string" || e.path.length === 0)
|
|
220
|
+
continue
|
|
221
|
+
if ((e.path === "/" ? path.charAt(0) === "/" : Mounts.inside(path, e.path)) && e.path.length > depth) {
|
|
222
|
+
best = i
|
|
223
|
+
depth = e.path.length
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return best
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// pathAlias is the pane's remembered gvfs share (or null); its key is the share's normalized address.
|
|
230
|
+
function litView(trashOpened, recentMode, searchMode, answeredQuery, pathAlias) {
|
|
231
|
+
if (trashOpened) return { kind: "trash" }
|
|
232
|
+
if (String(recentMode || "").length > 0) return { kind: "recent" }
|
|
233
|
+
var query = String(answeredQuery || "")
|
|
234
|
+
if (String(searchMode || "").length > 0 && query.indexOf("tag:") === 0) return { kind: "tag", tag: query.substring(4) }
|
|
235
|
+
return { kind: "dir", shareKey: pathAlias ? String(pathAlias.key || "") : "" }
|
|
236
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// The Add-network-place form is a form over a gvfs URI, never a second mount system: a protocol
|
|
4
|
+
// picks the scheme, prefills the port and swaps the field set, and the Mounts-as line always shows
|
|
5
|
+
// the exact URI the dialog will hand to gio mount.
|
|
6
|
+
|
|
7
|
+
// The five the canvas draws, in the order it draws them.
|
|
8
|
+
var PROTOCOLS = ["SMB", "SFTP", "FTPS", "WebDAV", "NFS"]
|
|
9
|
+
|
|
10
|
+
// Which fields each protocol asks for. "path" is the label the canvas gives that row, which differs
|
|
11
|
+
// per protocol because the thing it names differs: a share, a remote path, an export.
|
|
12
|
+
var FIELDS = {
|
|
13
|
+
"SMB": { pathLabel: "Share", credentials: true, domain: true, tls: false },
|
|
14
|
+
"SFTP": { pathLabel: "Path", credentials: true, domain: false, tls: false },
|
|
15
|
+
"FTPS": { pathLabel: "Path", credentials: true, domain: false, tls: true },
|
|
16
|
+
"WebDAV": { pathLabel: "Path", credentials: true, domain: false, tls: true },
|
|
17
|
+
// NFS has no credentials at all: the export is authorised by the server, not by a password.
|
|
18
|
+
"NFS": { pathLabel: "Export", credentials: false, domain: false, tls: false }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The TLS box flips two schemes and defaults on, which is what the canvas draws ticked.
|
|
22
|
+
function scheme(protocol, tls) {
|
|
23
|
+
switch (protocol) {
|
|
24
|
+
case "SMB": return "smb"
|
|
25
|
+
case "SFTP": return "sftp"
|
|
26
|
+
case "FTPS": return tls ? "ftps" : "ftp"
|
|
27
|
+
case "WebDAV": return tls ? "davs" : "dav"
|
|
28
|
+
case "NFS": return "nfs"
|
|
29
|
+
}
|
|
30
|
+
return ""
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function fieldsFor(protocol) {
|
|
34
|
+
return FIELDS[protocol] || FIELDS["SMB"]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The port the form prefills. It belongs to the scheme, never to the protocol: WebDAV builds two
|
|
38
|
+
// schemes and SCHEME_PORTS below is the one table both this and the rail's dedup read it out of.
|
|
39
|
+
function defaultPort(protocol, tls) {
|
|
40
|
+
return defaultPortFor(scheme(protocol, tls))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The exact URI gio mount will be handed. Empty when there is not enough to build one, so the
|
|
44
|
+
// Mounts-as line shows nothing rather than a half-formed address.
|
|
45
|
+
function uri(form) {
|
|
46
|
+
var host = String(form.host || "").trim()
|
|
47
|
+
var port = String(form.port || "").trim()
|
|
48
|
+
if (!validHost(host) || !validPort(port)) {
|
|
49
|
+
return ""
|
|
50
|
+
}
|
|
51
|
+
var spec = fieldsFor(form.protocol)
|
|
52
|
+
var head = scheme(form.protocol, form.tls) + "://"
|
|
53
|
+
if (spec.credentials) {
|
|
54
|
+
head += userPart(form, spec)
|
|
55
|
+
}
|
|
56
|
+
head += host
|
|
57
|
+
// Keep canvas defaults in the form, but omit them because GVFS NFS rejects an explicit :2049.
|
|
58
|
+
if (port.length > 0 && Number(port) !== defaultPort(form.protocol, form.tls)) {
|
|
59
|
+
head += ":" + port
|
|
60
|
+
}
|
|
61
|
+
return head + pathPart(form.path)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function validHost(host) {
|
|
65
|
+
return /^\[[0-9A-Fa-f:.]+\]$/.test(host) || /^[A-Za-z0-9._-]+$/.test(host)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validPort(port) {
|
|
69
|
+
if (port.length === 0) {
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
if (!/^[0-9]+$/.test(port)) {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
var number = Number(port)
|
|
76
|
+
return number >= 1 && number <= 65535
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// smb://DOMAIN;user@host/share is the form a domain takes; without one it is just user@host.
|
|
80
|
+
function userPart(form, spec) {
|
|
81
|
+
var user = String(form.user || "").trim()
|
|
82
|
+
if (user.length === 0) {
|
|
83
|
+
return ""
|
|
84
|
+
}
|
|
85
|
+
var domain = spec.domain ? String(form.domain || "").trim() : ""
|
|
86
|
+
return (domain.length > 0 ? encodeURIComponent(domain) + ";" : "")
|
|
87
|
+
+ encodeURIComponent(user) + "@"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function pathPart(path) {
|
|
91
|
+
var text = String(path || "").trim()
|
|
92
|
+
if (text.length === 0) {
|
|
93
|
+
return "/"
|
|
94
|
+
}
|
|
95
|
+
var parts = text.split("/")
|
|
96
|
+
for (var i = 0; i < parts.length; i++) {
|
|
97
|
+
parts[i] = encodeURIComponent(parts[i])
|
|
98
|
+
}
|
|
99
|
+
var encoded = parts.join("/")
|
|
100
|
+
return encoded.charAt(0) === "/" ? encoded : "/" + encoded
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The label the sidebar row will carry: what the operator typed, or the last part of the path.
|
|
104
|
+
function label(form) {
|
|
105
|
+
var given = String(form.label || "").trim()
|
|
106
|
+
if (given.length > 0) {
|
|
107
|
+
return given
|
|
108
|
+
}
|
|
109
|
+
var path = String(form.path || "").replace(/\/+$/, "")
|
|
110
|
+
var cut = path.lastIndexOf("/")
|
|
111
|
+
var leaf = cut >= 0 ? path.substring(cut + 1) : path
|
|
112
|
+
return leaf.length > 0 ? leaf : String(form.host || "").trim()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// A form with nothing to mount cannot be saved, which is what greys the Save row out.
|
|
116
|
+
function complete(form) {
|
|
117
|
+
var host = String(form.host || "").trim()
|
|
118
|
+
var port = String(form.port || "").trim()
|
|
119
|
+
return validHost(host) && validPort(port)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The port each scheme drops, which is a property of the scheme and not of the protocol the form
|
|
123
|
+
// picked: WebDAV builds two schemes whose ports differ, and one number for both stripped a real
|
|
124
|
+
// ":443" off "dav://" and left its real ":80" on, the duplicate rail row this exists to remove.
|
|
125
|
+
// Measured on this box against gvfs 1.60.2 and glib2 2.88.3 by round-tripping each spelling through
|
|
126
|
+
// Gio.File, the uri mapper "gio mount -l" prints through: gio drops smb 445, sftp 22, ftp 21,
|
|
127
|
+
// ftps 21 (not IANA's 990), dav 80 and davs 443, and keeps every other port it is given, ftps 990
|
|
128
|
+
// and dav 443 included. It drops no nfs port at all, so 2049 is here for the other half of the job:
|
|
129
|
+
// the port an nfs client uses when the line omits one, so two spellings of one export make one row.
|
|
130
|
+
var SCHEME_PORTS = {
|
|
131
|
+
"smb": 445, "sftp": 22, "ftp": 21, "ftps": 21, "dav": 80, "davs": 443, "nfs": 2049
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// A bookmarks line is arbitrary text, so an inherited Object member must never answer as a port.
|
|
135
|
+
function defaultPortFor(uriScheme) {
|
|
136
|
+
var want = String(uriScheme || "").toLowerCase()
|
|
137
|
+
return SCHEME_PORTS.hasOwnProperty(want) ? SCHEME_PORTS[want] : 0
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// "gio mount -l" never reports a scheme's default port and the add form spells out the one it
|
|
141
|
+
// prefilled, so the same share arrives spelled two ways and must resolve to one rail row. The port
|
|
142
|
+
// is found in the authority alone: a ":" or an "@" further along belongs to the path.
|
|
143
|
+
function stripDefaultPort(uri) {
|
|
144
|
+
var text = String(uri || "")
|
|
145
|
+
var auth = authority(text)
|
|
146
|
+
var tail = ":" + defaultPortFor(schemeOf(text))
|
|
147
|
+
// A bracketed IPv6 literal ends in "]", so its own trailing digits can never match this tail.
|
|
148
|
+
if (auth.length === 0 || tail === ":0" || auth.substring(auth.length - tail.length) !== tail)
|
|
149
|
+
return text
|
|
150
|
+
var mark = text.indexOf("://") + 3
|
|
151
|
+
return text.substring(0, mark) + auth.substring(0, auth.length - tail.length)
|
|
152
|
+
+ text.substring(mark + auth.length)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The scheme a built or bookmarked URI carries, lowercased; "" when the text is not a URI at all.
|
|
156
|
+
function schemeOf(uri) {
|
|
157
|
+
var text = String(uri || "")
|
|
158
|
+
var mark = text.indexOf("://")
|
|
159
|
+
return mark < 0 ? "" : text.substring(0, mark).toLowerCase()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Everything between "://" and the next "/", which is the only place a userinfo, a host or a port
|
|
163
|
+
// can live: a scan over the whole URI would read a "@" or a ":" inside the path as one of those.
|
|
164
|
+
function authority(uri) {
|
|
165
|
+
var text = String(uri || "")
|
|
166
|
+
var mark = text.indexOf("://")
|
|
167
|
+
if (mark < 0)
|
|
168
|
+
return ""
|
|
169
|
+
var rest = text.substring(mark + 3)
|
|
170
|
+
var slash = rest.indexOf("/")
|
|
171
|
+
return slash < 0 ? rest : rest.substring(0, slash)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// The host alone: userinfo dropped at the authority's own last "@", a bracketed IPv6 literal kept
|
|
175
|
+
// whole because its colons are not a port separator, and any real port dropped.
|
|
176
|
+
function hostOf(uri) {
|
|
177
|
+
var auth = authority(uri)
|
|
178
|
+
var at = auth.lastIndexOf("@")
|
|
179
|
+
var hostPort = at < 0 ? auth : auth.substring(at + 1)
|
|
180
|
+
if (hostPort.charAt(0) === "[") {
|
|
181
|
+
var close = hostPort.indexOf("]")
|
|
182
|
+
return close < 0 ? hostPort : hostPort.substring(0, close + 1)
|
|
183
|
+
}
|
|
184
|
+
var colon = hostPort.indexOf(":")
|
|
185
|
+
return colon < 0 ? hostPort : hostPort.substring(0, colon)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// gvfsd renders a network mount as "<share> <word> <host>" and translates that word, so issue #36's
|
|
189
|
+
// rule reads the URI instead: the tail that gets cut is this URI's own host, in any language, and a
|
|
190
|
+
// label that does not end in it is left exactly as gio gave it.
|
|
191
|
+
function shareName(rawLabel, uri) {
|
|
192
|
+
var text = String(rawLabel || "")
|
|
193
|
+
var host = hostOf(uri)
|
|
194
|
+
var head = text.substring(0, text.length - host.length)
|
|
195
|
+
if (host.length === 0 || head.length === 0 || text.substring(head.length) !== host)
|
|
196
|
+
return text
|
|
197
|
+
// The one assumption left after the host check: gvfsd's connector is a single whitespace-delimited
|
|
198
|
+
// word in whatever language it renders. A translation using two would leave one of them on the
|
|
199
|
+
// name, a wrong label and never a wrong destination; a head this does not shorten is left whole.
|
|
200
|
+
var name = head.replace(/\s+\S+\s+$/, "")
|
|
201
|
+
return name.length > 0 && name !== head ? name : text
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Sample input: smb://dan@192.168.1.75/test, the reporter's share, and its FUSE folder
|
|
205
|
+
// /run/user/1000/gvfs/smb-share:server=192.168.1.75,share=test,user=dan. A FUSE path for
|
|
206
|
+
// another user, another share or another server must never open as this share's listing:
|
|
207
|
+
// a mount problem answers as one instead of reading like an unreadable directory.
|
|
208
|
+
function decodeSegment(raw) {
|
|
209
|
+
try { return decodeURIComponent(String(raw)) } catch (e) { return String(raw) }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// The smb:// request as gio sees it: the user past any DOMAIN; prefix, the host without
|
|
213
|
+
// a port, and the share as the first path segment. Null when not an smb:// URI at all.
|
|
214
|
+
function smbParts(uri) {
|
|
215
|
+
if (schemeOf(uri) !== "smb") return null
|
|
216
|
+
var auth = authority(uri)
|
|
217
|
+
if (auth.length === 0) return null
|
|
218
|
+
var at = auth.lastIndexOf("@")
|
|
219
|
+
var info = at < 0 ? "" : auth.substring(0, at)
|
|
220
|
+
var semi = info.indexOf(";")
|
|
221
|
+
var user = semi < 0 ? info : info.substring(semi + 1)
|
|
222
|
+
var mark = String(uri).indexOf("://") + 3
|
|
223
|
+
var rest = String(uri).substring(mark + auth.length)
|
|
224
|
+
if (rest.charAt(0) === "/") rest = rest.substring(1)
|
|
225
|
+
var cut = rest.indexOf("/")
|
|
226
|
+
var share = cut < 0 ? rest : rest.substring(0, cut)
|
|
227
|
+
return { user: decodeSegment(user), host: hostOf(uri).toLowerCase(), share: decodeSegment(share) }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// The smb-share: FUSE directory gio info prints a local path for, or null when the path
|
|
231
|
+
// is no smb FUSE directory at all. Null is undecided, never a mismatch: an unknown shape
|
|
232
|
+
// opens exactly as before rather than refusing a mount over a format this never learned.
|
|
233
|
+
function fuseSmbParts(path) {
|
|
234
|
+
var m = String(path || "").match(/smb-share:server=([^,\/]+),share=([^,\/]+)(?:,user=([^,\/]+))?/)
|
|
235
|
+
if (!m) return null
|
|
236
|
+
return { host: String(m[1]).toLowerCase(), share: m[2], user: m[3] || "" }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Whether gio info's answer belongs to the smb:// request that asked for it. True for any
|
|
240
|
+
// non-smb URI and whenever either side will not parse; false only on a parsed mismatch, a
|
|
241
|
+
// user the answer drops or changes above all, so that answer is refused as a mount failure.
|
|
242
|
+
function smbFuseMatches(uri, fusePath) {
|
|
243
|
+
if (schemeOf(uri) !== "smb") return true
|
|
244
|
+
var want = smbParts(uri)
|
|
245
|
+
var got = fuseSmbParts(fusePath)
|
|
246
|
+
if (!want || !got) return true
|
|
247
|
+
var wantUser = String(want.user || "")
|
|
248
|
+
var gotUser = String(got.user || "")
|
|
249
|
+
var semi = gotUser.indexOf(";")
|
|
250
|
+
if (semi >= 0) gotUser = gotUser.substring(semi + 1)
|
|
251
|
+
return want.host === got.host
|
|
252
|
+
&& String(want.share).toLowerCase() === String(got.share).toLowerCase()
|
|
253
|
+
&& wantUser.toLowerCase() === gotUser.toLowerCase()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Sample input: uri smb://dan@192.168.1.75/test with gio info printing the path without
|
|
257
|
+
// ,user=dan while only the user=dan folder exists. Answers the entry whose user the URI
|
|
258
|
+
// names; with no user the single share match wins, several prefer the desktop login.
|
|
259
|
+
function smbResolve(uri, fusePaths, login) {
|
|
260
|
+
var found = { path: "", users: [] }
|
|
261
|
+
var want = smbParts(uri)
|
|
262
|
+
if (!want) return found
|
|
263
|
+
var hits = []
|
|
264
|
+
var list = fusePaths || []
|
|
265
|
+
for (var i = 0; i < list.length; i++) {
|
|
266
|
+
var got = fuseSmbParts(list[i])
|
|
267
|
+
if (!got || got.host !== want.host) continue
|
|
268
|
+
if (String(got.share).toLowerCase() !== String(want.share).toLowerCase()) continue
|
|
269
|
+
hits.push({ path: list[i], user: String(got.user || "") })
|
|
270
|
+
}
|
|
271
|
+
if (String(want.user || "").length > 0) {
|
|
272
|
+
for (var j = 0; j < hits.length; j++) {
|
|
273
|
+
if (hits[j].user.toLowerCase() === String(want.user).toLowerCase()) {
|
|
274
|
+
found.path = hits[j].path
|
|
275
|
+
return found
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return found
|
|
279
|
+
}
|
|
280
|
+
if (hits.length === 1) {
|
|
281
|
+
found.path = hits[0].path
|
|
282
|
+
return found
|
|
283
|
+
}
|
|
284
|
+
if (hits.length > 1) {
|
|
285
|
+
for (var k = 0; k < hits.length; k++) {
|
|
286
|
+
if (hits[k].user.toLowerCase() === String(login || "").toLowerCase()) {
|
|
287
|
+
found.path = hits[k].path
|
|
288
|
+
return found
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
for (var m = 0; m < hits.length; m++) {
|
|
292
|
+
if (found.users.indexOf(hits[m].user) < 0) found.users.push(hits[m].user)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return found
|
|
296
|
+
}
|