aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/network/NetworkModule.qml
blob: 6ff98b5b58f7813c4dce693f9fa0140ff88bfe73 (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
107
108
109
110
111
112
113
114
115
116
117
// 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.

import Quickshell
import Quickshell.Networking
import Quickshell.Io
import QtQuick
import "../.."

// Always active: the backends push and there is no poll to gate, so the
// state the tile shows is live from launch. Referencing Networking here is
// also what instantiates the singleton at shell start.
Module {
    id: mod

    name: "network"
    label: "Network"
    alwaysActive: true

    // .values on the device ObjectModel; empty until the backend is ready,
    // roughly two seconds in. Bindings repaint when it arrives.
    readonly property var devices: Networking.devices ? Networking.devices.values : []
    readonly property var wiredDevices: devices.filter(d => d.type === DeviceType.Wired)
    readonly property var wifiDevice: devices.find(d => d.type === DeviceType.Wifi) ?? null

    readonly property bool wiredUp: wiredDevices.some(d => d.connected)
    readonly property bool wifiUp: wifiDevice ? wifiDevice.connected : false
    readonly property bool wifiOn: Networking.wifiEnabled
    readonly property var wifiNetworks: wifiDevice && wifiDevice.networks ? wifiDevice.networks.values : []

    readonly property string wifiSsid: {
        const n = wifiNetworks.find(x => x.connected);
        return n ? n.name : "";
    }
    readonly property string wiredName: {
        const d = wiredDevices.find(x => x.connected);
        return d ? d.name : "";
    }

    // Connected first, then known, then strongest. Kept here so both the
    // tile and the page read the same order.
    readonly property var wifiNetworksSorted: {
        const arr = wifiNetworks.slice();
        arr.sort((a, b) =>
            ((b.connected ? 1 : 0) - (a.connected ? 1 : 0)) ||
            ((b.known ? 1 : 0) - (a.known ? 1 : 0)) ||
            ((b.signalStrength ?? 0) - (a.signalStrength ?? 0)));
        return arr;
    }

    // The icon follows the active link, live, with no contract change: the
    // drawer reads this property like any other.
    icon: mod.wiredUp ? "\uf796" : (mod.wifiUp ? "\uf1eb" : "\uf05e")

    // Active while any link is up.
    active: mod.wiredUp || mod.wifiUp

    // The network a connect is pending on, so connectionFailed can be caught.
    property var pendingNetwork: null
    property string error: ""

    function connect(net) {
        mod.pendingNetwork = net;
        mod.error = "";
        net.connect();
    }

    function connectWithPsk(net, psk) {
        mod.pendingNetwork = net;
        mod.error = "";
        net.connectWithPsk(psk);
    }

    function disconnectNetwork(net) { mod.error = ""; if (net) net.disconnect(); }
    function forget(net) { mod.error = ""; if (net) net.forget(); }

    // A failure after the drawer closed would otherwise go unseen, so it also
    // raises a notification. Same cached-Process shape as VmModule.
    function notify(title, body) {
        notifyProc.command = ["notify-send", "--app-name=network",
                              "--urgency=critical", "--icon=error", title, body];
        notifyProc.running = false;
        notifyProc.running = true;
    }

    property Process notifyProc: Process {}

    property Connections conn: Connections {
        target: mod.pendingNetwork
        function onConnectionFailed(reason) {
            mod.error = "Could not connect to " + (mod.pendingNetwork?.name ?? "network");
            mod.notify("Network", mod.error);
            mod.pendingNetwork = null;
        }
        function onConnectedChanged() {
            if (mod.pendingNetwork && mod.pendingNetwork.connected)
                mod.pendingNetwork = null;
        }
    }

    tileContent: Component { NetworkTile { net: mod } }

    page: Component {
        Page {
            title: "Network"
            NetworkPage { width: parent.width; net: mod }
        }
    }
}