198 lines · 7.0 KB · Text
RawDownload
import QtQuickimport QtQuick.Windowimport QtQml.Modelsimport org.kde.kwinimport "feedbackGeometry.js" as Geometry // Internal output-only windows do not change, activate, or intercept input to the real window.Item {    id: root    property var options: /*OPTIONS*/ null    property string bus: /*BUS*/ ""    property string scriptName: /*NAME*/ ""    property real motionFactor: /*FACTOR*/ 1    property rect frame: Qt.rect(options.bounds.x, options.bounds.y, options.bounds.width, options.bounds.height)    property bool showing: false    property bool started: false    property bool closing: false    property bool flying: false    property bool initialized: false    property bool readySent: false    property real flashOpacity: 0    property var target: null    property url screenshot: Qt.resolvedUrl("capture.png")     function finish() {        if (closing) return;        closing = true;        showing = false;        flight.stop();        deadline.stop();        done.call();    }    function begin() {        if (started || closing) return;        started = true;        showing = true;        if (options.flash) flash.start();        next.call();    }     // A cached, bounded PNG is shared by each output. Nothing repaints while waiting for T3.    Image {        id: snapshot        visible: false        source: root.screenshot        sourceSize.width: 2560        sourceSize.height: 1600        onStatusChanged: {            if (status === Image.Ready && root.initialized) root.begin();            else if (status === Image.Error) root.finish();        }    }    Component.onCompleted: {        initialized = true;        if (snapshot.status === Image.Ready) begin();        else if (snapshot.status === Image.Error) finish();    }    Instantiator {        model: Workspace.screens        delegate: Window {            required property var modelData            x: modelData.geometry.x            y: modelData.geometry.y            width: modelData.geometry.width            height: modelData.geometry.height            color: "transparent"            visible: root.showing            flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.BypassWindowManagerHint | Qt.WindowStaysOnTopHint | Qt.WindowTransparentForInput | Qt.WindowDoesNotAcceptFocus            property bool outputOnly: true            property bool kwin_shadow_enabled: false            Connections {                target: modelData                function onGeometryChanged() { root.finish(); }            }            onFrameSwapped: {                if (root.showing && !root.readySent) {                    root.readySent = true;                    ready.call();                }            }            Item {                x: root.frame.x - modelData.geometry.x                y: root.frame.y - modelData.geometry.y                width: root.frame.width                height: root.frame.height                Image {                    anchors.fill: parent                    visible: root.options.animate                    source: snapshot.source                    sourceSize: snapshot.sourceSize                    fillMode: Image.Stretch                }            }            Rectangle {                x: root.options.bounds.x - modelData.geometry.x                y: root.options.bounds.y - modelData.geometry.y                width: root.options.bounds.width                height: root.options.bounds.height                color: "white"                opacity: root.flashOpacity            }        }    }    NumberAnimation {        id: flash        target: root        property: "flashOpacity"        from: 0.10        to: 0        duration: 180        easing.type: Easing.OutQuad    }    PropertyAnimation {        id: flight        target: root        property: "frame"        easing.type: Easing.OutCubic        onFinished: landed.call()    }    Timer { id: deadline; interval: 6000; running: true; onTriggered: root.finish() }    Connections {        target: Workspace        function onScreensChanged() { root.finish(); }        function onVirtualScreenGeometryChanged() { root.finish(); }        function onCurrentDesktopChanged() { root.finish(); }        function onCurrentActivityChanged() { root.finish(); }    }    Connections {        target: root.target        function onClientGeometryChanged() { root.finish(); }        function onClosed() { root.finish(); }        function onMinimizedChanged() { root.finish(); }    }    DBusCall {        id: next        service: root.bus        path: "/com/t3tools/KdeCapture/Feedback"        dbusInterface: "com.t3tools.KdeCapture.Feedback"        method: "Next"        onFailed: root.finish()        onFinished: (values) => {            if (root.closing) return;            try {                const command = JSON.parse(values[0]);                if (command.command === "close") { root.finish(); return; }                if (command.command !== "animate" || root.flying || !root.options.animate)                    throw new Error("Invalid capture feedback command");                const destination = Geometry.destination(Workspace.windows, root.options.pid, command.title, command.frame);                root.target = destination.window;                const frame = destination.frame;                const distance = Math.hypot(frame.x - root.frame.x, frame.y - root.frame.y);                flight.duration = Math.round((680 - 400 * Math.exp(-distance / 2000)) * root.motionFactor);                flight.to = Qt.rect(frame.x, frame.y, frame.width, frame.height);                root.flying = true;                flight.start();                // Keep a pending call during the animation for cancellation and owner loss.                next.call();            } catch (error) { root.finish(); }        }    }    DBusCall {        id: ready        service: root.bus        path: "/com/t3tools/KdeCapture/Feedback"        dbusInterface: "com.t3tools.KdeCapture.Feedback"        method: "Event"        arguments: [JSON.stringify({event: "ready", animate: root.options.animate})]        onFailed: root.finish()    }    DBusCall {        id: landed        service: root.bus        path: "/com/t3tools/KdeCapture/Feedback"        dbusInterface: "com.t3tools.KdeCapture.Feedback"        method: "Event"        arguments: ['{"event":"landed"}']        onFailed: root.finish()    }    DBusCall {        id: done        service: root.bus        path: "/com/t3tools/KdeCapture/Feedback"        dbusInterface: "com.t3tools.KdeCapture.Feedback"        method: "Event"        arguments: ['{"event":"done"}']        onFinished: unload.call()        onFailed: unload.call()    }    // Also unload after helper loss; a hidden script would otherwise retain the screenshot.    DBusCall {        id: unload        service: "org.kde.KWin"        path: "/Scripting"        dbusInterface: "org.kde.kwin.Scripting"        method: "unloadScript"        arguments: [root.scriptName]    }}