mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
Compare commits
1 Commits
master
...
52068fb61b
| Author | SHA1 | Date | |
|---|---|---|---|
| 52068fb61b |
@@ -31,6 +31,8 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("DMSShell")
|
readonly property var log: Log.scoped("DMSShell")
|
||||||
readonly property var _sessionsServiceRef: SessionsService
|
readonly property var _sessionsServiceRef: SessionsService
|
||||||
|
// Keep the optional integration service alive
|
||||||
|
readonly property var _dankConnectServiceRef: DankConnectService
|
||||||
|
|
||||||
property var core: null
|
property var core: null
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Card {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
readonly property var device: DankConnectService.primaryDevice
|
||||||
|
readonly property var pairingRequest: DankConnectService.pairingRequest
|
||||||
|
readonly property bool hasBattery: !!device && typeof device.batteryCharge === "number" && device.batteryCharge >= 0
|
||||||
|
|
||||||
|
function statusText() {
|
||||||
|
if (!DankConnectService.connected)
|
||||||
|
return I18n.tr("Disconnected");
|
||||||
|
if (!device)
|
||||||
|
return I18n.tr("No devices");
|
||||||
|
const connection = device.isReachable ? I18n.tr("Connected") : I18n.tr("Disconnected");
|
||||||
|
const pairing = device.isPaired ? I18n.tr("Paired") : I18n.tr("Not paired");
|
||||||
|
return connection + " • " + pairing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function batteryText() {
|
||||||
|
if (!hasBattery)
|
||||||
|
return "";
|
||||||
|
const charge = Math.round(device.batteryCharge) + "%";
|
||||||
|
return device.batteryCharging ? charge + " • " + I18n.tr("Charging") : charge;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportError(title, response) {
|
||||||
|
if (response?.error)
|
||||||
|
ToastService.showError(title, response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: contentRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: Math.max(180, contentRow.width - actionRow.width - trailingPanel.width - contentRow.spacing * 2)
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: "smartphone"
|
||||||
|
size: 36
|
||||||
|
color: root.device?.isReachable ? Theme.primary : Theme.surfaceTextSecondary
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: parent.width - 36 - parent.spacing
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: root.device?.name || "DankConnect"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: root.statusText()
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
visible: root.hasBattery
|
||||||
|
text: root.batteryText()
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: actionRow
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: implicitWidth
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 34
|
||||||
|
iconSize: 17
|
||||||
|
iconName: "ring_volume"
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Ring")
|
||||||
|
enabled: DankConnectService.canRing(root.device)
|
||||||
|
onClicked: DankConnectService.ringDevice(root.device.id, response => root.reportError(I18n.tr("Ring"), response))
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 34
|
||||||
|
iconSize: 17
|
||||||
|
iconName: "network_ping"
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Ping")
|
||||||
|
enabled: DankConnectService.canPing(root.device)
|
||||||
|
onClicked: DankConnectService.pingDevice(root.device.id, response => root.reportError(I18n.tr("Ping"), response))
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 34
|
||||||
|
iconSize: 17
|
||||||
|
iconName: "content_paste_go"
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Send Clipboard")
|
||||||
|
enabled: DankConnectService.canSendClipboard(root.device)
|
||||||
|
onClicked: DankConnectService.sendClipboard(root.device.id, response => root.reportError(I18n.tr("Send Clipboard"), response))
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 34
|
||||||
|
iconSize: 17
|
||||||
|
iconName: "open_in_new"
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Open App")
|
||||||
|
onClicked: DankConnectService.openApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: trailingPanel
|
||||||
|
|
||||||
|
width: root.pairingRequest ? 270 : 52
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !root.pairingRequest && root.hasBattery
|
||||||
|
name: root.hasBattery ? Theme.getBatteryIcon(root.device.batteryCharge, root.device.batteryCharging, true) : "battery_std"
|
||||||
|
size: 28
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: root.pairingRequest !== null
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Pairing request from %1").arg(root.pairingRequest?.name || I18n.tr("Device"))
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: root.pairingRequest?.verificationKey || I18n.tr("Unknown")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.primary
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Reject")
|
||||||
|
buttonHeight: 30
|
||||||
|
horizontalPadding: Theme.spacingM
|
||||||
|
onClicked: DankConnectService.rejectPairing(root.pairingRequest.deviceId, response => root.reportError(I18n.tr("Failed to reject pairing"), response))
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Accept")
|
||||||
|
buttonHeight: 30
|
||||||
|
horizontalPadding: Theme.spacingM
|
||||||
|
backgroundColor: Theme.primary
|
||||||
|
textColor: Theme.primaryText
|
||||||
|
onClicked: DankConnectService.acceptPairing(root.pairingRequest.deviceId, response => root.reportError(I18n.tr("Failed to accept pairing"), response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,11 @@ Item {
|
|||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
readonly property int overviewGridHeight: 410
|
||||||
|
readonly property int dankConnectCardHeight: 124
|
||||||
|
|
||||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
implicitHeight: 410
|
implicitHeight: overviewGridHeight + Theme.spacingM + dankConnectCardHeight
|
||||||
|
|
||||||
signal switchToWeatherTab
|
signal switchToWeatherTab
|
||||||
signal switchToMediaTab
|
signal switchToMediaTab
|
||||||
@@ -89,5 +92,12 @@ Item {
|
|||||||
|
|
||||||
onClicked: root.switchToMediaTab()
|
onClicked: root.switchToMediaTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankConnectOverviewCard {
|
||||||
|
x: 0
|
||||||
|
y: root.overviewGridHeight + Theme.spacingM
|
||||||
|
width: parent.width
|
||||||
|
height: root.dankConnectCardHeight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
// Canonical DankConnect IPC client. Targets daemon apiVersion 1.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// dankgo omits zero-valued response IDs, so the subscription handshake
|
||||||
|
// must use a non-zero ID that can be matched in the response.
|
||||||
|
readonly property int subscriptionRequestId: 1
|
||||||
|
|
||||||
|
property string socketPath: {
|
||||||
|
const override = Quickshell.env("DANKCONNECT_SOCKET");
|
||||||
|
if (override && override.length > 0)
|
||||||
|
return override;
|
||||||
|
const runtimeDir = Quickshell.env("XDG_RUNTIME_DIR");
|
||||||
|
return runtimeDir ? runtimeDir + "/dankconnect.sock" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool connected: requestSocket.linkUp && ready
|
||||||
|
property bool ready: false
|
||||||
|
property bool initialized: false
|
||||||
|
property bool subscribed: false
|
||||||
|
property int apiVersion: 0
|
||||||
|
property var capabilities: []
|
||||||
|
property var subscribedTopics: []
|
||||||
|
property var devices: []
|
||||||
|
property var devicesById: ({})
|
||||||
|
property string lastError: ""
|
||||||
|
property bool launchPending: false
|
||||||
|
|
||||||
|
property int _requestId: 0
|
||||||
|
property var _pendingRequests: ({})
|
||||||
|
|
||||||
|
signal eventReceived(string topic, var data)
|
||||||
|
|
||||||
|
function _nextId() {
|
||||||
|
_requestId = _requestId >= 2147483647 ? 1 : _requestId + 1;
|
||||||
|
return _requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _parse(line) {
|
||||||
|
if (!line || line.length === 0)
|
||||||
|
return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(line);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isHello(message) {
|
||||||
|
return message && message.id === undefined && message.apiVersion !== undefined && Array.isArray(message.capabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _acceptHello(message) {
|
||||||
|
apiVersion = Number(message.apiVersion) || 0;
|
||||||
|
capabilities = message.capabilities.slice();
|
||||||
|
ready = apiVersion === 1;
|
||||||
|
if (!ready) {
|
||||||
|
lastError = "Unsupported DankConnect API version " + apiVersion;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastError = "";
|
||||||
|
launchDelayTimer.stop();
|
||||||
|
launchPending = false;
|
||||||
|
refreshDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _flushPending(message) {
|
||||||
|
const callbacks = _pendingRequests;
|
||||||
|
_pendingRequests = {};
|
||||||
|
for (const id in callbacks) {
|
||||||
|
const callback = callbacks[id];
|
||||||
|
if (!callback)
|
||||||
|
continue;
|
||||||
|
callback({
|
||||||
|
"id": Number(id),
|
||||||
|
"error": message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _applyDevices(snapshots) {
|
||||||
|
const nextDevices = [];
|
||||||
|
const nextById = {};
|
||||||
|
for (const device of (snapshots || [])) {
|
||||||
|
if (!device?.id)
|
||||||
|
continue;
|
||||||
|
nextDevices.push(device);
|
||||||
|
nextById[device.id] = device;
|
||||||
|
}
|
||||||
|
devicesById = nextById;
|
||||||
|
devices = nextDevices;
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _resolveDevice(deviceOrId) {
|
||||||
|
if (typeof deviceOrId === "string")
|
||||||
|
return devicesById[deviceOrId] || null;
|
||||||
|
return deviceOrId || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDevice(deviceId) {
|
||||||
|
return devicesById[deviceId] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// supportedPlugins is the peer's incoming view: actions this client sends.
|
||||||
|
function supports(deviceOrId, packetType) {
|
||||||
|
const device = _resolveDevice(deviceOrId);
|
||||||
|
return !!device && (device.supportedPlugins || []).includes(packetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// outgoingCapabilities is the peer's producing view: data this client consumes.
|
||||||
|
function produces(deviceOrId, packetType) {
|
||||||
|
const device = _resolveDevice(deviceOrId);
|
||||||
|
return !!device && (device.outgoingCapabilities || []).includes(packetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasCapability(family) {
|
||||||
|
return capabilities.includes(family);
|
||||||
|
}
|
||||||
|
|
||||||
|
function request(method, params, callback) {
|
||||||
|
if (!connected) {
|
||||||
|
callback?.({
|
||||||
|
"error": "daemon not connected"
|
||||||
|
});
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const family = method.split(".")[0];
|
||||||
|
if (!hasCapability(family)) {
|
||||||
|
callback?.({
|
||||||
|
"error": "daemon does not support " + family
|
||||||
|
});
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = _nextId();
|
||||||
|
const message = {
|
||||||
|
"id": id,
|
||||||
|
"method": method,
|
||||||
|
"params": params || {}
|
||||||
|
};
|
||||||
|
if (callback)
|
||||||
|
_pendingRequests[id] = callback;
|
||||||
|
requestSocket.send(message);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshDevices(callback) {
|
||||||
|
return request("device.list", {}, response => {
|
||||||
|
if (!response.error)
|
||||||
|
_applyDevices(response.result || []);
|
||||||
|
callback?.(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function launch() {
|
||||||
|
if (connected || requestSocket.linkUp || launchPending)
|
||||||
|
return;
|
||||||
|
launchPending = true;
|
||||||
|
launchDelayTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleRequestLine(line) {
|
||||||
|
const message = _parse(line);
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
if (_isHello(message)) {
|
||||||
|
_acceptHello(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.id === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const callback = _pendingRequests[message.id];
|
||||||
|
if (!callback)
|
||||||
|
return;
|
||||||
|
delete _pendingRequests[message.id];
|
||||||
|
callback(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleSubscriptionLine(line) {
|
||||||
|
const message = _parse(line);
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
if (_isHello(message)) {
|
||||||
|
if (Number(message.apiVersion) !== 1) {
|
||||||
|
lastError = "Unsupported DankConnect subscription API version " + message.apiVersion;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
subscriptionSocket.send({
|
||||||
|
"id": root.subscriptionRequestId,
|
||||||
|
"method": "subscribe",
|
||||||
|
"params": {}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.id === root.subscriptionRequestId) {
|
||||||
|
subscribed = !message.error;
|
||||||
|
subscribedTopics = message.result?.topics || [];
|
||||||
|
if (message.error)
|
||||||
|
lastError = message.error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const topic = message.event || message.topic || "";
|
||||||
|
if (!topic)
|
||||||
|
return;
|
||||||
|
if (topic === "devices")
|
||||||
|
_applyDevices(message.data || []);
|
||||||
|
eventReceived(topic, message.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSocket {
|
||||||
|
id: requestSocket
|
||||||
|
|
||||||
|
path: root.socketPath
|
||||||
|
connected: root.enabled && root.socketPath.length > 0
|
||||||
|
reconnectBaseMs: 3000
|
||||||
|
reconnectMaxMs: 3000
|
||||||
|
|
||||||
|
onConnectionStateChanged: {
|
||||||
|
if (linkUp)
|
||||||
|
return;
|
||||||
|
root.ready = false;
|
||||||
|
root.initialized = false;
|
||||||
|
root.subscribed = false;
|
||||||
|
root.subscribedTopics = [];
|
||||||
|
root.apiVersion = 0;
|
||||||
|
root.capabilities = [];
|
||||||
|
root._flushPending("daemon disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
parser: SplitParser {
|
||||||
|
onRead: line => root._handleRequestLine(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankSocket {
|
||||||
|
id: subscriptionSocket
|
||||||
|
|
||||||
|
path: root.socketPath
|
||||||
|
connected: root.enabled && root.ready
|
||||||
|
reconnectBaseMs: 3000
|
||||||
|
reconnectMaxMs: 3000
|
||||||
|
|
||||||
|
onConnectionStateChanged: {
|
||||||
|
root.subscribed = false;
|
||||||
|
root.subscribedTopics = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
parser: SplitParser {
|
||||||
|
onRead: line => root._handleSubscriptionLine(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: launchDelayTimer
|
||||||
|
|
||||||
|
interval: 1000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (root.connected || requestSocket.linkUp) {
|
||||||
|
root.launchPending = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Quickshell.execDetached(["sh", "-c", "if command -v dankconnect >/dev/null 2>&1; then exec dankconnect daemon; elif command -v flatpak >/dev/null 2>&1 && flatpak info com.danklinux.dankconnect >/dev/null 2>&1; then exec flatpak run --command=dankconnect com.danklinux.dankconnect daemon; fi"]);
|
||||||
|
launchResetTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: launchResetTimer
|
||||||
|
|
||||||
|
interval: 5000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.launchPending = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property bool connected: backend.connected && backend.subscribed
|
||||||
|
readonly property bool initialized: backend.initialized
|
||||||
|
readonly property int apiVersion: backend.apiVersion
|
||||||
|
readonly property var capabilities: backend.capabilities
|
||||||
|
readonly property var devices: backend.devices
|
||||||
|
readonly property var devicesById: backend.devicesById
|
||||||
|
readonly property string lastError: backend.lastError
|
||||||
|
readonly property var primaryDevice: _selectPrimaryDevice()
|
||||||
|
property var pairingRequest: null
|
||||||
|
property bool _openAppPending: false
|
||||||
|
|
||||||
|
Component.onCompleted: backend.launch()
|
||||||
|
|
||||||
|
onConnectedChanged: {
|
||||||
|
if (!connected) {
|
||||||
|
pairingRequest = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_openAppPending) {
|
||||||
|
_openAppPending = false;
|
||||||
|
_launchAttachedApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _selectPrimaryDevice() {
|
||||||
|
let selected = null;
|
||||||
|
let selectedScore = -1;
|
||||||
|
for (const device of devices) {
|
||||||
|
const score = (device.isPaired ? 2 : 0) + (device.isReachable ? 1 : 0);
|
||||||
|
if (score <= selectedScore)
|
||||||
|
continue;
|
||||||
|
selected = device;
|
||||||
|
selectedScore = score;
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _syncPairingRequest() {
|
||||||
|
for (const device of devices) {
|
||||||
|
if (!device.isPairRequestedByPeer)
|
||||||
|
continue;
|
||||||
|
pairingRequest = {
|
||||||
|
"deviceId": device.id,
|
||||||
|
"name": device.name || "",
|
||||||
|
"verificationKey": device.verificationKey || ""
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (initialized)
|
||||||
|
pairingRequest = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleEvent(topic, data) {
|
||||||
|
if (topic !== "pairing" || !data?.deviceId)
|
||||||
|
return;
|
||||||
|
pairingRequest = {
|
||||||
|
"deviceId": data.deviceId,
|
||||||
|
"name": data.name || "",
|
||||||
|
"verificationKey": data.verificationKey || ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function supports(deviceOrId, packetType) {
|
||||||
|
return backend.supports(deviceOrId, packetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canRing(deviceOrId) {
|
||||||
|
const device = typeof deviceOrId === "string" ? backend.getDevice(deviceOrId) : deviceOrId;
|
||||||
|
return connected && !!device?.isPaired && !!device?.isReachable && backend.hasCapability("device") && supports(device, "kdeconnect.findmyphone.request");
|
||||||
|
}
|
||||||
|
|
||||||
|
function canPing(deviceOrId) {
|
||||||
|
const device = typeof deviceOrId === "string" ? backend.getDevice(deviceOrId) : deviceOrId;
|
||||||
|
return connected && !!device?.isPaired && !!device?.isReachable && backend.hasCapability("ping") && supports(device, "kdeconnect.ping");
|
||||||
|
}
|
||||||
|
|
||||||
|
function canSendClipboard(deviceOrId) {
|
||||||
|
const device = typeof deviceOrId === "string" ? backend.getDevice(deviceOrId) : deviceOrId;
|
||||||
|
return connected && !!device?.isPaired && !!device?.isReachable && backend.hasCapability("clipboard") && supports(device, "kdeconnect.clipboard");
|
||||||
|
}
|
||||||
|
|
||||||
|
function ringDevice(deviceId, callback) {
|
||||||
|
if (!canRing(deviceId)) {
|
||||||
|
callback?.({
|
||||||
|
"error": "ring unavailable"
|
||||||
|
});
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return backend.request("device.ring", {
|
||||||
|
"deviceId": deviceId
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pingDevice(deviceId, callback) {
|
||||||
|
if (!canPing(deviceId)) {
|
||||||
|
callback?.({
|
||||||
|
"error": "ping unavailable"
|
||||||
|
});
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return backend.request("ping.send", {
|
||||||
|
"deviceId": deviceId
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendClipboard(deviceId, callback) {
|
||||||
|
if (!canSendClipboard(deviceId)) {
|
||||||
|
callback?.({
|
||||||
|
"error": "clipboard unavailable"
|
||||||
|
});
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return backend.request("clipboard.send", {
|
||||||
|
"deviceId": deviceId,
|
||||||
|
"content": ""
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function acceptPairing(deviceId, callback) {
|
||||||
|
return _respondToPairing("pairing.accept", deviceId, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rejectPairing(deviceId, callback) {
|
||||||
|
return _respondToPairing("pairing.reject", deviceId, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openApp() {
|
||||||
|
if (!connected) {
|
||||||
|
_openAppPending = true;
|
||||||
|
backend.launch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_launchAttachedApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _launchAttachedApp() {
|
||||||
|
Quickshell.execDetached(["sh", "-c", "if command -v dankconnect >/dev/null 2>&1; then exec dankconnect app; elif command -v flatpak >/dev/null 2>&1 && flatpak info com.danklinux.dankconnect >/dev/null 2>&1; then exec flatpak run --command=dankconnect com.danklinux.dankconnect app; fi"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _respondToPairing(method, deviceId, callback) {
|
||||||
|
if (!connected || !backend.hasCapability("pairing") || !deviceId) {
|
||||||
|
callback?.({
|
||||||
|
"error": "pairing unavailable"
|
||||||
|
});
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return backend.request(method, {
|
||||||
|
"deviceId": deviceId
|
||||||
|
}, response => {
|
||||||
|
if (!response.error && pairingRequest?.deviceId === deviceId)
|
||||||
|
pairingRequest = null;
|
||||||
|
callback?.(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DankConnectBackend {
|
||||||
|
id: backend
|
||||||
|
|
||||||
|
enabled: true
|
||||||
|
onDevicesChanged: root._syncPairingRequest()
|
||||||
|
onEventReceived: (topic, data) => root._handleEvent(topic, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user