feat(network): improve UI consistency and connection info display

This commit is contained in:
Turann_
2026-03-10 01:16:30 +03:00
parent ae8b13c11b
commit cada065b00
5 changed files with 341 additions and 196 deletions
+23 -33
View File
@@ -105,20 +105,24 @@ Item {
}
}
text: {
try {
if (NetworkService.ethernetConnected) {
return "";
let parts = [];
if (NetworkService.ethernetConnected) {
const d = NetworkService.activeEthernetDetails;
const name = d.connectionName || NetworkService.ethernetInterfaces[0]?.connectionName || "";
const speed = d.speed || "";
if (name) {
parts.push(speed ? (name + " - " + speed) : name);
}
for (const net in NetworkService.networks) {
if (NetworkService.networks[net].connected) {
return net;
}
}
return "";
} catch (error) {
Logger.e("Wi-Fi", "Error getting ssid:", error);
return "error";
}
if (NetworkService.activeWifiIf) {
const d = NetworkService.activeWifiDetails;
const name = d.connectionName || "";
const speed = d.rateShort || d.rate || "";
if (name) {
parts.push(speed ? (name + " - " + speed) : name);
}
}
return parts.join(isBarVertical ? "\n" : " | ");
}
autoHide: false
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
@@ -135,30 +139,16 @@ Item {
return "";
}
try {
if (NetworkService.ethernetConnected) {
const d = NetworkService.activeEthernetDetails || ({});
let base = "";
if (d.ifname && d.ifname.length > 0)
base = d.ifname;
else if (d.connectionName && d.connectionName.length > 0)
base = d.connectionName;
else if (NetworkService.activeEthernetIf && NetworkService.activeEthernetIf.length > 0)
base = NetworkService.activeEthernetIf;
else
base = I18n.tr("common.ethernet");
const speed = (d.speed && d.speed.length > 0) ? d.speed : "";
return speed ? (base + " — " + speed) : base;
}
// WiFi tooltip: SSID — link speed (if available)
if (pill.text !== "") {
const w = NetworkService.activeWifiDetails || ({});
const rate = (w.rateShort && w.rateShort.length > 0) ? w.rateShort : (w.rate || "");
return rate && rate.length > 0 ? (pill.text + " — " + rate) : pill.text;
const name = pill.text;
if (!name) {
return I18n.tr("common.wifi");
}
const d = NetworkService.ethernetConnected ? NetworkService.activeEthernetDetails : NetworkService.activeWifiDetails;
const speed = (d.speed && d.speed.length > 0) ? d.speed : ((d.rateShort && d.rateShort.length > 0) ? d.rateShort : (d.rate || ""));
return speed ? (name + " — " + speed) : name;
} catch (e) {
// noop
return I18n.tr("common.wifi");
}
return I18n.tr("common.wifi");
}
}
}
+198 -82
View File
@@ -7,6 +7,7 @@ import qs.Commons
import qs.Modules.MainScreen
import qs.Modules.Panels.Settings
import qs.Services.Networking
import qs.Services.System
import qs.Services.UI
import qs.Widgets
@@ -20,6 +21,7 @@ SmartPanel {
// Ethernet details UI state (mirrors WiFi info behavior)
property bool ethernetInfoExpanded: false
property bool ethernetDetailsGrid: (Settings.data.network.wifiDetailsViewMode === "grid")
property int ipVersion: 4
// Unified panel view mode: "wifi" | "ethernet" (persisted)
property string panelViewMode: "wifi"
@@ -45,6 +47,7 @@ SmartPanel {
}
onOpened: {
SystemStatService.registerComponent("network-panel");
NetworkService.scan();
// Preload active WiFi details so Info shows instantly
NetworkService.refreshActiveWifiDetails();
@@ -68,6 +71,10 @@ SmartPanel {
panelViewPersistEnabled = true;
}
onClosed: {
SystemStatService.unregisterComponent("network-panel");
}
panelContent: Rectangle {
color: "transparent"
@@ -95,7 +102,13 @@ SmartPanel {
id: modeIcon
icon: panelViewMode === "wifi" ? (Settings.data.network.wifiEnabled ? "wifi" : "wifi-off") : (NetworkService.hasEthernet() ? (NetworkService.ethernetConnected ? "ethernet" : "ethernet") : "ethernet-off")
pointSize: Style.fontSizeXXL
color: panelViewMode === "wifi" ? (Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant) : (NetworkService.ethernetConnected ? Color.mPrimary : Color.mOnSurfaceVariant)
color: {
if (panelViewMode === "wifi") {
return Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant;
} else {
return NetworkService.ethernetConnected ? Color.mPrimary : Color.mOnSurfaceVariant;
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
@@ -426,6 +439,10 @@ SmartPanel {
delegate: NBox {
id: ethItem
HoverHandler {
id: itemHover
}
Layout.fillWidth: true
Layout.leftMargin: Style.marginXS
Layout.rightMargin: Style.marginXS
@@ -462,7 +479,7 @@ SmartPanel {
spacing: 2
NText {
text: modelData.ifname
text: modelData.connectionName || modelData.ifname
pointSize: Style.fontSizeM
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
color: Color.mOnSurface
@@ -473,20 +490,75 @@ SmartPanel {
RowLayout {
spacing: Style.marginXS
// Connected badge (mirrors WiFi chip)
Rectangle {
visible: modelData.connected
color: Color.mPrimary
radius: height * 0.5
width: ethConnectedText.implicitWidth + Style.margin2S
height: ethConnectedText.implicitHeight + (Style.margin2XXS)
NText {
text: {
if (modelData.connected) {
switch (NetworkService.networkConnectivity) {
case "full":
return I18n.tr("common.connected");
case "limited":
return I18n.tr("wifi.panel.internet-limited");
case "portal":
return I18n.tr("wifi.panel.action-required");
default:
return NetworkService.networkConnectivity;
}
}
return I18n.tr("common.disconnected");
}
pointSize: Style.fontSizeXXS
color: {
if (!modelData.connected) {
return Color.mError;
}
if (NetworkService.networkConnectivity === "limited" || NetworkService.networkConnectivity === "portal") {
return Color.mError;
}
return Color.mPrimary;
}
}
// Network speed indicators (visible when connected and speed > 0)
RowLayout {
visible: modelData.connected && (SystemStatService.rxSpeed > 0 || SystemStatService.txSpeed > 0)
spacing: 2
Layout.leftMargin: Style.marginXS
Layout.fillWidth: false
NIcon {
visible: SystemStatService.rxSpeed > 0
icon: "arrow-down"
pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
NText {
id: ethConnectedText
anchors.centerIn: parent
text: I18n.tr("common.connected")
visible: SystemStatService.rxSpeed > 0
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
pointSize: Style.fontSizeXXS
color: Color.mOnPrimary
color: Color.mOnSurfaceVariant
elide: Text.ElideNone
}
Item {
visible: SystemStatService.rxSpeed > 0 && SystemStatService.txSpeed > 0
width: Style.marginXS
height: 1
}
NIcon {
visible: SystemStatService.txSpeed > 0
icon: "arrow-up"
pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
NText {
visible: SystemStatService.txSpeed > 0
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
elide: Text.ElideNone
}
}
}
@@ -494,6 +566,7 @@ SmartPanel {
// Info button on the right
NIconButton {
visible: itemHover.hovered
icon: "info"
tooltipText: I18n.tr("common.info")
baseSize: Style.baseWidgetSize * 0.8
@@ -563,6 +636,8 @@ SmartPanel {
anchors.fill: parent
anchors.margins: Style.marginS
anchors.rightMargin: Style.baseWidgetSize
flow: ethernetDetailsGrid ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: ethernetDetailsGrid ? 3 : 6
columns: ethernetDetailsGrid ? 2 : 1
columnSpacing: Style.marginM
rowSpacing: Style.marginXS
@@ -575,13 +650,10 @@ SmartPanel {
}
// --- Item 1: Interface ---
// Grid: Row 0, Col 0 | List: Row 0
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
spacing: Style.marginXS
Layout.row: 0
Layout.column: 0
NIcon {
icon: "ethernet"
pointSize: Style.fontSizeXS
@@ -618,55 +690,63 @@ SmartPanel {
const value = (NetworkService.activeEthernetDetails.ifname && NetworkService.activeEthernetDetails.ifname.length > 0) ? NetworkService.activeEthernetDetails.ifname : (NetworkService.activeEthernetIf || "");
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("toast.bluetooth.address-copied"), "ethernet");
ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet");
}
}
}
}
}
// --- Item 2: Internet connectivity --
// Grid: Row 1, Col 0 | List: Row 1
// --- Item 2: Hardware Address ---
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: 1
Layout.column: 0
spacing: Style.marginXS
NIcon {
// If the selected Ethernet interface is disconnected, show an explicit disconnected state
icon: modelData.connected ? (NetworkService.internetConnectivity ? "world" : "world-off") : "world-off"
icon: "hash"
pointSize: Style.fontSizeXS
color: modelData.connected ? (NetworkService.internetConnectivity ? Color.mOnSurface : Color.mError) : Color.mError
color: Color.mOnSurface
Layout.alignment: Qt.AlignVCenter
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: TooltipService.show(parent, I18n.tr("wifi.panel.internet-status"))
onEntered: TooltipService.show(parent, I18n.tr("bluetooth.panel.device-address"))
onExited: TooltipService.hide()
}
}
NText {
// Show "Disconnected" when the interface itself is down
text: modelData.connected ? (NetworkService.internetConnectivity ? I18n.tr("wifi.panel.internet-connected") : I18n.tr("wifi.panel.internet-limited")) : I18n.tr("common.disconnected")
text: NetworkService.activeEthernetDetails.hwAddr || "-"
pointSize: Style.fontSizeXS
color: modelData.connected ? (NetworkService.internetConnectivity ? Color.mOnSurface : Color.mError) : Color.mError
color: Color.mOnSurface
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere
elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone
maximumLineCount: ethernetDetailsGrid ? 1 : 6
clip: true
MouseArea {
anchors.fill: parent
enabled: (NetworkService.activeEthernetDetails.hwAddr || "").length > 0
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address"))
onExited: TooltipService.hide()
onClicked: {
const value = NetworkService.activeEthernetDetails.hwAddr || "";
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet");
}
}
}
}
}
// --- Iterm 3: Link speed ---
// Grid: Row 2, Col 0 | List: Row 2
// --- Item 3: Link speed ---
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: 2
Layout.column: 0
spacing: Style.marginXS
NIcon {
icon: "gauge"
@@ -693,46 +773,10 @@ SmartPanel {
}
}
// --- Item 4: Gateway ---
// Grid: Row 2, Col 1 | List: Row 5 (Last)
// --- Item 4: IPv4 || IPv6 ---
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: ethernetDetailsGrid ? 2 : 5
Layout.column: ethernetDetailsGrid ? 1 : 0
spacing: Style.marginXS
NIcon {
icon: "router"
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.alignment: Qt.AlignVCenter
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: TooltipService.show(parent, I18n.tr("common.gateway"))
onExited: TooltipService.hide()
}
}
NText {
text: NetworkService.activeEthernetDetails.gateway4 || "-"
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere
elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone
maximumLineCount: ethernetDetailsGrid ? 1 : 6
clip: true
}
}
// --- Item 5: IPv4 ---
// Grid: Row 0, Col 1 | List: Row 3
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: ethernetDetailsGrid ? 0 : 3
Layout.column: ethernetDetailsGrid ? 1 : 0
spacing: Style.marginXS
NIcon {
icon: "network"
@@ -742,12 +786,16 @@ SmartPanel {
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: TooltipService.show(parent, I18n.tr("wifi.panel.ipv4"))
onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.ipv4") : I18n.tr("wifi.panel.ipv6"))
onExited: TooltipService.hide()
onClicked: {
root.ipVersion = root.ipVersion === 4 ? 6 : 4;
TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.ipv4") : I18n.tr("wifi.panel.ipv6"));
}
}
}
NText {
text: NetworkService.activeEthernetDetails.ipv4 || "-"
text: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.ipv4 || "-") : ((NetworkService.activeEthernetDetails.ipv6 || []).join(", ") || "-")
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
@@ -757,33 +805,29 @@ SmartPanel {
maximumLineCount: ethernetDetailsGrid ? 1 : 6
clip: true
// Click-to-copy Ethernet IPv4 address
// Click-to-copy Ethernet IP address
MouseArea {
anchors.fill: parent
// Normalize to string to avoid undefined -> bool assignment warnings
enabled: (NetworkService.activeEthernetDetails.ipv4 || "").length > 0
enabled: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.ipv4 || "").length > 0 : (NetworkService.activeEthernetDetails.ipv6 || []).length > 0
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address"))
onExited: TooltipService.hide()
onClicked: {
const value = NetworkService.activeEthernetDetails.ipv4 || "";
const value = root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.ipv4 || "") : ((NetworkService.activeEthernetDetails.ipv6 || []).join(", ") || "");
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("toast.bluetooth.address-copied"), "ethernet");
ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet");
}
}
}
}
}
// --- Item 6: DNS ---
// Grid: Row 1, Col 1 | List: Row 4
// --- Item 5: DNS ---
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: ethernetDetailsGrid ? 1 : 4
Layout.column: ethernetDetailsGrid ? 1 : 0
spacing: Style.marginXS
NIcon {
icon: "world"
@@ -793,12 +837,16 @@ SmartPanel {
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: TooltipService.show(parent, I18n.tr("wifi.panel.dns"))
onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv6") + ")")
onExited: TooltipService.hide()
onClicked: {
root.ipVersion = root.ipVersion === 4 ? 6 : 4;
TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("wifi.panel.dns") + " (" + I18n.tr("wifi.panel.ipv6") + ")");
}
}
}
NText {
text: NetworkService.activeEthernetDetails.dns || "-"
text: root.ipVersion === 4 ? ((NetworkService.activeEthernetDetails.dns4 || []).join(", ") || "-") : ((NetworkService.activeEthernetDetails.dns6 || []).join(", ") || "-")
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
@@ -807,6 +855,74 @@ SmartPanel {
elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone
maximumLineCount: ethernetDetailsGrid ? 1 : 6
clip: true
// Click-to-copy Ethernet DNS
MouseArea {
anchors.fill: parent
enabled: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.dns4 || []).length > 0 : (NetworkService.activeEthernetDetails.dns6 || []).length > 0
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address"))
onExited: TooltipService.hide()
onClicked: {
const value = root.ipVersion === 4 ? ((NetworkService.activeEthernetDetails.dns4 || []).join(", ") || "") : ((NetworkService.activeEthernetDetails.dns6 || []).join(", ") || "");
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet");
}
}
}
}
}
// --- Item 6: Gateway ---
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
spacing: Style.marginXS
NIcon {
icon: "router"
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.alignment: Qt.AlignVCenter
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv6") + ")")
onExited: TooltipService.hide()
onClicked: {
root.ipVersion = root.ipVersion === 4 ? 6 : 4;
TooltipService.show(parent, root.ipVersion === 4 ? I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv4") + ")" : I18n.tr("common.gateway") + " (" + I18n.tr("wifi.panel.ipv6") + ")");
}
}
}
NText {
text: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.gateway4 || "-") : ((NetworkService.activeEthernetDetails.gateway6 || []).join(", ") || "-")
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
wrapMode: ethernetDetailsGrid ? Text.NoWrap : Text.WrapAtWordBoundaryOrAnywhere
elide: ethernetDetailsGrid ? Text.ElideRight : Text.ElideNone
maximumLineCount: ethernetDetailsGrid ? 1 : 6
clip: true
// Click-to-copy Ethernet Gateway
MouseArea {
anchors.fill: parent
enabled: root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.gateway4 || "").length > 0 : (NetworkService.activeEthernetDetails.gateway6 || []).length > 0
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address"))
onExited: TooltipService.hide()
onClicked: {
const value = root.ipVersion === 4 ? (NetworkService.activeEthernetDetails.gateway4 || "") : ((NetworkService.activeEthernetDetails.gateway6 || []).join(", ") || "");
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("common.ethernet"), I18n.tr("common.copied-to-clipboard"), "ethernet");
}
}
}
}
}
}
@@ -370,6 +370,10 @@ Item {
NBox {
id: device
HoverHandler {
id: itemHover
}
readonly property bool canConnect: BluetoothService.canConnect(modelData)
readonly property bool canDisconnect: BluetoothService.canDisconnect(modelData)
readonly property bool canPair: BluetoothService.canPair(modelData)
@@ -471,7 +475,7 @@ Item {
spacing: Style.marginS
NIconButton {
visible: modelData.connected
visible: itemHover.hovered && modelData.connected
icon: "info"
tooltipText: I18n.tr("common.info")
baseSize: Style.baseWidgetSize * 0.8
@@ -482,7 +486,7 @@ Item {
}
NIconButton {
visible: !root.showOnlyLists && (modelData.paired || modelData.trusted) && !modelData.connected && !isBusy && !modelData.blocked
visible: itemHover.hovered && !root.showOnlyLists && (modelData.paired || modelData.trusted) && !modelData.connected && !isBusy && !modelData.blocked
icon: "trash"
tooltipText: I18n.tr("common.unpair")
baseSize: Style.baseWidgetSize * 0.8
@@ -491,7 +495,7 @@ Item {
NButton {
id: button
visible: (modelData.state !== BluetoothDeviceState.Connecting)
visible: itemHover.hovered && (modelData.state !== BluetoothDeviceState.Connecting)
enabled: (canConnect || canDisconnect || (root.showOnlyLists ? false : canPair)) && !isBusy
outlined: !button.hovered
fontSize: Style.fontSizeS
@@ -552,6 +556,8 @@ Item {
id: infoColumn
anchors.fill: parent
anchors.margins: Style.marginS
flow: root.detailsGrid ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: root.detailsGrid ? 3 : 6
columns: root.detailsGrid ? 2 : 1
columnSpacing: Style.marginM
rowSpacing: Style.marginXS
@@ -561,8 +567,6 @@ Item {
Layout.fillWidth: true
Layout.preferredWidth: 1
spacing: Style.marginXS
Layout.row: detailsGrid ? 0 : 0
Layout.column: 0
NIcon {
icon: BluetoothService.getSignalIcon(modelData)
pointSize: Style.fontSizeXS
@@ -580,8 +584,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 0 : 1
Layout.column: detailsGrid ? 1 : 0
spacing: Style.marginXS
NIcon {
icon: {
@@ -605,8 +607,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 1 : 2
Layout.column: 0
spacing: Style.marginXS
NIcon {
icon: "link"
@@ -624,8 +624,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 1 : 3
Layout.column: detailsGrid ? 1 : 0
spacing: Style.marginXS
NIcon {
icon: "shield-check"
@@ -643,8 +641,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 2 : 4
Layout.column: 0
spacing: Style.marginXS
NIcon {
icon: "hash"
@@ -662,8 +658,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 2 : 5
Layout.column: detailsGrid ? 1 : 0
spacing: Style.marginXS
visible: Settings.data.network.bluetoothAutoConnect
@@ -510,6 +510,10 @@ Item {
NBox {
id: networkItem
HoverHandler {
id: itemHover
}
readonly property bool isBusy: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid
readonly property bool isExpanded: root.infoSsid === modelData.ssid
readonly property bool isEnterprise: NetworkService.isEnterprise(modelData.security)
@@ -606,7 +610,15 @@ Item {
return NetworkService.isSecured(modelData.security) ? modelData.security : "Open";
}
pointSize: Style.fontSizeXXS
color: networkItem.getContentColor(Color.mOnSurfaceVariant)
color: {
if (modelData.connected) {
return (NetworkService.networkConnectivity === "full") ? Color.mPrimary : Color.mError;
}
if (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) {
return Color.mError;
}
return networkItem.getContentColor(Color.mOnSurfaceVariant);
}
}
// Network speed indicators (visible when connected and speed > 0)
@@ -614,6 +626,7 @@ Item {
visible: modelData.connected && (SystemStatService.rxSpeed > 0 || SystemStatService.txSpeed > 0)
spacing: 2
Layout.leftMargin: Style.marginXS
Layout.fillWidth: false
NIcon {
visible: SystemStatService.rxSpeed > 0
@@ -627,6 +640,7 @@ Item {
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
pointSize: Style.fontSizeXXS
color: networkItem.getContentColor(Color.mOnSurfaceVariant)
elide: Text.ElideNone
}
Item {
@@ -647,6 +661,7 @@ Item {
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
pointSize: Style.fontSizeXXS
color: networkItem.getContentColor(Color.mOnSurfaceVariant)
elide: Text.ElideNone
}
}
}
@@ -667,7 +682,7 @@ Item {
}
NIconButton {
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
visible: itemHover.hovered && modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
icon: "info"
tooltipText: I18n.tr("common.info")
baseSize: Style.baseWidgetSize * 0.8
@@ -682,7 +697,7 @@ Item {
}
NIconButton {
visible: !root.showOnlyLists && (modelData.existing || modelData.cached) && !modelData.connected && !networkItem.isBusy
visible: itemHover.hovered && !root.showOnlyLists && (modelData.existing || modelData.cached) && !modelData.connected && !networkItem.isBusy
icon: "trash"
tooltipText: I18n.tr("tooltips.forget-network")
baseSize: Style.baseWidgetSize * 0.8
@@ -691,7 +706,7 @@ Item {
NButton {
id: button
visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && root.passwordSsid !== modelData.ssid
visible: itemHover.hovered && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && root.passwordSsid !== modelData.ssid
enabled: !NetworkService.connecting && !networkItem.isBusy
outlined: !button.hovered
fontSize: Style.fontSizeS
@@ -708,7 +723,7 @@ Item {
NButton {
id: disconnectButton
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
visible: itemHover.hovered && modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
text: I18n.tr("common.disconnect")
outlined: !disconnectButton.hovered
fontSize: Style.fontSizeS
@@ -755,6 +770,8 @@ Item {
id: infoColumn
anchors.fill: parent
anchors.margins: Style.marginS
flow: root.detailsGrid ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: root.detailsGrid ? 3 : 6
columns: root.detailsGrid ? 2 : 1
columnSpacing: Style.marginM
rowSpacing: Style.marginXS
@@ -771,8 +788,6 @@ Item {
Layout.fillWidth: true
Layout.preferredWidth: 1
spacing: Style.marginXS
Layout.row: 0
Layout.column: 0
NIcon {
icon: "network"
pointSize: Style.fontSizeXS
@@ -815,8 +830,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 1 : 1
Layout.column: 0
spacing: Style.marginXS
NIcon {
icon: "router"
@@ -839,8 +852,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 2 : 2
Layout.column: 0
spacing: Style.marginXS
NIcon {
icon: "gauge"
@@ -864,8 +875,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 0 : 3
Layout.column: detailsGrid ? 1 : 0
spacing: Style.marginXS
NIcon {
icon: "network"
@@ -883,7 +892,7 @@ Item {
}
}
NText {
text: root.ipVersion === 4 ? (NetworkService.activeWifiDetails.ipv4 || "-") : (NetworkService.activeWifiDetails.ipv6 || "-")
text: root.ipVersion === 4 ? (NetworkService.activeWifiDetails.ipv4 || "-") : ((NetworkService.activeWifiDetails.ipv6 || []).join(", ") || "-")
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
@@ -896,7 +905,7 @@ Item {
onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address"))
onExited: TooltipService.hide()
onClicked: {
const value = root.ipVersion === 4 ? (NetworkService.activeWifiDetails.ipv4 || "") : (NetworkService.activeWifiDetails.ipv6 || "");
const value = root.ipVersion === 4 ? (NetworkService.activeWifiDetails.ipv4 || "") : ((NetworkService.activeWifiDetails.ipv6 || []).join(", ") || "");
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.bluetooth.address-copied"), "wifi");
@@ -909,8 +918,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 1 : 4
Layout.column: detailsGrid ? 1 : 0
spacing: Style.marginXS
NIcon {
icon: "world"
@@ -928,7 +935,7 @@ Item {
}
}
NText {
text: root.ipVersion === 4 ? (NetworkService.activeWifiDetails.dns4 || "-") : (NetworkService.activeWifiDetails.dns6 || "-")
text: root.ipVersion === 4 ? ((NetworkService.activeWifiDetails.dns4 || []).join(", ") || "-") : ((NetworkService.activeWifiDetails.dns6 || []).join(", ") || "-")
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
@@ -941,7 +948,7 @@ Item {
onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address"))
onExited: TooltipService.hide()
onClicked: {
const value = root.ipVersion === 4 ? (NetworkService.activeWifiDetails.dns4 || "") : (NetworkService.activeWifiDetails.dns6 || "");
const value = root.ipVersion === 4 ? ((NetworkService.activeWifiDetails.dns4 || []).join(", ") || "") : ((NetworkService.activeWifiDetails.dns6 || []).join(", ") || "");
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.bluetooth.address-copied"), "wifi");
@@ -954,8 +961,6 @@ Item {
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 1
Layout.row: detailsGrid ? 2 : 5
Layout.column: detailsGrid ? 1 : 0
spacing: Style.marginXS
NIcon {
icon: "router"
@@ -973,7 +978,7 @@ Item {
}
}
NText {
text: root.ipVersion === 4 ? (NetworkService.activeWifiDetails.gateway4 || "-") : (NetworkService.activeWifiDetails.gateway6 || "-")
text: root.ipVersion === 4 ? (NetworkService.activeWifiDetails.gateway4 || "-") : ((NetworkService.activeWifiDetails.gateway6 || []).join(", ") || "-")
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
@@ -986,7 +991,7 @@ Item {
onEntered: TooltipService.show(parent, I18n.tr("tooltips.copy-address"))
onExited: TooltipService.hide()
onClicked: {
const value = root.ipVersion === 4 ? (NetworkService.activeWifiDetails.gateway4 || "") : (NetworkService.activeWifiDetails.gateway6 || "");
const value = root.ipVersion === 4 ? (NetworkService.activeWifiDetails.gateway4 || "") : ((NetworkService.activeWifiDetails.gateway6 || []).join(", ") || "");
if (value.length > 0) {
Quickshell.execDetached(["wl-copy", value]);
ToastService.showNotice(I18n.tr("common.wifi"), I18n.tr("toast.bluetooth.address-copied"), "wifi");
+83 -43
View File
@@ -41,14 +41,38 @@ Singleton {
// Supported Wi-Fi security types
property var supportedSecurityTypes: [
{ key: "open", name: I18n.tr("wifi.panel.security-open") },
{ key: "wep", name: I18n.tr("wifi.panel.security-wep") },
{ key: "wpa-psk", name: I18n.tr("wifi.panel.security-wpa") },
{ key: "wpa2-psk", name: I18n.tr("wifi.panel.security-wpa23") },
{ key: "sae", name: I18n.tr("wifi.panel.security-wpa3") },
{ key: "wpa-eap", name: I18n.tr("wifi.panel.security-wpa-ent") },
{ key: "wpa2-eap", name: I18n.tr("wifi.panel.security-wpa2-ent") },
{ key: "wpa3-eap", name: I18n.tr("wifi.panel.security-wpa3-ent") }
{
key: "open",
name: I18n.tr("wifi.panel.security-open")
},
{
key: "wep",
name: I18n.tr("wifi.panel.security-wep")
},
{
key: "wpa-psk",
name: I18n.tr("wifi.panel.security-wpa")
},
{
key: "wpa2-psk",
name: I18n.tr("wifi.panel.security-wpa23")
},
{
key: "sae",
name: I18n.tr("wifi.panel.security-wpa3")
},
{
key: "wpa-eap",
name: I18n.tr("wifi.panel.security-wpa-ent")
},
{
key: "wpa2-eap",
name: I18n.tr("wifi.panel.security-wpa2-ent")
},
{
key: "wpa3-eap",
name: I18n.tr("wifi.panel.security-wpa3-ent")
}
]
// Active WiFi connection details (for info panel)
@@ -476,14 +500,45 @@ Singleton {
function parseIpDetails(text) {
const details = {
connectionName: "",
ipv4: "",
gateway4: "",
ipv6: "",
gateway6: "",
dns4: [],
ipv6: [],
gateway6: [],
dns6: [],
dns: "",
connectionName: ""
hwAddr: ""
};
const addUnique = (arr, val) => {
if (val && arr.indexOf(val) === -1) {
arr.push(val);
}
};
const handlers = {
"GENERAL.CONNECTION": v => {
details.connectionName = v;
},
"GENERAL.HWADDR": v => {
details.hwAddr = v;
},
"IP4.ADDRESS": v => {
details.ipv4 = v.split("/")[0];
},
"IP4.GATEWAY": v => {
details.gateway4 = v;
},
"IP6.ADDRESS": v => {
addUnique(details.ipv6, v.split("/")[0]);
},
"IP6.GATEWAY": v => {
addUnique(details.gateway6, v);
},
"IP4.DNS": v => {
addUnique(details.dns4, v);
},
"IP6.DNS": v => {
addUnique(details.dns6, v);
}
};
const lines = text.split("\n");
for (let i = 0; i < lines.length; i++) {
@@ -495,31 +550,12 @@ Singleton {
if (idx === -1) {
continue;
}
const key = line.substring(0, idx);
const val = line.substring(idx + 1);
if (key === "GENERAL.CONNECTION") {
details.connectionName = val;
} else if (key.indexOf("IP4.ADDRESS") === 0) {
details.ipv4 = val.split("/")[0];
} else if (key === "IP4.GATEWAY") {
details.gateway4 = val;
} else if (key.indexOf("IP6.ADDRESS") === 0) {
details.ipv6 = val.split("/")[0];
} else if (key === "IP6.GATEWAY") {
details.gateway6 = val;
} else if (key.indexOf("IP4.DNS") === 0) {
if (val && details.dns4.indexOf(val) === -1) {
details.dns4.push(val);
}
} else if (key.indexOf("IP6.DNS") === 0) {
if (val && details.dns6.indexOf(val) === -1) {
details.dns6.push(val);
}
const key = line.substring(0, idx).replace(/\[\d+\]$/, "");
const val = line.substring(idx + 1).trim();
if (handlers[key]) {
handlers[key](val);
}
}
details.dns4 = details.dns4.join(", ");
details.dns6 = details.dns6.join(", ");
details.dns = [].concat(details.dns4 ? [details.dns4] : [], details.dns6 ? [details.dns6] : []).join(", ");
return details;
}
@@ -527,7 +563,7 @@ Singleton {
Process {
id: ethernetStateProcess
running: ProgramCheckerService.nmcliAvailable
command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"]
command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device"]
stdout: StdioCollector {
onStreamFinished: {
@@ -540,11 +576,13 @@ Singleton {
if (parts.length >= 3 && parts[1] === "ethernet" && parts[2] !== "unmanaged") {
var ifname = parts[0];
var state = parts[2];
var conName = parts.slice(3).join(":") || "";
var isConn = state === "connected";
ethList.push({
ifname: ifname,
state: state,
connected: isConn
connected: isConn,
connectionName: conName
});
if (isConn && !connected) {
connected = true;
@@ -592,7 +630,7 @@ Singleton {
Process {
id: ethernetDeviceListProcess
running: false
command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"]
command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device"]
stdout: StdioCollector {
onStreamFinished: {
@@ -605,6 +643,7 @@ Singleton {
const dev = parts[0];
const type = parts[1];
const state = parts[2];
const conName = parts.slice(3).join(":") || "";
if (state === "unmanaged") {
continue;
}
@@ -615,7 +654,8 @@ Singleton {
ethList.push({
ifname: dev,
state: state,
connected: state === "connected"
connected: state === "connected",
connectionName: conName
});
}
}
@@ -658,7 +698,7 @@ Singleton {
property string ifname: ""
running: false
// Speed is resolved via ethtool fallback below to avoid stderr warnings
command: ["nmcli", "-t", "-f", "GENERAL.CONNECTION,IP4.ADDRESS,IP4.GATEWAY,IP4.DNS", "device", "show", ifname]
command: ["nmcli", "-t", "-f", "GENERAL.CONNECTION,GENERAL.HWADDR,IP4.ADDRESS,IP4.GATEWAY,IP4.DNS,IP6.ADDRESS,IP6.GATEWAY,IP6.DNS", "device", "show", ifname]
stdout: StdioCollector {
onStreamFinished: {
@@ -675,7 +715,7 @@ Singleton {
details.gateway6 = parsed.gateway6;
details.dns4 = parsed.dns4;
details.dns6 = parsed.dns6;
details.dns = parsed.dns;
details.hwAddr = parsed.hwAddr;
root.activeEthernetDetails = details;
// If speed missing, try sysfs first, then fallback to ethtool
@@ -825,20 +865,20 @@ Singleton {
id: wifiDeviceShowProcess
property string ifname: ""
running: false
command: ["nmcli", "-t", "-f", "IP4.ADDRESS,IP4.GATEWAY,IP4.DNS,IP6.ADDRESS,IP6.GATEWAY,IP6.DNS", "device", "show", ifname]
command: ["nmcli", "-t", "-f", "GENERAL.CONNECTION,IP4.ADDRESS,IP4.GATEWAY,IP4.DNS,IP6.ADDRESS,IP6.GATEWAY,IP6.DNS", "device", "show", ifname]
stdout: StdioCollector {
onStreamFinished: {
const details = root.activeWifiDetails || ({});
const parsed = root.parseIpDetails(text);
details.connectionName = parsed.connectionName;
details.ipv4 = parsed.ipv4;
details.gateway4 = parsed.gateway4;
details.ipv6 = parsed.ipv6;
details.gateway6 = parsed.gateway6;
details.dns4 = parsed.dns4;
details.dns6 = parsed.dns6;
details.dns = parsed.dns;
root.activeWifiDetails = details;
// Try to get link rate (best effort)