#!/bin/bash # # Copyright (C) 2026 Danilo M. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # One state dump for the KDE Connect module: the daemon's devices and any # pending pairing requests, as tab-separated lines on stdout. # # deviceidnametypepairedreachablepairRequestedpairRequestedByPeerkeychargecharging # requestid # # request lines come first. A daemon query that fails exits non-zero with no # output, so the caller keeps its last list rather than showing an empty one; # a query that succeeds with no devices exits zero and also prints nothing. # # The test stubs qdbus6 on PATH; see test-kdeconnect-state.sh. set -u BUS=org.kde.kdeconnect ROOT=/modules/kdeconnect QDBUS=qdbus6 # A scalar property or method value for a device, empty on any failure. prop() { "$QDBUS" "$BUS" "$ROOT/devices/$1" "org.kde.kdeconnect.device.$2" 2>/dev/null } bool() { case "$(prop "$1" "$2")" in true) printf 1 ;; *) printf 0 ;; esac } # A device name is attacker-adjacent text; a tab or newline in it must not # break the line protocol. sanitize() { printf '%s' "$1" | tr '\t\n' ' ' } emit_device() { local id="$1" name type paired reachable pr pbp key charge charging name="$(sanitize "$(prop "$id" name)")" type="$(sanitize "$(prop "$id" type)")" paired="$(bool "$id" isPaired)" reachable="$(bool "$id" isReachable)" pr="$(bool "$id" isPairRequested)" pbp="$(bool "$id" isPairRequestedByPeer)" # The key only means anything while a pairing is in flight. key="" if [[ "$pr" == 1 || "$pbp" == 1 ]]; then key="$(sanitize "$(prop "$id" verificationKey)")" fi # The battery plugin is not on every device: a desktop without a battery # object already returned qdbus an error. An absent query is an empty # field, never a zero. charge="$("$QDBUS" "$BUS" "$ROOT/devices/$id/battery" \ org.freedesktop.DBus.Properties.Get \ org.kde.kdeconnect.device.battery charge 2>/dev/null)" [[ "$charge" =~ ^[0-9]+$ ]] || charge="" charging=0 if [[ -n "$charge" ]]; then case "$("$QDBUS" "$BUS" "$ROOT/devices/$id/battery" \ org.freedesktop.DBus.Properties.Get \ org.kde.kdeconnect.device.battery isCharging 2>/dev/null)" in true) charging=1 ;; esac fi printf 'device\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ "$id" "$name" "$type" "$paired" "$reachable" "$pr" "$pbp" \ "$key" "$charge" "$charging" } main() { local ids # Command substitution carries qdbus's exit status; a failed list is the # error case, not an empty device list. ids="$("$QDBUS" "$BUS" "$ROOT" org.kde.kdeconnect.daemon.devices 2>/dev/null)" || exit 1 local reqs r reqs="$("$QDBUS" "$BUS" "$ROOT" org.freedesktop.DBus.Properties.Get \ org.kde.kdeconnect.daemon pairingRequests 2>/dev/null)" || exit 1 for r in $reqs; do printf 'request\t%s\n' "$r" done local id for id in $ids; do [[ -n "$id" ]] && emit_device "$id" done } main "$@"