Safety: no auto-browser from text containing URLs (only pure-link entries); row click selects (double-click activates); missing deps shown as picker banner with run-in-terminal button

This commit is contained in:
2026-09-05 23:48:53 +01:00
parent a2ed301bb0
commit 6b0a2ac494
2 changed files with 108 additions and 4 deletions
+102 -3
View File
@@ -120,6 +120,7 @@ Item {
root.history = Store.parseHistory(raw, Math.floor(Date.now() / 1000)) root.history = Store.parseHistory(raw, Math.floor(Date.now() / 1000))
root.typeCache = {} root.typeCache = {}
root.applyRetentionPolicy() root.applyRetentionPolicy()
root.checkDeps()
if (root.opened) root.rebuild() if (root.opened) root.rebuild()
} }
@@ -330,6 +331,35 @@ Item {
onFileChanged: reload() onFileChanged: reload()
} }
// Missing-helper state surfaced IN the picker: banner with the exact
// command and a "Run in terminal" button. Nothing launches on its own.
property bool depsChecked: false
property var missingDeps: [] // [{ name, command }]
property string depsPendingSlot: ""
function checkDeps() {
if (root.depsChecked) return
root.depsChecked = true
if (root.qrDecode) {
depsProc.slot = "qr"
depsProc.command = ["bash", "-c", "command -v zbarimg >/dev/null 2>&1 && echo ok || echo missing"]
depsProc.running = true
}
if (root.ocr) {
depsProc.slot = "ocr"
depsProc.command = ["bash", "-c", "command -v tesseract >/dev/null 2>&1 && echo ok || echo missing"]
depsProc.running = true
}
}
function depResult(slot, line) {
if (String(line).trim() !== "missing") return
if (slot === "qr") {
root.missingDeps.push({ name: "QR decode", command: "omarchy pkg add zbar" })
} else if (slot === "ocr") {
root.missingDeps.push({ name: "OCR search", command: "omarchy pkg add tesseract tesseract-data-eng" })
}
}
function applySettings(raw) { function applySettings(raw) {
var s = Store.parseSettings(raw, "alanfortlink.clipboard") var s = Store.parseSettings(raw, "alanfortlink.clipboard")
var needsWatchRestart = s.qrDecode !== root.qrDecode || s.ocr !== root.ocr var needsWatchRestart = s.qrDecode !== root.qrDecode || s.ocr !== root.ocr
@@ -347,6 +377,7 @@ Item {
watchRestartTimer.restart() watchRestartTimer.restart()
} }
root.applyRetentionPolicy() root.applyRetentionPolicy()
root.checkDeps()
if (root.opened) root.rebuild() if (root.opened) root.rebuild()
} }
@@ -374,7 +405,14 @@ Item {
onFileChanged: reload() onFileChanged: reload()
} }
Process { id: depsProc } Process {
id: depsProc
property string slot: ""
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: root.depResult(depsProc.slot, text)
}
}
Process { Process {
id: gcProc id: gcProc
onExited: runNextGc() onExited: runNextGc()
@@ -671,11 +709,69 @@ Item {
} }
} }
// ---- missing-dependency banner: command + run-in-terminal button
Rectangle {
width: parent.width
height: root.missingDeps.length > 0 ? Style.space(24) : 0
visible: root.missingDeps.length > 0
radius: Style.cornerRadius
color: Util.alpha(Color.urgent, 0.15)
Row {
anchors.centerIn: parent
spacing: Style.space(8)
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.missingDeps.length > 0
? root.missingDeps[0].name + " unavailable — run: " + root.missingDeps[0].command
: ""
color: Color.urgent
font.family: root.fontFamily
font.pixelSize: Style.font.caption
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
radius: height / 2
color: Util.alpha(Color.accent, 0.2)
width: runLabel.implicitWidth + Style.space(14)
height: Style.space(18)
visible: root.missingDeps.length > 0
Text {
id: runLabel
anchors.centerIn: parent
text: "▶ Run in terminal"
color: root.accent
font.family: root.fontFamily
font.pixelSize: Style.font.caption
font.bold: true
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.missingDeps.length === 0) return
root.close()
var cmd = ""
for (var i = 0; i < root.missingDeps.length; i++)
cmd += (i ? " && " : "") + root.missingDeps[i].command
cmd += '; echo; echo "Done — press Enter to close"; read'
Quickshell.execDetached(["omarchy-launch-terminal", "bash", "-c", cmd])
}
}
}
}
}
// ---- list + preview // ---- list + preview
Item { Item {
width: parent.width width: parent.width
height: parent.height - root.headerHeight - chipsRow.height height: parent.height - root.headerHeight - chipsRow.height
- (root.paused ? Style.space(24) + Style.space(10) : 0) - (root.paused ? Style.space(24) + Style.space(10) : 0)
- (root.missingDeps.length > 0 ? Style.space(24) + Style.space(10) : 0)
- footer.height - Style.space(30) - footer.height - Style.space(30)
ListView { ListView {
@@ -785,11 +881,14 @@ Item {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPositionChanged: function(mouse) { root.selectFromPointer(row.index, row, mouse) } onPositionChanged: function(mouse) { root.selectFromPointer(row.index, row, mouse) }
onClicked: { // A stray click must never paste into the window behind the
// picker: single click selects, double click activates.
onClicked: function(mouse) {
root.cursorActive = true root.cursorActive = true
root.selectedIndex = row.index root.selectedIndex = row.index
root.pasteResult(root.currentResult) if (mouse.clickCount === 2) root.pasteResult(root.currentResult)
} }
onDoubleClicked: root.pasteResult(root.currentResult)
} }
} }
} }
+6 -1
View File
@@ -22,8 +22,13 @@ open_text() {
if [[ -z $url && $text =~ ^[[:space:]]*([[:alnum:]][[:alnum:].-]+\.[[:alpha:]]{2,})(/[^[:space:]]*)?[[:space:]]*$ ]]; then if [[ -z $url && $text =~ ^[[:space:]]*([[:alnum:]][[:alnum:].-]+\.[[:alpha:]]{2,})(/[^[:space:]]*)?[[:space:]]*$ ]]; then
url="https://${BASH_REMATCH[1]}${BASH_REMATCH[2]}" url="https://${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
fi fi
# Only open the browser when the WHOLE entry is that link. Text that merely
# contains a URL — like a copied shell command — must not launch anything.
if [[ -n $url ]]; then if [[ -n $url ]]; then
exec omarchy-launch-browser "$url" local compact=${text//[[:space:]]/}
if [[ ${compact,,} == ${url,,}* ]]; then
exec omarchy-launch-browser "$url"
fi
fi fi
local dir file local dir file
dir="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/clipboard-open" dir="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/clipboard-open"