Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
pragma Singleton
|
||||
|
||||
// Local screen recognition. Capture owns geometry; this service owns the
|
||||
// Tesseract/ZBar process boundary, result state, explicit follow-up actions,
|
||||
// and cleanup of temporary screen images.
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string helperPath: Quickshell.shellDir + "/scripts/screen-intelligence"
|
||||
|
||||
// "idle" | "capturing" | "reading" | "ready" | "error"
|
||||
property string phase: "idle"
|
||||
// Do not map the result surface until grim has finished, or the overlay
|
||||
// can appear in the very pixels it is trying to analyze.
|
||||
readonly property bool visible: phase !== "idle" && phase !== "capturing"
|
||||
readonly property bool busy: phase === "capturing" || phase === "reading"
|
||||
|
||||
property bool ocrReady: false
|
||||
property bool codeReady: false
|
||||
property bool englishReady: false
|
||||
property string text: ""
|
||||
property string codeType: ""
|
||||
property string codeValue: ""
|
||||
property string error: ""
|
||||
property string imagePath: ""
|
||||
property bool ownsImage: false
|
||||
|
||||
readonly property string primaryUrl: root._firstWebUrl(root.codeValue) || root._firstWebUrl(root.text)
|
||||
|
||||
function refresh(): void {
|
||||
probeProc.running = false;
|
||||
probeProc.running = true;
|
||||
}
|
||||
|
||||
function analyzeRegion(geometry: string, outputName: string): void {
|
||||
root._prepare();
|
||||
root.imagePath = Quickshell.cachePath("screen-intelligence-" + Date.now() + ".png");
|
||||
root.ownsImage = true;
|
||||
root.phase = "capturing";
|
||||
|
||||
const command = ["grim"];
|
||||
if (geometry !== "")
|
||||
command.push("-g", geometry);
|
||||
else if (outputName !== "")
|
||||
command.push("-o", outputName);
|
||||
command.push(root.imagePath);
|
||||
|
||||
captureProc.command = command;
|
||||
captureProc.running = false;
|
||||
captureProc.running = true;
|
||||
}
|
||||
|
||||
function analyzeFile(path: string): void {
|
||||
root._prepare();
|
||||
root.imagePath = path;
|
||||
root.ownsImage = false;
|
||||
root._startRecognition();
|
||||
}
|
||||
|
||||
function copyText(): void {
|
||||
if (root.text !== "")
|
||||
Quickshell.execDetached(["wl-copy", root.text]);
|
||||
}
|
||||
|
||||
function copyCode(): void {
|
||||
if (root.codeValue !== "")
|
||||
Quickshell.execDetached(["wl-copy", root.codeValue]);
|
||||
}
|
||||
|
||||
function search(): void {
|
||||
if (root.text === "")
|
||||
return;
|
||||
Quickshell.execDetached(["xdg-open", "https://www.google.com/search?q=" + encodeURIComponent(root.text.slice(0, 4000))]);
|
||||
}
|
||||
|
||||
function translate(): void {
|
||||
if (root.text === "")
|
||||
return;
|
||||
Quickshell.execDetached(["xdg-open", "https://translate.google.com/?sl=auto&tl=en&text=" + encodeURIComponent(root.text.slice(0, 4000))]);
|
||||
}
|
||||
|
||||
function openDetected(): void {
|
||||
if (root.primaryUrl !== "")
|
||||
Quickshell.execDetached(["xdg-open", root.primaryUrl]);
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
captureProc.running = false;
|
||||
recognizeProc.running = false;
|
||||
root._dropOwnedImage();
|
||||
root.phase = "idle";
|
||||
root.text = "";
|
||||
root.codeType = "";
|
||||
root.codeValue = "";
|
||||
root.error = "";
|
||||
root.imagePath = "";
|
||||
root.ownsImage = false;
|
||||
}
|
||||
|
||||
function _prepare(): void {
|
||||
captureProc.running = false;
|
||||
recognizeProc.running = false;
|
||||
root._dropOwnedImage();
|
||||
root.text = "";
|
||||
root.codeType = "";
|
||||
root.codeValue = "";
|
||||
root.error = "";
|
||||
root.imagePath = "";
|
||||
root.ownsImage = false;
|
||||
}
|
||||
|
||||
function _startRecognition(): void {
|
||||
root.phase = "reading";
|
||||
recognizeProc.sawOutput = false;
|
||||
recognizeProc.command = [root.helperPath, "analyze-file", root.imagePath];
|
||||
recognizeProc.running = false;
|
||||
recognizeProc.running = true;
|
||||
}
|
||||
|
||||
function _consume(raw: string): void {
|
||||
try {
|
||||
const result = JSON.parse(raw);
|
||||
if (!result.ok) {
|
||||
root.error = result.error || "Panama could not read that capture.";
|
||||
root.phase = "error";
|
||||
return;
|
||||
}
|
||||
root.text = (result.text || "").trim();
|
||||
root.codeType = result.codeType || "";
|
||||
root.codeValue = result.codeValue || "";
|
||||
root.error = "";
|
||||
root.phase = "ready";
|
||||
} catch (e) {
|
||||
root.error = "The local recognition engine returned an unreadable result.";
|
||||
root.phase = "error";
|
||||
}
|
||||
}
|
||||
|
||||
function _dropOwnedImage(): void {
|
||||
if (root.ownsImage && root.imagePath !== "")
|
||||
Quickshell.execDetached(["rm", "-f", root.imagePath]);
|
||||
}
|
||||
|
||||
function _firstWebUrl(value: string): string {
|
||||
if (!value)
|
||||
return "";
|
||||
const match = value.match(/https?:\/\/[^\s<>'\"]+/i);
|
||||
if (!match)
|
||||
return "";
|
||||
return match[0].replace(/[),.;!?]+$/, "");
|
||||
}
|
||||
|
||||
Process {
|
||||
id: probeProc
|
||||
command: [root.helperPath, "probe"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const result = JSON.parse(this.text);
|
||||
root.ocrReady = result.tesseract === true;
|
||||
root.codeReady = result.zbar === true;
|
||||
root.englishReady = result.english === true;
|
||||
} catch (e) {
|
||||
root.ocrReady = false;
|
||||
root.codeReady = false;
|
||||
root.englishReady = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: captureProc
|
||||
onExited: (code, status) => {
|
||||
if (root.phase !== "capturing")
|
||||
return;
|
||||
if (code !== 0) {
|
||||
root.error = "Panama could not capture the selected area.";
|
||||
root.phase = "error";
|
||||
return;
|
||||
}
|
||||
root._startRecognition();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: recognizeProc
|
||||
property bool sawOutput: false
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
recognizeProc.sawOutput = this.text !== "";
|
||||
root._consume(this.text);
|
||||
}
|
||||
}
|
||||
onExited: (code, status) => {
|
||||
if (!recognizeProc.sawOutput && root.phase === "reading") {
|
||||
root.error = "The local recognition engine did not return a result.";
|
||||
root.phase = "error";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: root.refresh()
|
||||
}
|
||||
Reference in New Issue
Block a user