From faf6a62bcf90124be0e65a8786ebc1c44a88c786 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 13:42:13 +0200 Subject: [PATCH] fix(network): detect ethernet cable unplug again (carrier/operstate) PR #4190 (merged as 93d85a0) reworked getNetworkState() so the rfkill "disabled" state is evaluated whenever the module has no carrier. Because the module always watches an RFKILL_TYPE_WLAN switch, a wired ethernet module whose cable is unplugged (carrier lost) would return "disabled" instead of "disconnected" whenever the system's WLAN radio happened to be rfkill-blocked. With no format-disabled configured, that state falls back to plain "format", so the interface kept looking connected after unplug. rfkill only concerns wireless radios, so only honor it when there is no interface at all or the current interface is actually wireless (detected via /sys/class/net//phy80211 or /wireless). A wired interface that lost its carrier now correctly reports "disconnected", while wifi rfkill display from #4190 is preserved. Fixes #4364. --- include/modules/network.hpp | 1 + src/modules/network.cpp | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index abab16c2..a74ffda2 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -51,6 +51,7 @@ class Network : public ALabel { bool matchInterface(const std::string& ifname, const std::vector& altnames, std::string& matched) const; auto getInfo() -> void; + bool isWireless() const; const std::string getNetworkState() const; void clearIface(); std::optional> readBandwidthUsage(); diff --git a/src/modules/network.cpp b/src/modules/network.cpp index f69d4f33..7b4dc15f 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -288,6 +289,19 @@ void waybar::modules::Network::worker() { }; } +bool waybar::modules::Network::isWireless() const { + // The rfkill switch we monitor (and thus the "disabled" state) only applies + // to wireless radios. An interface is wireless if the kernel exposes an + // 802.11 phy (cfg80211/mac80211) or a legacy "wireless" node for it in sysfs. + if (ifname_.empty()) { + return false; + } + const auto base = "/sys/class/net/" + ifname_; + std::error_code ec; + return std::filesystem::exists(base + "/phy80211", ec) || + std::filesystem::exists(base + "/wireless", ec); +} + const std::string waybar::modules::Network::getNetworkState() const { if (ifid_ == -1 || !carrier_) { #ifdef WANT_RFKILL @@ -295,7 +309,14 @@ const std::string waybar::modules::Network::getNetworkState() const { if (config_["rfkill"].isBool()) { display_rfkill = config_["rfkill"].asBool(); } - if (rfkill_.getState() && display_rfkill) return "disabled"; + // The rfkill switch is for wireless (WLAN) radios only, so it must not mask + // a wired interface that merely lost its carrier (e.g. an unplugged ethernet + // cable): such an interface has to report "disconnected", not "disabled", + // otherwise cable-unplug detection breaks on ethernet modules (#4364). + // Only honor rfkill when there is no interface or the interface is wireless. + if (rfkill_.getState() && display_rfkill && (ifname_.empty() || isWireless())) { + return "disabled"; + } #endif return "disconnected"; }