Skip to content

Commit 4719392

Browse files
authored
network widget: show a spinner when ethernet is resolving (#3079)
1 parent 8ddb84e commit 4719392

6 files changed

Lines changed: 87 additions & 10 deletions

File tree

src/dbus/network/network_manager_service.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,13 @@ namespace {
3838
constexpr std::string_view kNmWiredConnectionType = "802-3-ethernet";
3939

4040
// NMDeviceType values from NetworkManager D-Bus API.
41+
constexpr std::uint32_t kNmDeviceTypeEthernet = 1;
4142
constexpr std::uint32_t kNmDeviceTypeWifi = 2;
4243

44+
// NMDeviceState: a device between Prepare and Activated is mid-activation.
45+
constexpr std::uint32_t kNmDeviceStatePrepare = 40;
46+
constexpr std::uint32_t kNmDeviceStateActivated = 100;
47+
4348
// NMActiveConnectionState
4449
constexpr std::uint32_t kNmActiveConnectionStateActivating = 1;
4550
constexpr std::uint32_t kNmActiveConnectionStateActivated = 2;
@@ -1422,6 +1427,36 @@ void NetworkManagerService::finishRefreshAccessPoints(
14221427
onComplete();
14231428
}
14241429

1430+
std::string NetworkManagerService::physicalActivatingConnection() {
1431+
std::vector<sdbus::ObjectPath> devices;
1432+
try {
1433+
m_nm->callMethod("GetDevices").onInterface(kNmInterface).storeResultsTo(devices);
1434+
} catch (const sdbus::Error&) {
1435+
return {};
1436+
}
1437+
1438+
for (const auto& devicePath : devices) {
1439+
try {
1440+
auto device = sdbus::createProxy(m_bus.connection(), kNmBusName, devicePath);
1441+
const auto deviceType = getPropertyOr<std::uint32_t>(*device, kNmDeviceInterface, "DeviceType", 0U);
1442+
if (deviceType != kNmDeviceTypeEthernet && deviceType != kNmDeviceTypeWifi) {
1443+
continue;
1444+
}
1445+
const auto state = getPropertyOr<std::uint32_t>(*device, kNmDeviceInterface, "State", 0U);
1446+
if (state < kNmDeviceStatePrepare || state >= kNmDeviceStateActivated) {
1447+
continue;
1448+
}
1449+
const auto active =
1450+
getPropertyOr<sdbus::ObjectPath>(*device, kNmDeviceInterface, "ActiveConnection", sdbus::ObjectPath{"/"});
1451+
if (!active.empty() && active != "/") {
1452+
return active;
1453+
}
1454+
} catch (const sdbus::Error&) {
1455+
}
1456+
}
1457+
return {};
1458+
}
1459+
14251460
void NetworkManagerService::rebindActiveConnection() {
14261461
std::string newPath;
14271462
try {
@@ -1431,6 +1466,14 @@ void NetworkManagerService::rebindActiveConnection() {
14311466
kLog.debug("PrimaryConnection unavailable: {}", e.what());
14321467
}
14331468

1469+
// PrimaryConnection stays "/" until a connection finishes activating, and NM's
1470+
// ActivatingConnection property tracks loopback rather than the real link. Fall
1471+
// back to a physical ethernet/wifi device that is mid-activation so the resolving
1472+
// state is observable while virtual devices (loopback, bridge, vlan, …) are not.
1473+
if (newPath.empty() || newPath == "/") {
1474+
newPath = physicalActivatingConnection();
1475+
}
1476+
14341477
if (newPath != m_activeConnectionPath) {
14351478
m_activeConnectionPath = newPath;
14361479
m_activeConnection.reset();
@@ -1803,6 +1846,7 @@ void NetworkManagerService::readStateAsync(std::function<void(NetworkState)> onC
18031846

18041847
next->vpnActive = (type == "vpn" || type == "wireguard");
18051848
next->connected = state == kNmActiveConnectionStateActivated;
1849+
next->resolving = state == kNmActiveConnectionStateActivating;
18061850
}
18071851

18081852
readDeviceState();

src/dbus/network/network_manager_service.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class NetworkManagerService : public INetworkService {
8181
void persistConnectionToDisk(const std::string& connectionPath, const std::string& ssid);
8282
void deleteUnsavedConnection(const std::string& connectionPath, const std::string& ssid);
8383
void rebindActiveConnection();
84+
[[nodiscard]] std::string physicalActivatingConnection();
8485
void rebindActiveDevice(const std::string& devicePath);
8586
void rebindActiveAccessPoint(const std::string& apPath);
8687
void ensureWifiDeviceSubscribed(const std::string& devicePath);

src/dbus/network/network_types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ enum class NetworkConnectivity {
3232
struct NetworkState {
3333
NetworkConnectivity kind = NetworkConnectivity::Unknown;
3434
bool connected = false;
35+
bool resolving = false; // active connection is activating, not yet connected
3536
bool wirelessEnabled = false;
3637
bool scanning = false;
3738
bool vpnActive = false; // true if a VPN is the active connection

src/shell/bar/widgets/network_widget.cpp

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ namespace {
3232

3333
std::string onOffText(bool enabled) { return enabled ? "On" : "Off"; }
3434

35+
std::string disconnectedText(bool resolving) { return resolving ? "Connecting" : "Not connected"; }
36+
3537
std::string yesNoText(bool enabled) { return enabled ? "Yes" : "No"; }
3638

3739
std::string networkCountText(std::size_t count) {
@@ -68,6 +70,16 @@ void NetworkWidget::create() {
6870
})
6971
);
7072

73+
// Replaces the glyph while a wired link is activating.
74+
area->addChild(
75+
ui::spinner({
76+
.out = &m_spinner,
77+
.color = widgetIconColorOr(colorSpecFromRole(ColorRole::OnSurface)),
78+
.spinnerSize = Style::baseGlyphSize * 0.8f * m_contentScale,
79+
.visible = false,
80+
})
81+
);
82+
7183
// Always create the label node: horizontal bars honor m_showLabel, but
7284
// vertical bars always display a 3-char truncation under the glyph to match
7385
// volume/brightness.
@@ -96,18 +108,22 @@ void NetworkWidget::doLayout(Renderer& renderer, float containerWidth, float con
96108
m_label->measure(renderer);
97109
}
98110

111+
// Glyph and spinner share one slot; only one is visible.
112+
Node* icon =
113+
(m_spinner != nullptr && m_spinner->visible()) ? static_cast<Node*>(m_spinner) : static_cast<Node*>(m_glyph);
114+
99115
const bool labelVisible = m_label != nullptr && m_label->width() > 0.0f && m_label->visible();
100116
if (m_isVertical && labelVisible) {
101-
const float w = std::max(m_glyph->width(), m_label->width());
102-
m_glyph->setPosition(std::round((w - m_glyph->width()) * 0.5f), 0.0f);
103-
m_label->setPosition(std::round((w - m_label->width()) * 0.5f), m_glyph->height());
104-
rootNode->setSize(w, m_glyph->height() + m_label->height());
117+
const float w = std::max(icon->width(), m_label->width());
118+
icon->setPosition(std::round((w - icon->width()) * 0.5f), 0.0f);
119+
m_label->setPosition(std::round((w - m_label->width()) * 0.5f), icon->height());
120+
rootNode->setSize(w, icon->height() + m_label->height());
105121
} else {
106-
const float h = labelVisible ? std::max(m_glyph->height(), m_label->height()) : m_glyph->height();
107-
m_glyph->setPosition(0.0f, std::round((h - m_glyph->height()) * 0.5f));
108-
float totalWidth = m_glyph->width();
122+
const float h = labelVisible ? std::max(icon->height(), m_label->height()) : icon->height();
123+
icon->setPosition(0.0f, std::round((h - icon->height()) * 0.5f));
124+
float totalWidth = icon->width();
109125
if (labelVisible) {
110-
m_label->setPosition(m_glyph->width() + Style::spaceXs, std::round((h - m_label->height()) * 0.5f));
126+
m_label->setPosition(icon->width() + Style::spaceXs, std::round((h - m_label->height()) * 0.5f));
111127
totalWidth = m_label->x() + m_label->width();
112128
}
113129
rootNode->setSize(totalWidth, h);
@@ -129,6 +145,9 @@ void NetworkWidget::syncState(Renderer& renderer) {
129145
m_haveLastState = true;
130146
m_lastVertical = m_isVertical;
131147

148+
const bool showSpinner = s.kind == NetworkConnectivity::Wired && s.resolving;
149+
150+
m_glyph->setVisible(!showSpinner);
132151
m_glyph->setGlyph(network_glyphs::glyphForState(s));
133152
m_glyph->setGlyphSize(Style::baseGlyphSize * m_contentScale);
134153
m_glyph->setColor(
@@ -137,6 +156,16 @@ void NetworkWidget::syncState(Renderer& renderer) {
137156
);
138157
m_glyph->measure(renderer);
139158

159+
if (m_spinner != nullptr) {
160+
m_spinner->setVisible(showSpinner);
161+
m_spinner->setSpinnerSize(Style::baseGlyphSize * 0.8f * m_contentScale);
162+
if (showSpinner && !m_spinner->spinning()) {
163+
m_spinner->start();
164+
} else if (!showSpinner && m_spinner->spinning()) {
165+
m_spinner->stop();
166+
}
167+
}
168+
140169
if (m_label != nullptr) {
141170
const bool showLabel = m_showLabel;
142171
m_label->setVisible(showLabel);
@@ -216,7 +245,7 @@ std::vector<TooltipRow> NetworkWidget::buildTooltipRows() const {
216245
return rows;
217246
}
218247

219-
rows.push_back({"Network", "Not connected"});
248+
rows.push_back({"Network", disconnectedText(s.resolving)});
220249
rows.push_back({"Wi-Fi", onOffText(s.wirelessEnabled)});
221250
if (s.scanning) {
222251
rows.push_back({"Scanning", yesNoText(s.scanning)});

src/shell/bar/widgets/network_widget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
class Glyph;
1111
class Label;
12+
class Spinner;
1213
class SystemMonitorService;
1314
struct wl_output;
1415

@@ -28,6 +29,7 @@ class NetworkWidget : public Widget {
2829
SystemMonitorService* m_monitor = nullptr;
2930
bool m_showLabel = true;
3031
Glyph* m_glyph = nullptr;
32+
Spinner* m_spinner = nullptr;
3133
Label* m_label = nullptr;
3234
NetworkState m_lastState;
3335
bool m_haveLastState = false;

src/ui/controls/spinner.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace {
1111

1212
constexpr float kDefaultSize = 20.0f;
1313
constexpr float kDefaultThickness = 2.0f;
14-
constexpr float kRevolutionMs = 800.0f;
14+
constexpr float kRevolutionMs = 1200.0f;
1515
constexpr float kTwoPi = 2.0f * std::numbers::pi_v<float>;
1616

1717
} // namespace

0 commit comments

Comments
 (0)