up su Gitea
This commit is contained in:
@@ -0,0 +1,457 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property list<var> devices: []
|
||||
property bool daemonAvailable: false
|
||||
property int pendingDeviceCount: 0
|
||||
property list<var> pendingDevices: []
|
||||
|
||||
property var mainDevice: null
|
||||
property string mainDeviceId: ""
|
||||
property string busctlCmd: ""
|
||||
|
||||
property bool anyDevicesConnected: false;
|
||||
|
||||
onDevicesChanged: {
|
||||
setMainDevice(root.mainDeviceId)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
checkDaemon();
|
||||
}
|
||||
|
||||
// Check if KDE Connect daemon is available
|
||||
function checkDaemon(): void {
|
||||
detectBusctlProc.running = true;
|
||||
}
|
||||
|
||||
// Refresh the list of devices
|
||||
function refreshDevices(): void {
|
||||
getDevicesProc.running = true;
|
||||
}
|
||||
|
||||
function setMainDevice(deviceId: string): void {
|
||||
root.mainDeviceId = deviceId;
|
||||
updateMainDevice(false);
|
||||
}
|
||||
|
||||
function updateMainDevice(checkReachable) {
|
||||
let newMain;
|
||||
if (checkReachable) {
|
||||
newMain = devices.find((device) => device.id === root.mainDeviceId && device.reachable);
|
||||
if (newMain === undefined)
|
||||
newMain = devices.find((device) => device.reachable);
|
||||
if (newMain === undefined)
|
||||
newMain = devices.length === 0 ? null : devices[0];
|
||||
} else {
|
||||
newMain = devices.find((device) => device.id === root.mainDeviceId);
|
||||
if (newMain === undefined)
|
||||
newMain = devices.length === 0 ? null : devices[0];
|
||||
}
|
||||
|
||||
if (root.mainDevice !== newMain) {
|
||||
root.mainDevice = newMain;
|
||||
}
|
||||
|
||||
anyDevicesConnected = devices.find((device) => device.reachable) !== undefined;
|
||||
}
|
||||
|
||||
function triggerFindMyPhone(deviceId: string): void {
|
||||
const proc = findMyPhoneComponent.createObject(root, { deviceId: deviceId });
|
||||
proc.running = true;
|
||||
}
|
||||
|
||||
function browseFiles(deviceId: string): void {
|
||||
const proc = browseFilesComponent.createObject(root, { deviceId: deviceId });
|
||||
proc.running = true;
|
||||
}
|
||||
|
||||
// Share a file with a device
|
||||
function shareFile(deviceId: string, filePath: string): void {
|
||||
var proc = shareComponent.createObject(root, {
|
||||
deviceId: deviceId,
|
||||
filePath: filePath
|
||||
});
|
||||
proc.running = true;
|
||||
}
|
||||
|
||||
function requestPairing(deviceId: string): void {
|
||||
const proc = requestPairingComponent.createObject(root, { deviceId: deviceId });
|
||||
proc.running = true;
|
||||
}
|
||||
|
||||
function unpairDevice(deviceId: string): void {
|
||||
const proc = unpairingComponent.createObject(root, { deviceId: deviceId });
|
||||
proc.running = true;
|
||||
}
|
||||
|
||||
function wakeUpDevice(deviceId: string): void {
|
||||
const proc = wakeUpDeviceComponent.createObject(root, { deviceId: deviceId });
|
||||
proc.running = true;
|
||||
}
|
||||
|
||||
function busctlCall(obj, itf, method, params = []) {
|
||||
let result = [ root.busctlCmd, "--user", "call", "--json=short", "org.kde.kdeconnect", obj, itf, method ];
|
||||
return result.concat(params);
|
||||
}
|
||||
|
||||
function busctlGet(obj, itf, prop) {
|
||||
return [ root.busctlCmd, "--user", "get-property", "--json=short", "org.kde.kdeconnect", obj, itf, prop ];
|
||||
}
|
||||
|
||||
function busctlData(text) {
|
||||
if (text === "")
|
||||
return "";
|
||||
|
||||
try {
|
||||
let result = JSON.parse(text)?.data;
|
||||
if (Array.isArray(result) && Array.isArray(result[0]))
|
||||
return result[0]
|
||||
else
|
||||
return result;
|
||||
} catch (e) {
|
||||
Logger.e("KDEConnect", "Failed to parse busctl response: ", text)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
property Process detectBusctlProc: Process {
|
||||
command: ["which", "busctl"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (root.busctlCmd !== "") {
|
||||
root.daemonCheckProc.running = true
|
||||
return
|
||||
}
|
||||
|
||||
let location = text.trim()
|
||||
if (location !== "") {
|
||||
root.busctlCmd = location
|
||||
root.daemonCheckProc.running = true
|
||||
Logger.i("KDEConnect", "Found busctl command:", location)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check daemon
|
||||
property Process daemonCheckProc: Process {
|
||||
command: [root.busctlCmd, "--user", "status", "org.kde.kdeconnect"]
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
root.daemonAvailable = exitCode == 0;
|
||||
if (root.daemonAvailable) {
|
||||
forceOnNetworkChange.running = true;
|
||||
} else {
|
||||
root.devices = []
|
||||
root.mainDevice = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process forceOnNetworkChange: Process {
|
||||
command: busctlCall("/modules/kdeconnect", "org.kde.kdeconnect.daemon", "forceOnNetworkChange")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
getDevicesProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get device list
|
||||
property Process getDevicesProc: Process {
|
||||
command: busctlCall("/modules/kdeconnect", "org.kde.kdeconnect.daemon", "devices")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const deviceIds = busctlData(text);
|
||||
|
||||
root.pendingDevices = [];
|
||||
root.pendingDeviceCount = deviceIds.length;
|
||||
|
||||
deviceIds.forEach(deviceId => {
|
||||
const loader = deviceLoaderComponent.createObject(root, { deviceId: deviceId });
|
||||
loader.start();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Component that loads all info for a single device
|
||||
property Component deviceLoaderComponent: Component {
|
||||
QtObject {
|
||||
id: loader
|
||||
property string deviceId: ""
|
||||
property var deviceData: ({
|
||||
id: deviceId,
|
||||
name: "",
|
||||
reachable: false,
|
||||
paired: false,
|
||||
pairRequested: false,
|
||||
verificationKey: "",
|
||||
charging: false,
|
||||
battery: -1,
|
||||
cellularNetworkType: "",
|
||||
cellularNetworkStrength: -1,
|
||||
notificationIds: []
|
||||
})
|
||||
|
||||
function start() {
|
||||
nameProc.running = true
|
||||
}
|
||||
|
||||
property Process nameProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId, "org.kde.kdeconnect.device", "name")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
loader.deviceData.name = busctlData(text);
|
||||
|
||||
reachableProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process reachableProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId, "org.kde.kdeconnect.device", "isReachable")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
loader.deviceData.reachable = busctlData(text);
|
||||
|
||||
pairingRequestedProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process pairingRequestedProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId, "org.kde.kdeconnect.device", "isPairRequested")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
loader.deviceData.pairRequested = busctlData(text);
|
||||
|
||||
verificationKeyProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process verificationKeyProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId, "org.kde.kdeconnect.device", "verificationKey")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
loader.deviceData.verificationKey = busctlData(text);
|
||||
|
||||
pairedProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process pairedProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId, "org.kde.kdeconnect.device", "isPaired")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
loader.deviceData.paired = busctlData(text);
|
||||
|
||||
if (loader.deviceData.paired)
|
||||
activeNotificationsProc.running = true;
|
||||
else
|
||||
finalize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process activeNotificationsProc: Process {
|
||||
command: busctlCall("/modules/kdeconnect/devices/" + loader.deviceId + "/notifications", "org.kde.kdeconnect.device.notifications", "activeNotifications");
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
let ids = busctlData(text);
|
||||
loader.deviceData.notificationIds = ids
|
||||
|
||||
cellularNetworkTypeProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process cellularNetworkTypeProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId + "/connectivity_report", "org.kde.kdeconnect.device.connectivity_report", "cellularNetworkType")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
loader.deviceData.cellularNetworkType = busctlData(text);
|
||||
cellularNetworkStrengthProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process cellularNetworkStrengthProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId + "/connectivity_report", "org.kde.kdeconnect.device.connectivity_report", "cellularNetworkStrength")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const strength = busctlData(text);
|
||||
loader.deviceData.cellularNetworkStrength = strength;
|
||||
isChargingProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process isChargingProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId + "/battery", "org.kde.kdeconnect.device.battery", "isCharging")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
loader.deviceData.charging = busctlData(text);
|
||||
batteryProc.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Process batteryProc: Process {
|
||||
command: busctlGet("/modules/kdeconnect/devices/" + loader.deviceId + "/battery", "org.kde.kdeconnect.device.battery", "charge")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const charge = busctlData(text);
|
||||
if (!isNaN(charge)) {
|
||||
loader.deviceData.battery = charge;
|
||||
}
|
||||
|
||||
finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
root.pendingDevices = root.pendingDevices.concat([loader.deviceData]);
|
||||
|
||||
if (root.pendingDevices.length === root.pendingDeviceCount) {
|
||||
let newDevices = root.pendingDevices
|
||||
newDevices.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
let prevMainDevice = root.devices.find((device) => device.id === root.mainDeviceId);
|
||||
let newMainDevice = newDevices.find((device) => device.id === root.mainDeviceId);
|
||||
|
||||
let deviceNotReachableAnymore =
|
||||
prevMainDevice === undefined ||
|
||||
(
|
||||
(prevMainDevice?.reachable ?? false) &&
|
||||
!(newMainDevice?.reachable ?? false)
|
||||
) ||
|
||||
(
|
||||
(prevMainDevice?.paired ?? false) &&
|
||||
!(newMainDevice?.paired ?? false)
|
||||
)
|
||||
|
||||
root.devices = newDevices
|
||||
root.pendingDevices = []
|
||||
updateMainDevice(deviceNotReachableAnymore);
|
||||
}
|
||||
|
||||
loader.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindMyPhone component
|
||||
property Component findMyPhoneComponent: Component {
|
||||
Process {
|
||||
id: proc
|
||||
property string deviceId: ""
|
||||
command: busctlCall("/modules/kdeconnect/devices/" + deviceId + "/findmyphone", "org.kde.kdeconnect.device.findmyphone", "ring")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: proc.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SFTP Browse component
|
||||
property Component browseFilesComponent: Component {
|
||||
Process {
|
||||
id: mountProc
|
||||
property string deviceId: ""
|
||||
command: busctlCall("/modules/kdeconnect/devices/" + deviceId + "/sftp", "org.kde.kdeconnect.device.sftp", "mountAndWait")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: rootDirProc.running = true
|
||||
}
|
||||
|
||||
property Process rootDirProc: Process {
|
||||
command: busctlCall("/modules/kdeconnect/devices/" + mountProc.deviceId + "/sftp", "org.kde.kdeconnect.device.sftp", "getDirectories")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const dirs = busctlData(text);
|
||||
const path = Object.keys(dirs[0])[0];
|
||||
if (!Qt.openUrlExternally("file://" + path)) {
|
||||
Logger.e("KDEConnect", "Failed to open file manager for path:", path);
|
||||
}
|
||||
|
||||
mountProc.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request Pairing Component
|
||||
property Component requestPairingComponent: Component {
|
||||
Process {
|
||||
id: proc
|
||||
property string deviceId: ""
|
||||
command: busctlCall("/modules/kdeconnect/devices/" + deviceId, "org.kde.kdeconnect.device", "requestPairing")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: proc.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unpairing Component
|
||||
property Component unpairingComponent: Component {
|
||||
Process {
|
||||
id: proc
|
||||
property string deviceId: ""
|
||||
command: busctlCall("/modules/kdeconnect/devices/" + deviceId, "org.kde.kdeconnect.device", "unpair")
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
KDEConnect.refreshDevices()
|
||||
proc.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wake up Device Component
|
||||
property Component wakeUpDeviceComponent: Component {
|
||||
Process {
|
||||
id: proc
|
||||
property string deviceId: ""
|
||||
command: busctlCall("/modules/kdeconnect/devices/" + deviceId + "/remotecontrol", "org.kde.kdeconnect.device.remotecontrol", "sendCommand", [ "a{sv}", "1", "singleclick", "b", "true" ])
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
KDEConnect.refreshDevices()
|
||||
proc.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Share file component
|
||||
property Component shareComponent: Component {
|
||||
Process {
|
||||
id: proc
|
||||
property string deviceId: ""
|
||||
property string filePath: ""
|
||||
command: busctlCall("/modules/kdeconnect/devices/" + deviceId + "/share", "org.kde.kdeconnect.device.share", "shareUrl", [ "file://" + filePath ])
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
proc.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic refresh timer
|
||||
property Timer refreshTimer: Timer {
|
||||
interval: 5000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: root.checkDaemon()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user