summaryrefslogtreecommitdiff
path: root/overlays
diff options
context:
space:
mode:
Diffstat (limited to 'overlays')
-rw-r--r--overlays/changedetection-io.nix7
-rw-r--r--overlays/etesync-dav.nix49
-rw-r--r--overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py22
-rw-r--r--overlays/niri.nix1
-rw-r--r--overlays/nix-output-monitor.nix6
-rw-r--r--overlays/postfix-mta-sts-resolver/default.nix2
-rw-r--r--overlays/prometheus-lvm-exporter.nix2
-rw-r--r--overlays/quickshell/default.nix14
-rw-r--r--overlays/quickshell/io.patch13
-rw-r--r--overlays/quickshell/lock-state-changed.patch12
-rw-r--r--overlays/quickshell/pipewire.patch488
-rw-r--r--overlays/spice-record.nix2
-rw-r--r--overlays/spm/default.nix3
-rw-r--r--overlays/spm/lib/Spm/Api.hs19
-rw-r--r--overlays/spm/server/Spm/Server.hs7
-rw-r--r--overlays/spm/server/Spm/Server/Ctx.hs3
-rw-r--r--overlays/spm/server/Spm/Server/Database.hs9
-rw-r--r--overlays/swayosd/default.nix13
-rw-r--r--overlays/swayosd/exponential.patch57
-rw-r--r--overlays/uucp/default.nix9
-rw-r--r--overlays/uucp/mailprogram.patch16
-rw-r--r--overlays/waybar.nix8
-rwxr-xr-xoverlays/worktime/worktime/__main__.py91
-rw-r--r--overlays/yt-dlp.nix10
-rw-r--r--overlays/zte-prometheus-exporter/default.nix2
-rw-r--r--overlays/zte-prometheus-exporter/zte-prometheus-exporter.py90
26 files changed, 722 insertions, 233 deletions
diff --git a/overlays/changedetection-io.nix b/overlays/changedetection-io.nix
new file mode 100644
index 00000000..95e8985e
--- /dev/null
+++ b/overlays/changedetection-io.nix
@@ -0,0 +1,7 @@
1{ final, prev, ... }: {
2 changedetection-io = prev.changedetection-io.overrideAttrs (oldAttrs: {
3 propagatedBuildInputs = (oldAttrs.propagatedBuildInputs or []) ++ (with final.python3.pkgs; [
4 playwright
5 ]);
6 });
7}
diff --git a/overlays/etesync-dav.nix b/overlays/etesync-dav.nix
index cec216e2..e0ced1e3 100644
--- a/overlays/etesync-dav.nix
+++ b/overlays/etesync-dav.nix
@@ -1,55 +1,58 @@
1{ final, prev, ... }: { 1{ final, prev, ... }: {
2 etesync-dav = prev.python3Packages.buildPythonApplication rec { 2 etesync-dav = final.python3Packages.buildPythonApplication rec {
3 pname = "etesync-dav"; 3 pname = "etesync-dav";
4 version = "0.33.4"; 4 version = "0.35.1";
5 pyproject = true;
5 6
6 src = prev.fetchFromGitHub { 7 src = prev.fetchFromGitHub {
7 owner = "etesync"; 8 owner = "etesync";
8 repo = "etesync-dav"; 9 repo = "etesync-dav";
9 rev = "v${version}"; 10 tag = "v${version}";
10 hash = "sha256-g+rK762tSWPDaBsaTwpTzfK/lqVs+Z/Qrpq2HCpipQE="; 11 hash = "sha256-y4BhU2kSn+RWqc5+pJQFhbwfat9cMWD0ED0EXJp25cY=";
11 }; 12 };
12 13
13 dependencies = with prev.python3Packages; [ 14 build-system = with final.python3Packages; [ setuptools ];
15
16 dependencies = with final.python3Packages; [
14 appdirs 17 appdirs
15 etebase 18 etebase
16 etesync 19 etesync
17 flask 20 flask
18 flask-wtf 21 flask-wtf
19 msgpack 22 msgpack
20 setuptools 23 requests
21 (toPythonModule (buildPythonApplication rec { 24 requests.optional-dependencies.socks
25 (buildPythonApplication rec {
22 pname = "radicale"; 26 pname = "radicale";
23 version = "3.2.3"; 27 version = "3.2.0";
24 pyproject = true; 28 pyproject = true;
25 29
26 src = prev.fetchFromGitHub { 30 src = prev.fetchFromGitHub {
27 owner = "Kozea"; 31 owner = "Kozea";
28 repo = "Radicale"; 32 repo = "Radicale";
29 rev = "refs/tags/v${version}"; 33 rev = "refs/tags/v${version}";
30 hash = "sha256-1IlnXVetQQuKBt6+QVKNeMM6qBQAiUhqc+4x3xOnSdE="; 34 hash = "sha256-RxC8VOfdTXJZiAroDHTKjJqGWu65Z5uyb4WK1LOqubQ=";
31 }; 35 };
32 36
37 postPatch = ''
38 sed -i '/addopts/d' setup.cfg
39 '';
40
33 build-system = [ 41 build-system = [
34 setuptools 42 setuptools
35 ]; 43 ];
36 44
37 dependencies = 45 dependencies = [
38 [ 46 defusedxml
39 defusedxml 47 passlib
40 passlib 48 vobject
41 vobject 49 pika
42 pika 50 python-dateutil
43 python-dateutil 51 pytz # https://github.com/Kozea/Radicale/issues/816
44 pytz # https://github.com/Kozea/Radicale/issues/816 52 ] ++ passlib.optional-dependencies.bcrypt;
45 ]
46 ++ passlib.optional-dependencies.bcrypt;
47 53
48 doCheck = false; 54 doCheck = false;
49 })) 55 })
50 requests
51 types-setuptools
52 requests.optional-dependencies.socks
53 ]; 56 ];
54 57
55 doCheck = false; 58 doCheck = false;
diff --git a/overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py b/overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py
index 484228c8..60ef4670 100644
--- a/overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py
+++ b/overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py
@@ -42,7 +42,7 @@ class NFTMetrics:
42 cls._instance = cls.__new__(cls) 42 cls._instance = cls.__new__(cls)
43 cls._instance.attrs = None 43 cls._instance.attrs = None
44 return cls._instance 44 return cls._instance
45 45
46 46
47 def __init__(self): 47 def __init__(self):
48 raise RuntimeError('Call instance() instead') 48 raise RuntimeError('Call instance() instead')
@@ -62,7 +62,7 @@ class NFTMetrics:
62 raise RuntimeError(f'nftables json schema v{version} is not supported') 62 raise RuntimeError(f'nftables json schema v{version} is not supported')
63 queries[query_name] = data['nftables'][1:] 63 queries[query_name] = data['nftables'][1:]
64 64
65 65
66 def extract_query(query_name, type_name): 66 def extract_query(query_name, type_name):
67 return [ 67 return [
68 item[type_name] 68 item[type_name]
@@ -98,21 +98,21 @@ class NFTMetrics:
98 metrics += _format_prom_metrics('nftables_counter_packets_count', 'counter', counter_packets) 98 metrics += _format_prom_metrics('nftables_counter_packets_count', 'counter', counter_packets)
99 99
100 map_counts = [] 100 map_counts = []
101 for meter in self.attrs['maps']: 101 for item in self.attrs['maps']:
102 labels = { k: v for k, v in counter.items() if k not in set(['elem']) } 102 labels = { k: v for k, v in counter.items() if k not in set(['elem']) }
103 map_counts += [(labels, len(meter['elem']))] 103 map_counts += [(labels, len(item['elem']) if 'elem' in item else 0)]
104 metrics += _format_prom_metrics('nftables_map_elem_count', 'gauge', map_counts) 104 metrics += _format_prom_metrics('nftables_map_elem_count', 'gauge', map_counts)
105 105
106 meter_counts = [] 106 meter_counts = []
107 for meter in self.attrs['meters']: 107 for item in self.attrs['meters']:
108 labels = { k: v for k, v in counter.items() if k not in set(['elem']) } 108 labels = { k: v for k, v in counter.items() if k not in set(['elem']) }
109 meter_counts += [(labels, len(meter['elem']))] 109 item_counts += [(labels, len(item['elem']) if 'elem' in item else 0)]
110 metrics += _format_prom_metrics('nftables_meter_elem_count', 'gauge', meter_counts) 110 metrics += _format_prom_metrics('nftables_meter_elem_count', 'gauge', meter_counts)
111 111
112 set_counts = [] 112 set_counts = []
113 for meter in self.attrs['sets']: 113 for item in self.attrs['sets']:
114 labels = { k: v for k, v in counter.items() if k not in set(['elem']) } 114 labels = { k: v for k, v in counter.items() if k not in set(['elem']) }
115 set_counts += [(labels, len(meter['elem']))] 115 set_counts += [(labels, len(item['elem']) if 'elem' in item else 0)]
116 metrics += _format_prom_metrics('nftables_set_elem_count', 'gauge', set_counts) 116 metrics += _format_prom_metrics('nftables_set_elem_count', 'gauge', set_counts)
117 117
118 return metrics.encode('utf-8') 118 return metrics.encode('utf-8')
@@ -120,7 +120,7 @@ class NFTMetrics:
120class NFTMetricsServer(BaseHTTPRequestHandler): 120class NFTMetricsServer(BaseHTTPRequestHandler):
121 def log_message(self, format, *args): 121 def log_message(self, format, *args):
122 pass 122 pass
123 123
124 def do_GET(self): 124 def do_GET(self):
125 nft_metrics = NFTMetrics.instance() 125 nft_metrics = NFTMetrics.instance()
126 nft_metrics.update() 126 nft_metrics.update()
@@ -138,7 +138,7 @@ class NFTMetricsServer(BaseHTTPRequestHandler):
138 self.send_response(200) 138 self.send_response(200)
139 self.send_header("Content-type", "text/plain") 139 self.send_header("Content-type", "text/plain")
140 self.end_headers() 140 self.end_headers()
141 141
142 self.wfile.write(nft_metrics.prometheus()) 142 self.wfile.write(nft_metrics.prometheus())
143 case _: 143 case _:
144 self.send_response(404) 144 self.send_response(404)
diff --git a/overlays/niri.nix b/overlays/niri.nix
index 9188ed7d..95a918b0 100644
--- a/overlays/niri.nix
+++ b/overlays/niri.nix
@@ -3,6 +3,7 @@
3 (final: prev: { 3 (final: prev: {
4 niri-unstable = prev.niri-unstable.overrideAttrs (oldAttrs: { 4 niri-unstable = prev.niri-unstable.overrideAttrs (oldAttrs: {
5 buildInputs = (oldAttrs.buildInputs or []) ++ [ final.libgbm ]; 5 buildInputs = (oldAttrs.buildInputs or []) ++ [ final.libgbm ];
6 doCheck = false;
6 }); 7 });
7 }) 8 })
8 final prev 9 final prev
diff --git a/overlays/nix-output-monitor.nix b/overlays/nix-output-monitor.nix
new file mode 100644
index 00000000..a15913ef
--- /dev/null
+++ b/overlays/nix-output-monitor.nix
@@ -0,0 +1,6 @@
1{ final, prev, sources, ... }:
2{
3 nix-output-monitor = prev.nix-output-monitor.overrideAttrs (oldAttrs: prev.lib.optionalAttrs (prev.lib.versionAtLeast prev.ghc.version "9.10.1") {
4 inherit (sources.nix-output-monitor) version src;
5 });
6}
diff --git a/overlays/postfix-mta-sts-resolver/default.nix b/overlays/postfix-mta-sts-resolver/default.nix
index 52ab2d40..c210cf79 100644
--- a/overlays/postfix-mta-sts-resolver/default.nix
+++ b/overlays/postfix-mta-sts-resolver/default.nix
@@ -11,7 +11,7 @@
11 projectDir = cleanPythonSources { 11 projectDir = cleanPythonSources {
12 src = prev.runCommand "sources" {} '' 12 src = prev.runCommand "sources" {} ''
13 mkdir $out 13 mkdir $out
14 cp -r ${sources.postfix-mta-sts-resolver.src}/. $out 14 cp -r --no-preserve=all ${sources.postfix-mta-sts-resolver.src}/. $out
15 15
16 cp ${./pyproject.toml} $out/pyproject.toml 16 cp ${./pyproject.toml} $out/pyproject.toml
17 cp ${./poetry.lock} $out/poetry.lock 17 cp ${./poetry.lock} $out/poetry.lock
diff --git a/overlays/prometheus-lvm-exporter.nix b/overlays/prometheus-lvm-exporter.nix
index 240f8d85..72d9c7ca 100644
--- a/overlays/prometheus-lvm-exporter.nix
+++ b/overlays/prometheus-lvm-exporter.nix
@@ -3,7 +3,7 @@
3 pname = "prometheus-lvm-exporter"; 3 pname = "prometheus-lvm-exporter";
4 inherit (sources.prometheus-lvm-exporter) version src; 4 inherit (sources.prometheus-lvm-exporter) version src;
5 5
6 vendorHash = "sha256-z/fV0PzoWSDTJ44En19o7zJPPPox5ymFw7sw0Ab9t00="; 6 vendorHash = "sha256-CoTNTCBBugbHWDsOuZY1t8HrpdmEbbSMyVb3+1u0q+g=";
7 7
8 doCheck = false; 8 doCheck = false;
9 9
diff --git a/overlays/quickshell/default.nix b/overlays/quickshell/default.nix
new file mode 100644
index 00000000..7c4a263d
--- /dev/null
+++ b/overlays/quickshell/default.nix
@@ -0,0 +1,14 @@
1{ final, prev, sources, ... }:
2{
3 quickshell = prev.quickshell.overrideAttrs (oldAttrs: {
4 inherit (sources.quickshell) version src;
5
6 buildInputs = (oldAttrs.buildInputs or []) ++ [ final.polkit.dev ];
7
8 patches = (oldAttrs.patches or []) ++ [
9 ./lock-state-changed.patch
10 ./pipewire.patch
11 ./io.patch
12 ];
13 });
14}
diff --git a/overlays/quickshell/io.patch b/overlays/quickshell/io.patch
new file mode 100644
index 00000000..961bdcaf
--- /dev/null
+++ b/overlays/quickshell/io.patch
@@ -0,0 +1,13 @@
1diff --git i/src/io/socket.cpp w/src/io/socket.cpp
2index 371f687..d12eaeb 100644
3--- i/src/io/socket.cpp
4+++ w/src/io/socket.cpp
5@@ -66,7 +66,7 @@ void Socket::onSocketDisconnected() {
6 }
7
8 void Socket::onSocketError(QLocalSocket::LocalSocketError error) {
9- qCWarning(logSocket) << "Socket error for" << this << error;
10+ // qCWarning(logSocket) << "Socket error for" << this << error;
11 emit this->error(error);
12 }
13
diff --git a/overlays/quickshell/lock-state-changed.patch b/overlays/quickshell/lock-state-changed.patch
new file mode 100644
index 00000000..4be273fa
--- /dev/null
+++ b/overlays/quickshell/lock-state-changed.patch
@@ -0,0 +1,12 @@
1diff --git i/src/wayland/session_lock.cpp w/src/wayland/session_lock.cpp
2index 0ecf9ec..3dbd19b 100644
3--- i/src/wayland/session_lock.cpp
4+++ w/src/wayland/session_lock.cpp
5@@ -127,6 +127,7 @@ void WlSessionLock::realizeLockTarget(WlSessionLock* old) {
6 this->updateSurfaces(false);
7
8 if (!this->manager->lock()) this->lockTarget = false;
9+ emit this->lockStateChanged();
10
11 this->updateSurfaces(true, old);
12 } else {
diff --git a/overlays/quickshell/pipewire.patch b/overlays/quickshell/pipewire.patch
new file mode 100644
index 00000000..2d98eefc
--- /dev/null
+++ b/overlays/quickshell/pipewire.patch
@@ -0,0 +1,488 @@
1diff --git i/src/services/pipewire/device.cpp w/src/services/pipewire/device.cpp
2index 616e7d0..0c55008 100644
3--- i/src/services/pipewire/device.cpp
4+++ w/src/services/pipewire/device.cpp
5@@ -3,6 +3,7 @@
6 #include <cstdint>
7 #include <functional>
8 #include <utility>
9+#include <algorithm>
10
11 #include <pipewire/device.h>
12 #include <qcontainerfwd.h>
13@@ -19,6 +20,8 @@
14 #include <spa/pod/pod.h>
15 #include <spa/pod/vararg.h>
16 #include <spa/utils/type.h>
17+#include <spa/monitor/device.h>
18+#include <spa/utils/keys.h>
19
20 #include "../../core/logcat.hpp"
21 #include "core.hpp"
22@@ -46,6 +49,25 @@ void PwDevice::unbindHooks() {
23 this->mWaitingForDevice = false;
24 }
25
26+void PwDevice::initProps(const spa_dict* props) {
27+ if (const auto* deviceName = spa_dict_lookup(props, SPA_KEY_DEVICE_NAME)) {
28+ this->name = deviceName;
29+ }
30+
31+ if (const auto* deviceDesc = spa_dict_lookup(props, SPA_KEY_DEVICE_DESCRIPTION)) {
32+ this->description = deviceDesc;
33+ }
34+
35+ if (const auto* deviceNick = spa_dict_lookup(props, SPA_KEY_DEVICE_NICK)) {
36+ this->nick = deviceNick;
37+ }
38+
39+ if (const auto* mediaClass = spa_dict_lookup(props, SPA_KEY_MEDIA_CLASS)) {
40+ this->type = mediaClass;
41+ }
42+}
43+
44+
45 const pw_device_events PwDevice::EVENTS = {
46 .version = PW_VERSION_DEVICE_EVENTS,
47 .info = &PwDevice::onInfo,
48@@ -71,6 +93,11 @@ void PwDevice::onInfo(void* data, const pw_device_info* info) {
49 }
50
51 break;
52+ } else if (param.id == SPA_PARAM_EnumProfile && param.flags & SPA_PARAM_INFO_READ) {
53+ self->validProfiles.clear();
54+ pw_device_enum_params(self->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
55+ } else if (param.id == SPA_PARAM_Profile && param.flags & SPA_PARAM_INFO_READ) {
56+ pw_device_enum_params(self->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
57 }
58 }
59 }
60@@ -97,6 +124,15 @@ void PwDevice::onParam(
61 }
62
63 self->addDeviceIndexPairs(param);
64+ } else if (id == SPA_PARAM_EnumProfile) {
65+ PwProfile profile = PwProfile::parseSpaPod(param);
66+ self->profilesUpdated = true;
67+ self->profiles.insertOrAssign(profile.index, profile);
68+ self->validProfiles.insert(profile.index);
69+ } else if (id == SPA_PARAM_Profile) {
70+ PwProfile profile = PwProfile::parseSpaPod(param);
71+ self->currentProfileUpdated = true;
72+ self->currentProfile = profile;
73 }
74 }
75
76@@ -145,6 +181,21 @@ void PwDevice::polled() {
77 return false;
78 });
79 }
80+ if (this->profilesUpdated) {
81+ this->profiles.removeIf([&](const std::pair<qint32, PwProfile>& entry) {
82+ return !this->validProfiles.contains(entry.first);
83+ });
84+ this->profilesUpdated = false;
85+ QList<PwProfile> profiles = this->profiles.values();
86+ std::sort(profiles.begin(), profiles.end(), [](const PwProfile& a, const PwProfile& b) { return a.index < b.index; });
87+ emit this->profilesChanged(profiles);
88+ }
89+ if (this->currentProfileUpdated) {
90+ this->currentProfileUpdated = false;
91+ if (this->currentProfile) {
92+ emit this->currentProfileChanged(*this->currentProfile);
93+ }
94+ }
95 }
96
97 bool PwDevice::setVolumes(qint32 routeDevice, const QVector<float>& volumes) {
98@@ -182,6 +233,15 @@ bool PwDevice::setMuted(qint32 routeDevice, bool muted) {
99 });
100 }
101
102+void PwDevice::setProfile(qint32 profileIndex) {
103+ auto buffer = std::array<uint8_t, 1024>();
104+ auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
105+ auto* pod = spa_pod_builder_add_object(&builder,
106+ SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile,
107+ SPA_PARAM_PROFILE_index, SPA_POD_Int(profileIndex));
108+ pw_device_set_param(this->proxy(), SPA_PARAM_Profile, 0, static_cast<spa_pod*>(pod));
109+}
110+
111 void PwDevice::waitForDevice() { this->mWaitingForDevice = true; }
112 bool PwDevice::waitingForDevice() const { return this->mWaitingForDevice; }
113
114@@ -222,4 +282,24 @@ bool PwDevice::setRouteProps(
115 return true;
116 }
117
118+PwProfile PwProfile::parseSpaPod(const spa_pod* param) {
119+ PwProfile profile;
120+
121+ const auto* indexProp = spa_pod_find_prop(param, nullptr, SPA_PARAM_PROFILE_index);
122+ const auto* descProp = spa_pod_find_prop(param, nullptr, SPA_PARAM_PROFILE_description);
123+ const auto* nameProp = spa_pod_find_prop(param, nullptr, SPA_PARAM_PROFILE_name);
124+
125+ spa_pod_get_int(&indexProp->value, &profile.index);
126+
127+ const char* desc_cstr = nullptr;
128+ spa_pod_get_string(&descProp->value, &desc_cstr);
129+ profile.description = QString(desc_cstr);
130+
131+ const char* name_cstr = nullptr;
132+ spa_pod_get_string(&nameProp->value, &name_cstr);
133+ profile.name = QString(name_cstr);
134+
135+ return profile;
136+}
137+
138 } // namespace qs::service::pipewire
139diff --git i/src/services/pipewire/device.hpp w/src/services/pipewire/device.hpp
140index 1a1f705..ee64858 100644
141--- i/src/services/pipewire/device.hpp
142+++ w/src/services/pipewire/device.hpp
143@@ -1,6 +1,7 @@
144 #pragma once
145
146 #include <functional>
147+#include <optional>
148
149 #include <pipewire/core.h>
150 #include <pipewire/device.h>
151@@ -17,6 +18,20 @@
152
153 namespace qs::service::pipewire {
154
155+struct PwProfile {
156+ Q_GADGET;
157+ Q_PROPERTY(qint32 index MEMBER index)
158+ Q_PROPERTY(QString description MEMBER description)
159+ Q_PROPERTY(QString name MEMBER name)
160+
161+public:
162+ qint32 index;
163+ QString description;
164+ QString name;
165+
166+ static PwProfile parseSpaPod(const spa_pod* param);
167+};
168+
169 class PwDevice;
170
171 class PwDevice: public PwBindable<pw_device, PW_TYPE_INTERFACE_Device, PW_VERSION_DEVICE> {
172@@ -25,6 +40,12 @@ class PwDevice: public PwBindable<pw_device, PW_TYPE_INTERFACE_Device, PW_VERSIO
173 public:
174 void bindHooks() override;
175 void unbindHooks() override;
176+ void initProps(const spa_dict* props) override;
177+
178+ QString name;
179+ QString description;
180+ QString nick;
181+ QString type;
182
183 bool setVolumes(qint32 routeDevice, const QVector<float>& volumes);
184 bool setMuted(qint32 routeDevice, bool muted);
185@@ -32,9 +53,16 @@ public:
186 void waitForDevice();
187 [[nodiscard]] bool waitingForDevice() const;
188
189+ void setProfile(qint32 profileIndex);
190+
191+ QHash<qint32, PwProfile> profiles;
192+ std::optional<PwProfile> currentProfile;
193+
194 signals:
195 void deviceReady();
196 void routeVolumesChanged(qint32 routeDevice, const PwVolumeProps& volumeProps);
197+ void profilesChanged(QList<PwProfile> profiles);
198+ void currentProfileChanged(PwProfile profile);
199
200 private slots:
201 void polled();
202@@ -49,6 +77,11 @@ private:
203 QList<qint32> stagingIndexes;
204 void addDeviceIndexPairs(const spa_pod* param);
205
206+ bool profilesUpdated = false;
207+ QSet<qint32> validProfiles;
208+
209+ bool currentProfileUpdated = false;
210+
211 bool
212 setRouteProps(qint32 routeDevice, const std::function<void*(spa_pod_builder*)>& propsCallback);
213
214diff --git i/src/services/pipewire/node.cpp w/src/services/pipewire/node.cpp
215index 3e68149..4721a58 100644
216--- i/src/services/pipewire/node.cpp
217+++ w/src/services/pipewire/node.cpp
218@@ -145,6 +145,10 @@ void PwNode::initProps(const spa_dict* props) {
219 this->type = PwNodeType::VideoSink;
220 } else if (strcmp(mediaClass, "Video/Source") == 0) {
221 this->type = PwNodeType::VideoSource;
222+ } else if (strcmp(mediaClass, "Stream/Output/Video") == 0) {
223+ this->type = PwNodeType::VideoOutStream;
224+ } else if (strcmp(mediaClass, "Stream/Input/Video") == 0) {
225+ this->type = PwNodeType::VideoInStream;
226 }
227 }
228
229diff --git i/src/services/pipewire/node.hpp w/src/services/pipewire/node.hpp
230index 0d4c92e..ee6f223 100644
231--- i/src/services/pipewire/node.hpp
232+++ w/src/services/pipewire/node.hpp
233@@ -144,6 +144,8 @@ public:
234 // This is equivalent to the media class `Video/Sink` and is composed of the
235 // @@PwNodeType.Video and @@PwNodeType.Sink flags.
236 VideoSink = Video | Sink,
237+ VideoOutStream = Video | Sink | Stream,
238+ VideoInStream = Video | Source | Stream,
239 };
240 Q_ENUM(Flag);
241 Q_DECLARE_FLAGS(Flags, Flag);
242diff --git i/src/services/pipewire/qml.cpp w/src/services/pipewire/qml.cpp
243index 9efb17e..921d12a 100644
244--- i/src/services/pipewire/qml.cpp
245+++ w/src/services/pipewire/qml.cpp
246@@ -9,6 +9,9 @@
247 #include <qtypes.h>
248 #include <qvariant.h>
249
250+#include <cstdint>
251+#include <algorithm>
252+
253 #include "../../core/model.hpp"
254 #include "connection.hpp"
255 #include "defaults.hpp"
256@@ -54,6 +57,12 @@ Pipewire::Pipewire(QObject* parent): QObject(parent) {
257
258 QObject::connect(&connection->registry, &PwRegistry::nodeAdded, this, &Pipewire::onNodeAdded);
259
260+ for (auto* device: connection->registry.devices.values()) {
261+ this->onDeviceAdded(device);
262+ }
263+
264+ QObject::connect(&connection->registry, &PwRegistry::deviceAdded, this, &Pipewire::onDeviceAdded);
265+
266 for (auto* link: connection->registry.links.values()) {
267 this->onLinkAdded(link);
268 }
269@@ -123,6 +132,19 @@ void Pipewire::onNodeRemoved(QObject* object) {
270 this->mNodes.removeObject(iface);
271 }
272
273+ObjectModel<PwDeviceIface>* Pipewire::devices() { return &this->mDevices; }
274+
275+void Pipewire::onDeviceAdded(PwDevice* device) {
276+ auto* iface = PwDeviceIface::instance(device);
277+ QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onDeviceRemoved);
278+ this->mDevices.insertObject(iface);
279+}
280+
281+void Pipewire::onDeviceRemoved(QObject* object) {
282+ auto* iface = static_cast<PwDeviceIface*>(object); // NOLINT
283+ this->mDevices.removeObject(iface);
284+}
285+
286 ObjectModel<PwLinkIface>* Pipewire::links() { return &this->mLinks; }
287
288 void Pipewire::onLinkAdded(PwLink* link) {
289@@ -357,6 +379,8 @@ QVariantMap PwNodeIface::properties() const {
290
291 PwNodeAudioIface* PwNodeIface::audio() const { return this->audioIface; }
292
293+PwDeviceIface* PwNodeIface::device() const { return PwDeviceIface::instance(this->mNode->device); }
294+
295 PwNodeIface* PwNodeIface::instance(PwNode* node) {
296 if (node == nullptr) return nullptr;
297
298@@ -481,4 +505,42 @@ void PwObjectTracker::objectDestroyed(QObject* object) {
299 emit this->objectsChanged();
300 }
301
302+PwDeviceIface::PwDeviceIface(PwDevice* device): PwObjectIface(device), mDevice(device) {
303+ QObject::connect(device, &PwDevice::profilesChanged, this, &PwDeviceIface::deviceProfilesChanged);
304+ QObject::connect(device, &PwDevice::currentProfileChanged, this, &PwDeviceIface::deviceCurrentProfileChanged);
305+}
306+
307+void PwDeviceIface::deviceProfilesChanged(QList<PwProfile>) { emit this->profilesChanged(); }
308+void PwDeviceIface::deviceCurrentProfileChanged(PwProfile) { emit this->currentProfileChanged(); }
309+
310+quint32 PwDeviceIface::id() const { return this->mDevice->id; }
311+QString PwDeviceIface::name() const { return this->mDevice->name; }
312+QString PwDeviceIface::description() const { return this->mDevice->description; }
313+QString PwDeviceIface::nickname() const { return this->mDevice->nick; }
314+QString PwDeviceIface::type() const { return this->mDevice->type; }
315+QList<PwProfile> PwDeviceIface::profiles() const {
316+ QList<PwProfile> profiles = this->mDevice->profiles.values();
317+ std::sort(profiles.begin(), profiles.end(), [](const PwProfile& a, const PwProfile& b) { return a.index < b.index; });
318+ return profiles;
319+}
320+qint32 PwDeviceIface::currentProfile() const { return this->mDevice->currentProfile->index; }
321+
322+PwDeviceIface* PwDeviceIface::instance(PwDevice* device) {
323+ if (device == nullptr) return nullptr;
324+
325+ auto v = device->property("iface");
326+ if (v.canConvert<PwDeviceIface*>()) {
327+ return v.value<PwDeviceIface*>();
328+ }
329+
330+ auto* instance = new PwDeviceIface(device);
331+ device->setProperty("iface", QVariant::fromValue(instance));
332+
333+ return instance;
334+}
335+
336+void PwDeviceIface::setProfile(qint32 profileIndex) {
337+ this->mDevice->setProfile(profileIndex);
338+}
339+
340 } // namespace qs::service::pipewire
341diff --git i/src/services/pipewire/qml.hpp w/src/services/pipewire/qml.hpp
342index e3489a1..e5e1891 100644
343--- i/src/services/pipewire/qml.hpp
344+++ w/src/services/pipewire/qml.hpp
345@@ -12,11 +12,13 @@
346 #include "../../core/model.hpp"
347 #include "link.hpp"
348 #include "node.hpp"
349+#include "device.hpp"
350 #include "registry.hpp"
351
352 namespace qs::service::pipewire {
353
354 class PwNodeIface;
355+class PwDeviceIface;
356 class PwLinkIface;
357 class PwLinkGroupIface;
358
359@@ -65,6 +67,8 @@ class Pipewire: public QObject {
360 /// - @@PwNode.audio - if non null the node is an audio node.
361 QSDOC_TYPE_OVERRIDE(ObjectModel<qs::service::pipewire::PwNodeIface>*);
362 Q_PROPERTY(UntypedObjectModel* nodes READ nodes CONSTANT);
363+ QSDOC_TYPE_OVERRIDE(ObjectModel<qs::service::pipewire::PwDeviceIface>*);
364+ Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
365 /// All links present in pipewire.
366 ///
367 /// Links connect pipewire nodes to each other, and can be used to determine
368@@ -134,6 +138,7 @@ public:
369 explicit Pipewire(QObject* parent = nullptr);
370
371 [[nodiscard]] ObjectModel<PwNodeIface>* nodes();
372+ [[nodiscard]] ObjectModel<PwDeviceIface>* devices();
373 [[nodiscard]] ObjectModel<PwLinkIface>* links();
374 [[nodiscard]] ObjectModel<PwLinkGroupIface>* linkGroups();
375
376@@ -159,7 +164,9 @@ signals:
377
378 private slots:
379 void onNodeAdded(PwNode* node);
380+ void onDeviceAdded(PwDevice* node);
381 void onNodeRemoved(QObject* object);
382+ void onDeviceRemoved(QObject* object);
383 void onLinkAdded(PwLink* link);
384 void onLinkRemoved(QObject* object);
385 void onLinkGroupAdded(PwLinkGroup* group);
386@@ -167,6 +174,7 @@ private slots:
387
388 private:
389 ObjectModel<PwNodeIface> mNodes {this};
390+ ObjectModel<PwDeviceIface> mDevices {this};
391 ObjectModel<PwLinkIface> mLinks {this};
392 ObjectModel<PwLinkGroupIface> mLinkGroups {this};
393 };
394@@ -315,6 +323,7 @@ class PwNodeIface: public PwObjectIface {
395 /// > [!NOTE] The node may be used before it is fully bound, but some data
396 /// > may be missing or incorrect.
397 Q_PROPERTY(bool ready READ isReady NOTIFY readyChanged);
398+ Q_PROPERTY(qs::service::pipewire::PwDeviceIface* device READ device CONSTANT);
399 QML_NAMED_ELEMENT(PwNode);
400 QML_UNCREATABLE("PwNodes cannot be created directly");
401
402@@ -332,6 +341,7 @@ public:
403 [[nodiscard]] PwNodeType::Flags type() const;
404 [[nodiscard]] QVariantMap properties() const;
405 [[nodiscard]] PwNodeAudioIface* audio() const;
406+ [[nodiscard]] PwDeviceIface* device() const;
407
408 static PwNodeIface* instance(PwNode* node);
409
410@@ -344,6 +354,44 @@ private:
411 PwNodeAudioIface* audioIface = nullptr;
412 };
413
414+class PwDeviceIface: public PwObjectIface {
415+ Q_OBJECT;
416+ Q_PROPERTY(quint32 id READ id CONSTANT);
417+ Q_PROPERTY(QString name READ name CONSTANT);
418+ Q_PROPERTY(QString description READ description CONSTANT);
419+ Q_PROPERTY(QString nickname READ nickname CONSTANT);
420+ Q_PROPERTY(QString type READ type CONSTANT);
421+ Q_PROPERTY(QList<PwProfile> profiles READ profiles NOTIFY profilesChanged);
422+ Q_PROPERTY(qint32 currentProfile READ currentProfile NOTIFY currentProfileChanged);
423+
424+ QML_NAMED_ELEMENT(PwDevice);
425+ QML_UNCREATABLE("PwDevices cannot be created directly");
426+
427+signals:
428+ void profilesChanged();
429+ void currentProfileChanged();
430+
431+public:
432+ explicit PwDeviceIface(PwDevice* node);
433+
434+ [[nodiscard]] quint32 id() const;
435+ [[nodiscard]] QString name() const;
436+ [[nodiscard]] QString description() const;
437+ [[nodiscard]] QString nickname() const;
438+ [[nodiscard]] QString type() const;
439+ QList<PwProfile> profiles() const;
440+ qint32 currentProfile() const;
441+
442+ Q_INVOKABLE void setProfile(qint32 profileIndex);
443+
444+ static PwDeviceIface* instance(PwDevice* node);
445+private:
446+ PwDevice* mDevice;
447+
448+ void deviceProfilesChanged(QList<PwProfile> profiles);
449+ void deviceCurrentProfileChanged(PwProfile profile);
450+};
451+
452 ///! A connection between pipewire nodes.
453 /// Note that there is one link per *channel* of a connection between nodes.
454 /// You usually want @@PwLinkGroup.
455diff --git i/src/services/pipewire/registry.cpp w/src/services/pipewire/registry.cpp
456index c08fc1d..50c6d7a 100644
457--- i/src/services/pipewire/registry.cpp
458+++ w/src/services/pipewire/registry.cpp
459@@ -196,6 +196,7 @@ void PwRegistry::onGlobal(
460 device->initProps(props);
461
462 self->devices.emplace(id, device);
463+ emit self->deviceAdded(device);
464 }
465 }
466
467@@ -211,6 +212,9 @@ void PwRegistry::onGlobalRemoved(void* data, quint32 id) {
468 } else if (auto* node = self->nodes.value(id)) {
469 self->nodes.remove(id);
470 node->safeDestroy();
471+ } else if (auto* device = self->devices.value(id)) {
472+ self->devices.remove(id);
473+ device->safeDestroy();
474 }
475 }
476
477diff --git i/src/services/pipewire/registry.hpp w/src/services/pipewire/registry.hpp
478index 8473f04..87e0766 100644
479--- i/src/services/pipewire/registry.hpp
480+++ w/src/services/pipewire/registry.hpp
481@@ -132,6 +132,7 @@ public:
482
483 signals:
484 void nodeAdded(PwNode* node);
485+ void deviceAdded(PwDevice* node);
486 void linkAdded(PwLink* link);
487 void linkGroupAdded(PwLinkGroup* group);
488 void metadataAdded(PwMetadata* metadata);
diff --git a/overlays/spice-record.nix b/overlays/spice-record.nix
index 06a114da..2d37079b 100644
--- a/overlays/spice-record.nix
+++ b/overlays/spice-record.nix
@@ -8,5 +8,7 @@
8 wrapProgram $out/bin/spice-record \ 8 wrapProgram $out/bin/spice-record \
9 --prefix PATH : ${prev.lib.makeBinPath (with prev; [ ffmpeg-full ])} 9 --prefix PATH : ${prev.lib.makeBinPath (with prev; [ ffmpeg-full ])}
10 ''; 10 '';
11 pyproject = true;
12 build-system = [ prev.python3Packages.setuptools ];
11 }; 13 };
12} 14}
diff --git a/overlays/spm/default.nix b/overlays/spm/default.nix
index ff135279..bd81ef82 100644
--- a/overlays/spm/default.nix
+++ b/overlays/spm/default.nix
@@ -4,10 +4,11 @@ let
4 # defaultPackages = (import ./stackage.nix {}); 4 # defaultPackages = (import ./stackage.nix {});
5 # haskellPackages = defaultPackages // argumentPackages; 5 # haskellPackages = defaultPackages // argumentPackages;
6 # haskellPackages = argumentPackages; 6 # haskellPackages = argumentPackages;
7 haskellPackages = final.haskell.packages.ghc96.override { 7 haskellPackages = final.haskell.packages.ghc912.override {
8 overrides = self: super: { 8 overrides = self: super: {
9 warp-systemd = final.haskell.lib.doJailbreak (super.warp-systemd.overrideAttrs (oldAttrs: { meta = oldAttrs.meta // { broken = false; }; })); 9 warp-systemd = final.haskell.lib.doJailbreak (super.warp-systemd.overrideAttrs (oldAttrs: { meta = oldAttrs.meta // { broken = false; }; }));
10 unliftio-pool = final.haskell.lib.doJailbreak super.unliftio-pool; 10 unliftio-pool = final.haskell.lib.doJailbreak super.unliftio-pool;
11 cryptonite = super.cryptonite.overrideAttrs (oldAttrs: { doCheck = false; });
11 # servant-server = super.servant-server.overrideAttrs (oldAttrs: { 12 # servant-server = super.servant-server.overrideAttrs (oldAttrs: {
12 # patches = []; 13 # patches = [];
13 # }); 14 # });
diff --git a/overlays/spm/lib/Spm/Api.hs b/overlays/spm/lib/Spm/Api.hs
index 8285cc55..3c22bfb6 100644
--- a/overlays/spm/lib/Spm/Api.hs
+++ b/overlays/spm/lib/Spm/Api.hs
@@ -21,7 +21,6 @@ import Data.Text (Text)
21import qualified Data.Text as Text 21import qualified Data.Text as Text
22 22
23import GHC.Generics (Generic) 23import GHC.Generics (Generic)
24import Type.Reflection (Typeable)
25 24
26import Control.Lens 25import Control.Lens
27 26
@@ -62,7 +61,7 @@ instance FromHttpApiData SpmStyle where
62 61
63 62
64newtype SpmMailbox = SpmMailbox { unSpmMailbox :: CI Text } 63newtype SpmMailbox = SpmMailbox { unSpmMailbox :: CI Text }
65 deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 64 deriving stock (Eq, Ord, Read, Show, Generic)
66 deriving newtype (MimeRender PlainText) 65 deriving newtype (MimeRender PlainText)
67makeWrapped ''SpmMailbox 66makeWrapped ''SpmMailbox
68 67
@@ -70,7 +69,7 @@ instance MimeRender JSON SpmMailbox where
70 mimeRender p mbox = mimeRender p $ JSON.object [ "mailbox" JSON..= unSpmMailbox mbox ] 69 mimeRender p mbox = mimeRender p $ JSON.object [ "mailbox" JSON..= unSpmMailbox mbox ]
71 70
72newtype SpmDomain = SpmDomain { unSpmDomain :: CI Text } 71newtype SpmDomain = SpmDomain { unSpmDomain :: CI Text }
73 deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 72 deriving stock (Eq, Ord, Read, Show, Generic)
74 deriving newtype (MimeRender PlainText) 73 deriving newtype (MimeRender PlainText)
75makeWrapped ''SpmDomain 74makeWrapped ''SpmDomain
76 75
@@ -79,17 +78,17 @@ instance MimeRender JSON SpmDomain where
79 78
80newtype SpmLocal = SpmLocal 79newtype SpmLocal = SpmLocal
81 { unSpmLocal :: CI Text 80 { unSpmLocal :: CI Text
82 } deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 81 } deriving stock (Eq, Ord, Read, Show, Generic)
83 deriving newtype (ToJSON, FromJSON) 82 deriving newtype (ToJSON, FromJSON)
84makeWrapped ''SpmLocal 83makeWrapped ''SpmLocal
85newtype SpmExtension = SpmExtension 84newtype SpmExtension = SpmExtension
86 { unSpmExtension :: CI Text 85 { unSpmExtension :: CI Text
87 } deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 86 } deriving stock (Eq, Ord, Read, Show, Generic)
88 deriving newtype (ToJSON, FromJSON) 87 deriving newtype (ToJSON, FromJSON)
89makeWrapped ''SpmExtension 88makeWrapped ''SpmExtension
90 89
91data SpmMappingState = Valid | Reject 90data SpmMappingState = Valid | Reject
92 deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable) 91 deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic)
93instance MimeRender PlainText SpmMappingState where 92instance MimeRender PlainText SpmMappingState where
94 mimeRender p = mimeRender @_ @Text p . \case 93 mimeRender p = mimeRender @_ @Text p . \case
95 Valid -> "valid" 94 Valid -> "valid"
@@ -109,15 +108,15 @@ _SpmMappingStateReject = iso toReject fromReject
109data SpmMappingListingItem = SpmMappingListingItem 108data SpmMappingListingItem = SpmMappingListingItem
110 { smlMapping :: SpmMapping 109 { smlMapping :: SpmMapping
111 , smlState :: SpmMappingState 110 , smlState :: SpmMappingState
112 } deriving (Eq, Ord, Read, Show, Generic, Typeable) 111 } deriving (Eq, Ord, Read, Show, Generic)
113 112
114newtype SpmMappingListing = SpmMappingListing { unSpmMappingListing :: [SpmMappingListingItem] } 113newtype SpmMappingListing = SpmMappingListing { unSpmMappingListing :: [SpmMappingListingItem] }
115 deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 114 deriving stock (Eq, Ord, Read, Show, Generic)
116 115
117data SpmMapping = SpmMapping 116data SpmMapping = SpmMapping
118 { spmMappingLocal :: Maybe SpmLocal 117 { spmMappingLocal :: Maybe SpmLocal
119 , spmMappingExtension :: Maybe SpmExtension 118 , spmMappingExtension :: Maybe SpmExtension
120 } deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 119 } deriving stock (Eq, Ord, Read, Show, Generic)
121 120
122_SpmMappingText :: Iso' SpmMapping Text 121_SpmMappingText :: Iso' SpmMapping Text
123_SpmMappingText = iso toText fromText 122_SpmMappingText = iso toText fromText
@@ -170,7 +169,7 @@ instance ToJSON SpmMappingListing where
170data SpmJWTClaims = SpmJWTClaims 169data SpmJWTClaims = SpmJWTClaims
171 { spmjwtStdClaims :: ClaimsSet 170 { spmjwtStdClaims :: ClaimsSet
172 , spmjwtLocal :: SpmLocal 171 , spmjwtLocal :: SpmLocal
173 } deriving stock (Eq, Show, Generic, Typeable) 172 } deriving stock (Eq, Show, Generic)
174 173
175makeLensesFor [("spmjwtStdClaims", "_stdClaims"), ("spmjwtLocal", "_spmjwtLocal")] ''SpmJWTClaims 174makeLensesFor [("spmjwtStdClaims", "_stdClaims"), ("spmjwtLocal", "_spmjwtLocal")] ''SpmJWTClaims
176 175
diff --git a/overlays/spm/server/Spm/Server.hs b/overlays/spm/server/Spm/Server.hs
index 8e7f8786..dc334729 100644
--- a/overlays/spm/server/Spm/Server.hs
+++ b/overlays/spm/server/Spm/Server.hs
@@ -1,3 +1,5 @@
1{-# OPTIONS_GHC -Wno-orphans #-}
2
1{-# LANGUAGE OverloadedStrings #-} 3{-# LANGUAGE OverloadedStrings #-}
2 4
3module Spm.Server 5module Spm.Server
@@ -24,7 +26,6 @@ import Data.Attoparsec.Text
24import qualified Data.ByteString.Lazy as LBS 26import qualified Data.ByteString.Lazy as LBS
25 27
26import GHC.Generics (Generic) 28import GHC.Generics (Generic)
27import Type.Reflection (Typeable)
28 29
29import Control.Applicative 30import Control.Applicative
30import Control.Monad 31import Control.Monad
@@ -101,7 +102,7 @@ hSslClientSDn = "SSL-Client-S-DN"
101data SSLClientVerify 102data SSLClientVerify
102 = SSLClientVerifySuccess 103 = SSLClientVerifySuccess
103 | SSLClientVerifyOther Text 104 | SSLClientVerifyOther Text
104 deriving (Eq, Ord, Read, Show, Generic, Typeable) 105 deriving (Eq, Ord, Read, Show, Generic)
105instance FromHttpApiData SSLClientVerify where 106instance FromHttpApiData SSLClientVerify where
106 parseUrlPiece = (left Text.pack .) . parseOnly $ p <* endOfInput 107 parseUrlPiece = (left Text.pack .) . parseOnly $ p <* endOfInput
107 where 108 where
@@ -163,7 +164,7 @@ data ServerCtxError
163 | ServerCtxNoCredentialsDirectory 164 | ServerCtxNoCredentialsDirectory
164 | ServerCtxJwkSetDecodeError String 165 | ServerCtxJwkSetDecodeError String
165 | ServerCtxJwkSetEmpty 166 | ServerCtxJwkSetEmpty
166 deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 167 deriving stock (Eq, Ord, Read, Show, Generic)
167 deriving anyclass (Exception) 168 deriving anyclass (Exception)
168 169
169mkSpmApp :: (MonadUnliftIO m, MonadThrow m) => m Application 170mkSpmApp :: (MonadUnliftIO m, MonadThrow m) => m Application
diff --git a/overlays/spm/server/Spm/Server/Ctx.hs b/overlays/spm/server/Spm/Server/Ctx.hs
index 18452a0a..1d228043 100644
--- a/overlays/spm/server/Spm/Server/Ctx.hs
+++ b/overlays/spm/server/Spm/Server/Ctx.hs
@@ -11,7 +11,6 @@ import Database.Persist.Postgresql
11import UnliftIO.Pool 11import UnliftIO.Pool
12import Control.Lens.TH 12import Control.Lens.TH
13 13
14import Type.Reflection (Typeable)
15import GHC.Generics (Generic) 14import GHC.Generics (Generic)
16 15
17 16
@@ -19,6 +18,6 @@ data ServerCtx = ServerCtx
19 { _sctxSqlPool :: Pool SqlBackend 18 { _sctxSqlPool :: Pool SqlBackend
20 , _sctxInstanceId :: UUID 19 , _sctxInstanceId :: UUID
21 , _sctxJwkSet :: JWKSet 20 , _sctxJwkSet :: JWKSet
22 } deriving (Generic, Typeable) 21 } deriving (Generic)
23makeLenses ''ServerCtx 22makeLenses ''ServerCtx
24 23
diff --git a/overlays/spm/server/Spm/Server/Database.hs b/overlays/spm/server/Spm/Server/Database.hs
index 3156e920..4405452f 100644
--- a/overlays/spm/server/Spm/Server/Database.hs
+++ b/overlays/spm/server/Spm/Server/Database.hs
@@ -13,7 +13,6 @@ import Database.Persist.Sql
13import Database.Persist.TH 13import Database.Persist.TH
14 14
15import GHC.Generics (Generic) 15import GHC.Generics (Generic)
16import Type.Reflection (Typeable)
17 16
18import Data.Text (Text) 17import Data.Text (Text)
19 18
@@ -33,22 +32,22 @@ import Web.HttpApiData
33 32
34newtype MailMailbox = MailMailbox 33newtype MailMailbox = MailMailbox
35 { unMailMailbox :: CI Text 34 { unMailMailbox :: CI Text
36 } deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 35 } deriving stock (Eq, Ord, Read, Show, Generic)
37 deriving newtype (PersistField, PersistFieldSql) 36 deriving newtype (PersistField, PersistFieldSql)
38makeWrapped ''MailMailbox 37makeWrapped ''MailMailbox
39newtype MailLocal = MailLocal 38newtype MailLocal = MailLocal
40 { unMailLocal :: CI Text 39 { unMailLocal :: CI Text
41 } deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 40 } deriving stock (Eq, Ord, Read, Show, Generic)
42 deriving newtype (PersistField, PersistFieldSql) 41 deriving newtype (PersistField, PersistFieldSql)
43makeWrapped ''MailLocal 42makeWrapped ''MailLocal
44newtype MailExtension = MailExtension 43newtype MailExtension = MailExtension
45 { unMailExtension :: CI Text 44 { unMailExtension :: CI Text
46 } deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 45 } deriving stock (Eq, Ord, Read, Show, Generic)
47 deriving newtype (PersistField, PersistFieldSql) 46 deriving newtype (PersistField, PersistFieldSql)
48makeWrapped ''MailExtension 47makeWrapped ''MailExtension
49newtype MailDomain = MailDomain 48newtype MailDomain = MailDomain
50 { unMailDomain :: CI Text 49 { unMailDomain :: CI Text
51 } deriving stock (Eq, Ord, Read, Show, Generic, Typeable) 50 } deriving stock (Eq, Ord, Read, Show, Generic)
52 deriving newtype (PersistField, PersistFieldSql) 51 deriving newtype (PersistField, PersistFieldSql)
53makeWrapped ''MailDomain 52makeWrapped ''MailDomain
54 53
diff --git a/overlays/swayosd/default.nix b/overlays/swayosd/default.nix
deleted file mode 100644
index b4601a03..00000000
--- a/overlays/swayosd/default.nix
+++ /dev/null
@@ -1,13 +0,0 @@
1{ final, prev, sources, ... }: {
2 swayosd = prev.swayosd.overrideAttrs (oldAttrs: rec {
3 inherit (sources.swayosd) version src;
4 cargoDeps = prev.rustPlatform.fetchCargoVendor {
5 inherit (oldAttrs) pname;
6 inherit version src;
7 hash = "sha256-yWybf4GKxHrk4WrW5SmjfPD0Gv79tpXOwNLlWeykYy0=";
8 };
9 patches = (oldAttrs.patches or []) ++ [
10 ./exponential.patch
11 ];
12 });
13}
diff --git a/overlays/swayosd/exponential.patch b/overlays/swayosd/exponential.patch
deleted file mode 100644
index eb90d739..00000000
--- a/overlays/swayosd/exponential.patch
+++ /dev/null
@@ -1,57 +0,0 @@
1diff --git a/src/brightness_backend/brightnessctl.rs b/src/brightness_backend/brightnessctl.rs
2index ccb0e11..740fdb6 100644
3--- a/src/brightness_backend/brightnessctl.rs
4+++ b/src/brightness_backend/brightnessctl.rs
5@@ -107,10 +107,21 @@ impl VirtualDevice {
6 }
7 }
8
9- fn set_percent(&mut self, mut val: u32) -> anyhow::Result<()> {
10- val = val.clamp(0, 100);
11- self.current = self.max.map(|max| val * max / 100);
12- let _: String = self.run(("set", &*format!("{val}%")))?;
13+ fn val_to_percent(&mut self, val: u32) -> u32 {
14+ return ((val as f64 / self.get_max() as f64).powf(0.25) * 100_f64).round() as u32;
15+ }
16+ fn percent_to_val(&mut self, perc: u32) -> u32 {
17+ return ((perc as f64 / 100_f64).powf(4_f64) * self.get_max() as f64).round() as u32;
18+ }
19+
20+ fn set_percent(&mut self, val: u32) -> anyhow::Result<()> {
21+ let new = self.percent_to_val(val);
22+ self.set_val(new)
23+ }
24+ fn set_val(&mut self, val: u32) -> anyhow::Result<()> {
25+ let curr = val.clamp(0, self.get_max());
26+ self.current = Some(curr);
27+ let _: String = self.run(("set", &*format!("{curr}")))?;
28 Ok(())
29 }
30 }
31@@ -134,20 +145,18 @@ impl BrightnessBackend for BrightnessCtl {
32
33 fn lower(&mut self, by: u32) -> anyhow::Result<()> {
34 let curr = self.get_current();
35- let max = self.get_max();
36-
37- let curr = curr * 100 / max;
38+ let mut new = self.device.val_to_percent(curr).saturating_sub(by);
39+ new = self.device.percent_to_val(new).min(curr.saturating_sub(1));
40
41- self.device.set_percent(curr.saturating_sub(by))
42+ self.device.set_val(new)
43 }
44
45 fn raise(&mut self, by: u32) -> anyhow::Result<()> {
46 let curr = self.get_current();
47- let max = self.get_max();
48-
49- let curr = curr * 100 / max;
50+ let mut new = self.device.val_to_percent(curr) + by;
51+ new = self.device.percent_to_val(new).max(curr + 1);
52
53- self.device.set_percent(curr + by)
54+ self.device.set_val(new)
55 }
56
57 fn set(&mut self, val: u32) -> anyhow::Result<()> {
diff --git a/overlays/uucp/default.nix b/overlays/uucp/default.nix
deleted file mode 100644
index 4189dbcc..00000000
--- a/overlays/uucp/default.nix
+++ /dev/null
@@ -1,9 +0,0 @@
1{ final, prev, ... }: {
2 uucp = prev.uucp.overrideAttrs (oldAttrs: {
3 configureFlags = (oldAttrs.configureFlags or []) ++ ["--with-newconfigdir=/etc/uucp"];
4 patches = (oldAttrs.patches or []) ++ [
5 ./mailprogram.patch
6 ];
7 NIX_CFLAGS_COMPILE = "${oldAttrs.NIX_CFLAGS_COMPILE or ""} -Wno-error=incompatible-pointer-types";
8 });
9}
diff --git a/overlays/uucp/mailprogram.patch b/overlays/uucp/mailprogram.patch
deleted file mode 100644
index 89ac8f31..00000000
--- a/overlays/uucp/mailprogram.patch
+++ /dev/null
@@ -1,16 +0,0 @@
1 policy.h | 2 +-
2 1 file changed, 1 insertion(+), 1 deletion(-)
3
4diff --git a/policy.h b/policy.h
5index 5afe34b..8e92c8b 100644
6--- a/policy.h
7+++ b/policy.h
8@@ -240,7 +240,7 @@
9 the sendmail choice below. Otherwise, select one of the other
10 choices as appropriate. */
11 #if 1
12-#define MAIL_PROGRAM "/usr/lib/sendmail -t"
13+#define MAIL_PROGRAM "${config.security.wrapperDir}/sendmail -t"
14 /* #define MAIL_PROGRAM "/usr/sbin/sendmail -t" */
15 #define MAIL_PROGRAM_TO_BODY 1
16 #define MAIL_PROGRAM_SUBJECT_BODY 1
diff --git a/overlays/waybar.nix b/overlays/waybar.nix
deleted file mode 100644
index e7e3b807..00000000
--- a/overlays/waybar.nix
+++ /dev/null
@@ -1,8 +0,0 @@
1{ final, prev, flakeInputs, ... }: prev.lib.composeExtensions
2 flakeInputs.waybar.overlays.default
3 (final: prev: {
4 waybar = prev.waybar.overrideAttrs (oldAttrs: {
5 dontVersionCheck = true;
6 });
7 })
8 final prev
diff --git a/overlays/worktime/worktime/__main__.py b/overlays/worktime/worktime/__main__.py
index bf24bbec..ffeb1b84 100755
--- a/overlays/worktime/worktime/__main__.py
+++ b/overlays/worktime/worktime/__main__.py
@@ -375,10 +375,7 @@ class Worktime(object):
375 parse_datestr(stripped_line) 375 parse_datestr(stripped_line)
376 376
377 for day in [fromDay + timedelta(days = x) for x in range(0, (toDay - fromDay).days + 1)]: 377 for day in [fromDay + timedelta(days = x) for x in range(0, (toDay - fromDay).days + 1)]:
378 if self.end_date.date() < day or day < self.start_date.date(): 378 if self.would_be_workday(day) and self.start_date.date() <= day and day <= self.end_date.date():
379 continue
380
381 if self.would_be_workday(day):
382 if excused_kind == 'leave': 379 if excused_kind == 'leave':
383 self.leave_days.add(day) 380 self.leave_days.add(day)
384 elif time is not None and time >= self.time_per_day(day): 381 elif time is not None and time >= self.time_per_day(day):
@@ -393,9 +390,29 @@ class Worktime(object):
393 start_day = self.start_date.date() 390 start_day = self.start_date.date()
394 end_day = self.end_date.date() 391 end_day = self.end_date.date()
395 392
393 self.extra_days_to_work = dict()
394
396 try: 395 try:
397 with open(Path(config_dir) / "pull-forward", 'r') as excused: 396 with open(Path(config_dir) / "days-to-work", 'r') as extra_days_to_work_file:
398 for line in excused: 397 for line in extra_days_to_work_file:
398 stripped_line = line.strip()
399 if stripped_line:
400 splitLine = stripped_line.split(' ')
401 if len(splitLine) == 2:
402 [hours, datestr] = splitLine
403 day = datetime.strptime(datestr, date_format).replace(tzinfo=tzlocal()).date()
404 self.extra_days_to_work[day] = timedelta(hours = float(hours))
405 else:
406 day = datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()
407 self.extra_days_to_work[day] = self.time_per_day(day)
408 except IOError as e:
409 if e.errno != 2:
410 raise e
411
412
413 try:
414 with open(Path(config_dir) / "pull-forward", 'r') as pull_forward:
415 for line in pull_forward:
399 stripped_line = line.strip() 416 stripped_line = line.strip()
400 if stripped_line: 417 if stripped_line:
401 [hours, datestr] = stripped_line.split(' ') 418 [hours, datestr] = stripped_line.split(' ')
@@ -416,15 +433,22 @@ class Worktime(object):
416 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break 433 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break
417 else: 434 else:
418 if d >= self.end_date.date(): 435 if d >= self.end_date.date():
419 self.pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta())) 436 time_for_day = self.time_per_day(d) if d.isoweekday() in self.workdays else timedelta()
437 if d in self.extra_days_to_work:
438 time_for_day += self.extra_days_to_work[d]
439 self.pull_forward[d] = min(timedelta(hours = float(hours)), time_for_day)
420 except IOError as e: 440 except IOError as e:
421 if e.errno != 2: 441 if e.errno != 2:
422 raise e 442 raise e
423 443
444 if self.pull_forward:
445 for year in range(self.end_date.year + 1, max(self.pull_forward.keys()).year + 1):
446 holidays |= {k: v * timedelta(hours = hours_per_week(k)) / len(self.workdays) for k, v in Worktime.holidays(year).items()}
447
424 self.days_to_work = dict() 448 self.days_to_work = dict()
425 449
426 if self.pull_forward: 450 # if self.pull_forward:
427 end_day = max(end_day, max(list(self.pull_forward))) 451 # end_day = max(end_day, max(self.pull_forward.keys()))
428 452
429 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]: 453 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]:
430 if day.isoweekday() in self.workdays: 454 if day.isoweekday() in self.workdays:
@@ -432,26 +456,6 @@ class Worktime(object):
432 if time_to_work > timedelta(): 456 if time_to_work > timedelta():
433 self.days_to_work[day] = time_to_work 457 self.days_to_work[day] = time_to_work
434 458
435 self.extra_days_to_work = dict()
436
437 try:
438 with open(Path(config_dir) / "days-to-work", 'r') as extra_days_to_work_file:
439 for line in extra_days_to_work_file:
440 stripped_line = line.strip()
441 if stripped_line:
442 splitLine = stripped_line.split(' ')
443 if len(splitLine) == 2:
444 [hours, datestr] = splitLine
445 day = datetime.strptime(datestr, date_format).replace(tzinfo=tzlocal()).date()
446 self.extra_days_to_work[day] = timedelta(hours = float(hours))
447 else:
448 day = datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()
449 self.extra_days_to_work[day] = self.time_per_day(day)
450 except IOError as e:
451 if e.errno != 2:
452 raise e
453
454
455 self.now_is_workday = self.is_workday(self.now.date()) 459 self.now_is_workday = self.is_workday(self.now.date())
456 460
457 self.time_worked = timedelta() 461 self.time_worked = timedelta()
@@ -467,9 +471,9 @@ class Worktime(object):
467 471
468 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta()) 472 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta())
469 for day in [d for d in list(self.pull_forward) if d > self.end_date.date()]: 473 for day in [d for d in list(self.pull_forward) if d > self.end_date.date()]:
470 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())]) 474 days_forward = set([d for d in [start_day + timedelta(days = x) for x in range(0, (day - start_day).days + 1)] if d >= self.end_date.date() and d < day and (d not in self.pull_forward or d == self.end_date.date())])
471 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())]) 475 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (d not in self.pull_forward or d == self.end_date.date())])
472 days_forward = days_forward.union(extra_days_forward) 476 days_forward |= extra_days_forward
473 477
474 extra_day_time_left = timedelta() 478 extra_day_time_left = timedelta()
475 for extra_day in extra_days_forward: 479 for extra_day in extra_days_forward:
@@ -482,15 +486,28 @@ class Worktime(object):
482 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 486 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
483 self.extra_days_to_work[extra_day] += extra_day_time * (day_time / extra_day_time_left) 487 self.extra_days_to_work[extra_day] += extra_day_time * (day_time / extra_day_time_left)
484 488
485 hours_per_day_forward = time_forward / len(days_forward) if len(days_forward) > 0 else timedelta() 489 def days_count(days_forward):
490 r = 0
491 for day in sorted(days_forward):
492 day_time = timedelta()
493 if day in self.extra_days_to_work:
494 day_time += self.extra_days_to_work[day]
495 if day in holidays and not day in self.extra_days_to_work:
496 day_time -= holidays[day]
497 if day.isoweekday() in self.workdays:
498 day_time += timedelta(hours = hours_per_week(day)) / len(self.workdays)
499 r += max(timedelta(), day_time) / (timedelta(hours = hours_per_week(day)) / len(self.workdays))
500 return r
501
502 hours_per_day_forward = time_forward / days_count(days_forward) if days_count(days_forward) > 0 else timedelta()
486 days_forward.discard(self.end_date.date()) 503 days_forward.discard(self.end_date.date())
487 504
488 self.time_pulled_forward += time_forward - hours_per_day_forward * len(days_forward) 505 self.time_pulled_forward += time_forward - hours_per_day_forward * days_count(days_forward)
489 506
490 if self.end_date.date() in self.extra_days_to_work: 507 if self.end_date.date() in self.extra_days_to_work:
491 self.time_pulled_forward += self.extra_days_to_work[self.end_date.date()] 508 self.time_pulled_forward += self.extra_days_to_work[self.end_date.date()]
492 509
493 self.time_to_work += self.time_pulled_forward 510 # self.time_to_work += self.time_pulled_forward
494 511
495 self.time_worked += api.get_billable_hours(self.start_date, self.now) 512 self.time_worked += api.get_billable_hours(self.start_date, self.now)
496 513
@@ -569,7 +586,7 @@ def worktime(pull_forward_cutoff, waybar, **args):
569 pull_forward_sum = sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0)) 586 pull_forward_sum = sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0))
570 if pull_forward_sum >= min(pull_forward_cutoff, timedelta(seconds = 1)): 587 if pull_forward_sum >= min(pull_forward_cutoff, timedelta(seconds = 1)):
571 worktime_no_pulled_forward = deepcopy(worktime) 588 worktime_no_pulled_forward = deepcopy(worktime)
572 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward 589 # worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward
573 worktime_no_pulled_forward.time_pulled_forward = timedelta() 590 worktime_no_pulled_forward.time_pulled_forward = timedelta()
574 worktime_no_pulled_forward.pull_forward = dict() 591 worktime_no_pulled_forward.pull_forward = dict()
575 worktime.time_to_work += pull_forward_sum 592 worktime.time_to_work += pull_forward_sum
@@ -607,7 +624,7 @@ def time_worked(now, waybar, **args):
607 out_text = None 624 out_text = None
608 out_class = "running" if now.running_entry else "stopped" 625 out_class = "running" if now.running_entry else "stopped"
609 tooltip = tooltip_timedelta(worked) 626 tooltip = tooltip_timedelta(worked)
610 target_time = max(then.time_per_day(then.now.date()), now.time_per_day(now.now.date())) if then.time_per_day(then.now.date()) and now.time_per_day(now.now.date()) else (then.time_per_day(then.now.date()) if then.time_per_day(then.now.date()) else now.time_per_day(now.now.date())); 627 target_time = max(then.time_per_day(then.now.date()), now.time_per_day(now.now.date())) if then.time_per_day(then.now.date()) and now.time_per_day(now.now.date()) else (then.time_per_day(then.now.date()) if then.time_per_day(then.now.date()) else now.time_per_day(now.now.date()))
611 difference = target_time - worked 628 difference = target_time - worked
612 difference_pull_forward = difference + now.time_pulled_forward 629 difference_pull_forward = difference + now.time_pulled_forward
613 if now.running_entry and difference_pull_forward < timedelta(seconds=0): 630 if now.running_entry and difference_pull_forward < timedelta(seconds=0):
diff --git a/overlays/yt-dlp.nix b/overlays/yt-dlp.nix
index 94ab1fdd..1c9f77d8 100644
--- a/overlays/yt-dlp.nix
+++ b/overlays/yt-dlp.nix
@@ -1,5 +1,13 @@
1{ prev, sources, ... }: { 1{ prev, final, sources, ... }: {
2 yt-dlp = prev.yt-dlp.overrideAttrs (oldAttrs: { 2 yt-dlp = prev.yt-dlp.overrideAttrs (oldAttrs: {
3 inherit (sources.yt-dlp) pname version src; 3 inherit (sources.yt-dlp) pname version src;
4 }); 4 });
5 python3 = prev.python3.override {
6 packageOverrides = python-self: python-super: {
7 yt-dlp-ejs = python-super.yt-dlp-ejs.overridePythonAttrs (oldAttrs: {
8 inherit (sources.yt-dlp-ejs) pname version src;
9 });
10 };
11 };
12 python3Packages = final.python3.pkgs;
5} 13}
diff --git a/overlays/zte-prometheus-exporter/default.nix b/overlays/zte-prometheus-exporter/default.nix
index cd4207cd..6295567d 100644
--- a/overlays/zte-prometheus-exporter/default.nix
+++ b/overlays/zte-prometheus-exporter/default.nix
@@ -1,7 +1,7 @@
1{ final, prev, ... }: 1{ final, prev, ... }:
2let 2let
3 packageOverrides = final.callPackage ./python-packages.nix {}; 3 packageOverrides = final.callPackage ./python-packages.nix {};
4 inpPython = final.python310.override { inherit packageOverrides; }; 4 inpPython = final.python3.override { inherit packageOverrides; };
5 python = inpPython.withPackages (ps: with ps; [pytimeparse requests]); 5 python = inpPython.withPackages (ps: with ps; [pytimeparse requests]);
6in { 6in {
7 zte-prometheus-exporter = prev.stdenv.mkDerivation rec { 7 zte-prometheus-exporter = prev.stdenv.mkDerivation rec {
diff --git a/overlays/zte-prometheus-exporter/zte-prometheus-exporter.py b/overlays/zte-prometheus-exporter/zte-prometheus-exporter.py
index fc719a96..1d150eda 100644
--- a/overlays/zte-prometheus-exporter/zte-prometheus-exporter.py
+++ b/overlays/zte-prometheus-exporter/zte-prometheus-exporter.py
@@ -54,13 +54,13 @@ class ZTEMetrics:
54 cls._instance.password = environ.get('ZTE_PASSWORD') 54 cls._instance.password = environ.get('ZTE_PASSWORD')
55 cls._instance.attrs = None 55 cls._instance.attrs = None
56 return cls._instance 56 return cls._instance
57 57
58 58
59 def __init__(self): 59 def __init__(self):
60 raise RuntimeError('Call instance() instead') 60 raise RuntimeError('Call instance() instead')
61 61
62 _error_pattern = re.compile('^IF_ERROR(PARAM|TYPE|STR|ID)$') 62 _error_pattern = re.compile(r'^IF_ERROR(PARAM|TYPE|STR|ID)$')
63 _obj_pattern = re.compile('^(?:OBJ_(.+)_ID)|(?:ID_(WAN_COMFIG))$') 63 _obj_pattern = re.compile(r'^(?:OBJ_(.+)_ID)|(?:ID_(WAN_COMFIG))$')
64 def update(self): 64 def update(self):
65 attrs = dict() 65 attrs = dict()
66 66
@@ -106,6 +106,8 @@ class ZTEMetrics:
106 value = child.text 106 value = child.text
107 case _: 107 case _:
108 pass 108 pass
109 if value == '0,0':
110 value = '0'
109 if not name is None and not value is None: 111 if not name is None and not value is None:
110 instance_dict[name] = value 112 instance_dict[name] = value
111 name = None 113 name = None
@@ -120,11 +122,21 @@ class ZTEMetrics:
120 def json_text(self): 122 def json_text(self):
121 return json.dumps(self.attrs) 123 return json.dumps(self.attrs)
122 124
123 _link_pattern = re.compile('^IGD\.WD1\.LINE([0-9]+)$') 125 _link_pattern = re.compile(r'^IGD\.WD1\.LINE([0-9]+)$')
124 _eth_pattern = re.compile('^IGD\.LD1\.ETH([0-9]+)$') 126 _eth_pattern = re.compile(r'^IGD\.LD1\.ETH([0-9]+)$')
125 def prometheus(self): 127 def prometheus(self):
126 metrics = '' 128 metrics = ''
127 129
130 metrics += _format_prom_metrics('info', 'gauge', [({
131 "version_date": self.attrs['DEVINFO']['IGD']['VerDate'],
132 "software_version": self.attrs['DEVINFO']['IGD']['SoftwareVer'],
133 "model_name": self.attrs['DEVINFO']['IGD']['ModelName'],
134 "model_firmware_version": self.attrs['DEVINFO']['IGD']['ModelFirmwareVer'],
135 "hardware_version": self.attrs['DEVINFO']['IGD']['HardwareVer'],
136 "serial_number": self.attrs['DEVINFO']['IGD']['SerialNumber'],
137 "boot_version": self.attrs['DEVINFO']['IGD']['BootVer'],
138 }, 1)], 'Metadata about a given ZTE device')
139
128 uptime_seconds = timeparse(self.attrs['SYSTEMYIME']['IGD']['systemTime']) 140 uptime_seconds = timeparse(self.attrs['SYSTEMYIME']['IGD']['systemTime'])
129 metrics += _format_prom_metrics('uptime_seconds', 'gauge', [({}, uptime_seconds)], 'Seconds device has been running') 141 metrics += _format_prom_metrics('uptime_seconds', 'gauge', [({}, uptime_seconds)], 'Seconds device has been running')
130 142
@@ -133,34 +145,44 @@ class ZTEMetrics:
133 link_match = self._link_pattern.match(link) 145 link_match = self._link_pattern.match(link)
134 link_number = link_match.group(1) 146 link_number = link_match.group(1)
135 147
136 if 'crc_errors_count' not in link_metrics: 148 link_is_up = self.attrs['DSLINTERFACE'][link]['Status'] == 'Up'
137 link_metrics['crc_errors_count'] = {'type': 'counter', 'metrics': []} 149
138 link_metrics['crc_errors_count']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['UpCrc_errors']))] 150 if link_is_up:
139 link_metrics['crc_errors_count']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['DownCrc_errors']))] 151 if 'crc_errors_count' not in link_metrics:
140 152 link_metrics['crc_errors_count'] = {'type': 'counter', 'metrics': []}
141 if 'noise_margin_db' not in link_metrics: 153 link_metrics['crc_errors_count']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['UpCrc_errors']))]
142 link_metrics['noise_margin_db'] = {'type': 'gauge', 'metrics': []} 154 link_metrics['crc_errors_count']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['DownCrc_errors']))]
143 link_metrics['noise_margin_db']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Upstream_noise_margin']))] 155
144 link_metrics['noise_margin_db']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Downstream_noise_margin']))] 156 if 'noise_margin_db' not in link_metrics:
145 157 link_metrics['noise_margin_db'] = {'type': 'gauge', 'metrics': []}
146 if 'attenuation_db' not in link_metrics: 158 link_metrics['noise_margin_db']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Upstream_noise_margin']) / 10)]
147 link_metrics['attenuation_db'] = {'type': 'gauge', 'metrics': []} 159 link_metrics['noise_margin_db']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Downstream_noise_margin']) / 10)]
148 link_metrics['attenuation_db']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Upstream_attenuation']))] 160
149 link_metrics['attenuation_db']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Downstream_attenuation']))] 161 if 'attenuation_db' not in link_metrics:
150 162 link_metrics['attenuation_db'] = {'type': 'gauge', 'metrics': []}
151 if 'max_rate_kbps' not in link_metrics: 163 link_metrics['attenuation_db']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Upstream_attenuation']) / 10)]
152 link_metrics['max_rate_kbps'] = {'type': 'gauge', 'metrics': []} 164 link_metrics['attenuation_db']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Downstream_attenuation']) / 10)]
153 link_metrics['max_rate_kbps']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Upstream_max_rate']))] 165
154 link_metrics['max_rate_kbps']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Downstream_max_rate']))] 166 if 'max_rate_kbps' not in link_metrics:
155 167 link_metrics['max_rate_kbps'] = {'type': 'gauge', 'metrics': []}
156 if 'current_rate_kbps' not in link_metrics: 168 link_metrics['max_rate_kbps']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Upstream_max_rate']))]
157 link_metrics['current_rate_kbps'] = {'type': 'gauge', 'metrics': []} 169 link_metrics['max_rate_kbps']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Downstream_max_rate']))]
158 link_metrics['current_rate_kbps']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Upstream_current_rate']))] 170
159 link_metrics['current_rate_kbps']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Downstream_current_rate']))] 171 if 'current_rate_kbps' not in link_metrics:
160 172 link_metrics['current_rate_kbps'] = {'type': 'gauge', 'metrics': []}
161 if 'dsl_uptime_seconds' not in link_metrics: 173 link_metrics['current_rate_kbps']['metrics'] += [({"direction": "up", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Upstream_current_rate']))]
162 link_metrics['dsl_uptime_seconds'] = {'type': 'gauge', 'metrics': []} 174 link_metrics['current_rate_kbps']['metrics'] += [({"direction": "down", "link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Downstream_current_rate']))]
163 link_metrics['dsl_uptime_seconds']['metrics'] += [({"link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Showtime_start']))] 175
176 if 'uptime_seconds' not in link_metrics:
177 link_metrics['uptime_seconds'] = {'type': 'gauge', 'metrics': []}
178 link_metrics['uptime_seconds']['metrics'] += [({"link": link_number}, int(self.attrs['DSLINTERFACE'][link]['Showtime_start']) if link_is_up else 0)]
179
180 if 'status' not in link_metrics:
181 link_metrics['status'] = {'type': 'gauge', 'metrics': []}
182 up_status, *status = self.attrs['DSLINTERFACE'][link]['Status'].split(',')
183 down_status = up_status if not status else status[0]
184 link_metrics['status']['metrics'] += [({"link": link_number, "direction": "up", "status": up_status}, 1)]
185 link_metrics['status']['metrics'] += [({"link": link_number, "direction": "down", "status": down_status}, 1)]
164 if link_metrics: 186 if link_metrics:
165 for metric_name in link_metrics: 187 for metric_name in link_metrics:
166 metrics += _format_prom_metrics(f'dsl_{metric_name}', link_metrics[metric_name]['type'], link_metrics[metric_name]['metrics']) 188 metrics += _format_prom_metrics(f'dsl_{metric_name}', link_metrics[metric_name]['type'], link_metrics[metric_name]['metrics'])
@@ -203,7 +225,7 @@ class ZTEMetricsServer(BaseHTTPRequestHandler):
203 self.send_response(200) 225 self.send_response(200)
204 self.send_header("Content-type", "text/plain") 226 self.send_header("Content-type", "text/plain")
205 self.end_headers() 227 self.end_headers()
206 228
207 self.wfile.write(zte_metrics.prometheus()) 229 self.wfile.write(zte_metrics.prometheus())
208 case _: 230 case _:
209 self.send_response(404) 231 self.send_response(404)