Skip to content

Commit d53e4c7

Browse files
committed
snapshot
1 parent a9cf9e5 commit d53e4c7

41 files changed

Lines changed: 4978 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

home/linux/gui/base/noctalia/config/plugins.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
}
88
],
99
"states": {
10+
"ip-monitor": {
11+
"enabled": false,
12+
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
13+
},
14+
"keybind-cheatsheet": {
15+
"enabled": true,
16+
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
17+
},
1018
"privacy-indicator": {
1119
"enabled": true,
1220
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import QtQuick
2+
import QtQuick.Layouts
3+
import Quickshell
4+
import qs.Commons
5+
import qs.Modules.Bar.Extras
6+
import qs.Modules.Panels.Settings
7+
import qs.Services.UI
8+
import qs.Widgets
9+
import "." as Local
10+
11+
Item {
12+
id: root
13+
14+
property var pluginApi: null
15+
property var ipMonitorService: pluginApi?.mainInstance?.ipMonitorService || null
16+
17+
property ShellScreen screen
18+
property string widgetId: ""
19+
property string section: ""
20+
21+
property var cfg: pluginApi?.pluginSettings || ({})
22+
property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
23+
24+
readonly property string iconColorKey: cfg.iconColor ?? defaults.iconColor
25+
readonly property string successIconKey: cfg.successIcon ?? defaults.successIcon ?? "network"
26+
readonly property string errorIconKey: cfg.errorIcon ?? defaults.errorIcon ?? "alert-circle"
27+
readonly property string loadingIconKey: cfg.loadingIcon ?? defaults.loadingIcon ?? "loader"
28+
// Read IP state from service (service is the source of truth)
29+
readonly property string currentIp: ipMonitorService?.currentIp ?? "n/a"
30+
readonly property var ipData: ipMonitorService?.ipData ?? null
31+
readonly property string fetchState: ipMonitorService?.fetchState ?? "idle"
32+
33+
readonly property string displayIp: currentIp
34+
readonly property bool isHot: fetchState === "success"
35+
36+
// Bar position handling
37+
readonly property string screenName: screen ? screen.name : ""
38+
readonly property string barPosition: Settings.getBarPositionForScreen(screenName)
39+
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
40+
41+
readonly property string currentIcon: {
42+
if (fetchState === "loading") return loadingIconKey;
43+
if (fetchState === "error") return errorIconKey;
44+
return successIconKey;
45+
}
46+
47+
readonly property color iconColor: isHot ? Color.resolveColorKey(iconColorKey) : Color.mOnSurfaceVariant
48+
readonly property color textColor: isHot ? Color.mOnSurface : Color.mOnSurfaceVariant
49+
50+
implicitWidth: pill.width
51+
implicitHeight: pill.height
52+
53+
onIpMonitorServiceChanged: {
54+
Logger.d("IpMonitor", "BarWidget ipMonitorService changed:", ipMonitorService !== null);
55+
}
56+
57+
onCurrentIpChanged: {
58+
Logger.d("IpMonitor", "BarWidget currentIp changed to:", currentIp);
59+
}
60+
61+
Component.onCompleted: {
62+
Logger.d("IpMonitor", "BarWidget completed refresh");
63+
}
64+
65+
BarPill {
66+
id: pill
67+
screen: root.screen
68+
oppositeDirection: BarService.getPillDirection(root)
69+
icon: root.currentIcon
70+
text: root.displayIp
71+
rotateText: isVerticalBar
72+
forceOpen: true
73+
autoHide: false
74+
customTextIconColor: root.iconColor
75+
76+
tooltipText: {
77+
var lines = [];
78+
lines.push("Left click: Open panel");
79+
lines.push("Right click: Menu");
80+
if (root.fetchState === "success" && root.ipData) {
81+
var data = root.ipData;
82+
lines.push("");
83+
if (data.city || data.country) {
84+
var parts = [];
85+
if (data.city) parts.push(data.city);
86+
if (data.country) parts.push(data.country);
87+
lines.push(parts.join(", "));
88+
}
89+
}
90+
return lines.join("\n");
91+
}
92+
93+
onClicked: {
94+
// Open panel (displays cached IP data)
95+
if (pluginApi) {
96+
pluginApi.openPanel(root.screen, root);
97+
}
98+
}
99+
100+
onRightClicked: {
101+
PanelService.showContextMenu(contextMenu, root, screen);
102+
}
103+
}
104+
105+
NPopupContextMenu {
106+
id: contextMenu
107+
108+
model: [
109+
{
110+
"label": "Copy IP",
111+
"action": "copy",
112+
"icon": "copy"
113+
},
114+
{
115+
"label": "Refresh IP",
116+
"action": "refresh",
117+
"icon": "refresh"
118+
},
119+
{
120+
"label": pluginApi?.tr("menu.settings"),
121+
"action": "settings",
122+
"icon": "settings"
123+
},
124+
]
125+
126+
onTriggered: function (action) {
127+
contextMenu.close();
128+
PanelService.closeContextMenu(screen);
129+
if (action === "copy") {
130+
if (currentIp && currentIp !== "n/a") {
131+
Quickshell.execDetached(["sh", "-c", `printf '%s' '${currentIp}' | wl-copy`]);
132+
ToastService.showNotice("IP copied to clipboard: " + currentIp);
133+
Logger.d("IpMonitor", "Copied IP to clipboard:", currentIp);
134+
} else {
135+
ToastService.showNotice("No IP to copy");
136+
}
137+
} else if (action === "refresh") {
138+
ipMonitorService.fetchIp();
139+
} else if (action === "settings") {
140+
BarService.openPluginSettings(root.screen, pluginApi.manifest);
141+
}
142+
}
143+
}
144+
}
145+
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import QtQuick
2+
import Quickshell
3+
import Quickshell.Io
4+
import qs.Commons
5+
import qs.Services.UI
6+
import "." as Local
7+
8+
Item {
9+
id: root
10+
11+
property var pluginApi: null
12+
13+
// IP Monitor Service
14+
Item {
15+
id: service
16+
17+
property var ipData: null
18+
property string currentIp: "n/a"
19+
property string fetchState: "idle" // idle, loading, success, error
20+
property int lastFetchTime: 0
21+
22+
// Increment this to trigger refresh in all listening widgets (for IPC)
23+
property int refreshTrigger: 0
24+
25+
// Read global settings
26+
readonly property var cfg: root.pluginApi?.pluginSettings || ({})
27+
readonly property var defaults: root.pluginApi?.manifest?.metadata?.defaultSettings || ({})
28+
readonly property int refreshInterval: cfg.refreshInterval ?? defaults.refreshInterval ?? 300
29+
30+
// Process for fetching IP info
31+
property Process ipFetchProcess: Process {
32+
running: false
33+
command: ["curl", "-s", "-m", "10", "https://ipinfo.io"]
34+
stdout: StdioCollector {
35+
id: stdoutCollector
36+
}
37+
stderr: StdioCollector {
38+
id: stderrCollector
39+
}
40+
41+
onStarted: {
42+
service.fetchState = "loading";
43+
Logger.d("IpMonitor", "Service fetching IP info...");
44+
}
45+
46+
onExited: function(exitCode, exitStatus) {
47+
var output = stdoutCollector.text;
48+
Logger.d("IpMonitor", "Service process exited:", exitCode, "length:", output.length);
49+
50+
if (exitCode === 0 && output.length > 0) {
51+
try {
52+
var data = JSON.parse(output);
53+
if (data.ip) {
54+
service.ipData = data;
55+
service.currentIp = data.ip;
56+
service.fetchState = "success";
57+
service.lastFetchTime = Date.now();
58+
Logger.d("IpMonitor", "Service IP fetched successfully:", service.currentIp);
59+
} else {
60+
throw new Error("No IP field in response");
61+
}
62+
} catch (e) {
63+
Logger.e("IpMonitor", "Service parse error:", e.message);
64+
service.currentIp = "n/a";
65+
service.ipData = null;
66+
service.fetchState = "error";
67+
}
68+
} else {
69+
Logger.e("IpMonitor", "Service curl failed:", exitCode);
70+
service.currentIp = "n/a";
71+
service.ipData = null;
72+
service.fetchState = "error";
73+
}
74+
}
75+
}
76+
77+
property Timer autoRefreshTimer: Timer {
78+
interval: service.refreshInterval * 1000
79+
running: interval > 0
80+
repeat: true
81+
onTriggered: service.fetchIp()
82+
}
83+
84+
Component.onCompleted: {
85+
Logger.i("IpMonitor", "Service initialized, first time fetching IP.");
86+
// Auto-fetch on startup
87+
Qt.callLater(() => fetchIp());
88+
}
89+
90+
function fetchIp() {
91+
if (!service.ipFetchProcess) {
92+
Logger.e("IpMonitor", "Service ipFetchProcess is null!");
93+
return;
94+
}
95+
if (!service.ipFetchProcess.running) {
96+
Logger.d("IpMonitor", "Service starting ipFetchProcess");
97+
service.ipFetchProcess.running = true;
98+
} else {
99+
Logger.d("IpMonitor", "Service fetch already in progress");
100+
}
101+
}
102+
103+
function triggerRefresh() {
104+
Logger.d("IpMonitor", "Service triggerRefresh() called");
105+
refreshTrigger++;
106+
fetchIp();
107+
}
108+
}
109+
110+
// Expose service for access via pluginApi.root.ipMonitorService
111+
property alias ipMonitorService: service
112+
113+
Component.onCompleted: {}
114+
115+
// IPC handlers
116+
IpcHandler {
117+
target: "plugin:ip-monitor"
118+
119+
function refreshIp() {
120+
Logger.i("IpMonitor", "IPC refreshIp() called");
121+
service.triggerRefresh();
122+
ToastService.showNotice("Refreshing IP info...");
123+
}
124+
125+
function toggle() {
126+
if (pluginApi) {
127+
pluginApi.withCurrentScreen(screen => {
128+
pluginApi.openPanel(screen);
129+
});
130+
}
131+
}
132+
}
133+
}
134+

0 commit comments

Comments
 (0)