aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/kdeconnect/kdeconnect-state.sh
blob: 1bf3e4ddf76ce6086d6fefa44ec81bb667fb1f6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/bin/bash
#
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
#
# 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.
#
#   device<TAB>id<TAB>name<TAB>type<TAB>paired<TAB>reachable<TAB>pairRequested<TAB>pairRequestedByPeer<TAB>key<TAB>charge<TAB>charging
#   request<TAB>id
#
# 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 "$@"