diff --git a/include/bar.hpp b/include/bar.hpp index d6cd895f..6f3dfcf9 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -44,6 +44,7 @@ class BarSurface { virtual void setExclusiveZone(bool enable) = 0; virtual void setLayer(bar_layer layer) = 0; virtual void setMargins(const struct bar_margins &margins) = 0; + virtual void setPassThrough(bool enable) = 0; virtual void setPosition(const std::string_view &position) = 0; virtual void setSize(uint32_t width, uint32_t height) = 0; virtual void commit(){}; @@ -64,6 +65,7 @@ class Bar { struct waybar_output *output; Json::Value config; struct wl_surface * surface; + bool exclusive = true; bool visible = true; bool vertical = false; Gtk::Window window; diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 539f4583..2523dc22 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -2,9 +2,7 @@ #include #include -#include #include -#include #include #include #include @@ -28,23 +26,20 @@ class Network : public ALabel { static const uint8_t EPOLL_MAX = 200; static int handleEvents(struct nl_msg*, void*); + static int handleEventsDone(struct nl_msg*, void*); static int handleScan(struct nl_msg*, void*); + void askForStateDump(void); + void worker(); void createInfoSocket(); void createEventSocket(); - int getExternalInterface(int skip_idx = -1) const; - void getInterfaceAddress(); - int netlinkRequest(void*, uint32_t, uint32_t groups = 0) const; - int netlinkResponse(void*, uint32_t, uint32_t groups = 0) const; void parseEssid(struct nlattr**); void parseSignal(struct nlattr**); void parseFreq(struct nlattr**); bool associatedOrJoined(struct nlattr**); - bool checkInterface(struct ifinfomsg* rtif, std::string name); - int getPreferredIface(int skip_idx = -1, bool wait = true) const; + bool checkInterface(std::string name); auto getInfo() -> void; - void checkNewInterface(struct ifinfomsg* rtif); const std::string getNetworkState() const; void clearIface(); bool wildcardMatch(const std::string& pattern, const std::string& text) const; @@ -59,11 +54,17 @@ class Network : public ALabel { int nl80211_id_; std::mutex mutex_; + bool want_route_dump_; + bool want_link_dump_; + bool want_addr_dump_; + bool dump_in_progress_; + unsigned long long bandwidth_down_total_; unsigned long long bandwidth_up_total_; std::string state_; std::string essid_; + bool carrier_; std::string ifname_; std::string ipaddr_; std::string netmask_; diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 430b9fcd..fe11c4a7 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -68,6 +68,17 @@ Also a minimal example configuration can be found on the at the bottom of this m typeof: string ++ Optional name added as a CSS class, for styling multiple waybars. +*exclusive* ++ + typeof: bool ++ + default: *true* unless the layer is set to *overlay* ++ + Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. + +*passthrough* ++ + typeof: bool ++ + default: *false* unless the layer is set to *overlay* ++ + Option to pass any pointer events to the window under the bar. + Intended to be used with either *top* or *overlay* layers and without exclusive zone. + *gtk-layer-shell* ++ typeof: bool ++ default: true ++ diff --git a/src/bar.cpp b/src/bar.cpp index 1dbd69a0..7d763599 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -33,6 +33,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj()); gtk_layer_set_namespace(window_.gobj(), "waybar"); + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap)); window.signal_configure_event().connect_notify( sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure)); } @@ -62,6 +63,18 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { gtk_layer_set_layer(window_.gobj(), layer); } + void setPassThrough(bool enable) override { + passthrough_ = enable; + auto gdk_window = window_.get_window(); + if (gdk_window) { + Cairo::RefPtr region; + if (enable) { + region = Cairo::Region::create(); + } + gdk_window->input_shape_combine_region(region, 0, 0); + } + } + void setPosition(const std::string_view& position) override { auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM; vertical_ = false; @@ -93,8 +106,11 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { std::string output_name_; uint32_t width_; uint32_t height_; + bool passthrough_ = false; bool vertical_ = false; + void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); } + void onConfigure(GdkEventConfigure* ev) { /* * GTK wants new size for the window. @@ -182,6 +198,20 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } } + void setPassThrough(bool enable) override { + passthrough_ = enable; + /* GTK overwrites any region changes applied directly to the wl_surface, + * thus the same GTK region API as in the GLS impl has to be used. */ + auto gdk_window = window_.get_window(); + if (gdk_window) { + Cairo::RefPtr region; + if (enable) { + region = Cairo::Region::create(); + } + gdk_window->input_shape_combine_region(region, 0, 0); + } + } + void setPosition(const std::string_view& position) override { anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; if (position == "bottom") { @@ -230,6 +260,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { uint32_t height_ = 0; uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; bool exclusive_zone_ = true; + bool passthrough_ = false; struct bar_margins margins_; zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; @@ -262,6 +293,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { setSurfaceSize(width_, height_); setExclusiveZone(exclusive_zone_); + setPassThrough(passthrough_); commit(); wl_display_roundtrip(client->wl_display); @@ -377,6 +409,21 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) layer_ = bar_layer::OVERLAY; } + if (config["exclusive"].isBool()) { + exclusive = config["exclusive"].asBool(); + } else if (layer_ == bar_layer::OVERLAY) { + // swaybar defaults: overlay mode does not reserve an exclusive zone + exclusive = false; + } + + bool passthrough = false; + if (config["passthrough"].isBool()) { + passthrough = config["passthrough"].asBool(); + } else if (layer_ == bar_layer::OVERLAY) { + // swaybar defaults: overlay mode does not accept pointer events. + passthrough = true; + } + auto position = config["position"].asString(); if (position == "right" || position == "left") { @@ -386,7 +433,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); vertical = true; } - + left_.get_style_context()->add_class("modules-left"); center_.get_style_context()->add_class("modules-center"); right_.get_style_context()->add_class("modules-right"); @@ -452,8 +499,9 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } surface_impl_->setLayer(layer_); - surface_impl_->setExclusiveZone(true); + surface_impl_->setExclusiveZone(exclusive); surface_impl_->setMargins(margins_); + surface_impl_->setPassThrough(passthrough); surface_impl_->setPosition(position); surface_impl_->setSize(width, height); @@ -492,7 +540,7 @@ void waybar::Bar::setVisible(bool value) { window.set_opacity(1); surface_impl_->setLayer(layer_); } - surface_impl_->setExclusiveZone(visible); + surface_impl_->setExclusiveZone(exclusive && visible); surface_impl_->commit(); } diff --git a/src/client.cpp b/src/client.cpp index 1c48c813..ced9e492 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -207,9 +207,13 @@ std::tuple waybar::Client::getConfigs( const std::string &config, const std::string &style) const { auto config_file = config.empty() ? getValidPath({ "$XDG_CONFIG_HOME/waybar/config", + "$XDG_CONFIG_HOME/waybar/config.jsonc", "$HOME/.config/waybar/config", + "$HOME/.config/waybar/config.jsonc", "$HOME/waybar/config", + "$HOME/waybar/config.jsonc", "/etc/xdg/waybar/config", + "/etc/xdg/waybar/config.jsonc", SYSCONFDIR "/xdg/waybar/config", "./resources/config", }) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a8aaffaf..583daaea 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -86,13 +86,24 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), efd_(-1), ev_fd_(-1), - cidr_(-1), + want_route_dump_(false), + want_link_dump_(false), + want_addr_dump_(false), + dump_in_progress_(false), + cidr_(0), signal_strength_dbm_(0), signal_strength_(0), #ifdef WANT_RFKILL rfkill_{RFKILL_TYPE_WLAN}, #endif frequency_(0) { + + // Start with some "text" in the module's label_, update() will then + // update it. Since the text should be different, update() will be able + // to show or hide the event_box_. This is to work around the case where + // the module start with no text, but the the event_box_ is shown. + label_.set_markup(""); + auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); if (down_octets) { @@ -107,17 +118,25 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf bandwidth_up_total_ = 0; } + if (!config_["interface"].isString()) { + // "interface" isn't configure, then try to guess the external + // interface currently used for internet. + want_route_dump_ = true; + } else { + // Look for an interface that match "interface" + // and then find the address associated with it. + want_link_dump_ = true; + want_addr_dump_ = true; + } + createEventSocket(); createInfoSocket(); - auto default_iface = getPreferredIface(-1, false); - if (default_iface != -1) { - ifid_ = default_iface; - char ifname[IF_NAMESIZE]; - if_indextoname(default_iface, ifname); - ifname_ = ifname; - getInterfaceAddress(); - } + dp.emit(); + // Ask for a dump of interfaces and then addresses to populate our + // information. First the interface dump, and once done, the callback + // will be called again which will ask for addresses dump. + askForStateDump(); worker(); } @@ -148,17 +167,29 @@ void waybar::modules::Network::createEventSocket() { ev_sock_ = nl_socket_alloc(); nl_socket_disable_seq_check(ev_sock_); nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this); + nl_socket_modify_cb(ev_sock_, NL_CB_FINISH, NL_CB_CUSTOM, handleEventsDone, this); auto groups = RTMGRP_LINK | (family_ == AF_INET ? RTMGRP_IPV4_IFADDR : RTMGRP_IPV6_IFADDR); nl_join_groups(ev_sock_, groups); // Deprecated if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) { throw std::runtime_error("Can't connect network socket"); } + if (nl_socket_set_nonblocking(ev_sock_)) { + throw std::runtime_error("Can't set non-blocking on network socket"); + } nl_socket_add_membership(ev_sock_, RTNLGRP_LINK); if (family_ == AF_INET) { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_IFADDR); } else { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); } + if (!config_["interface"].isString()) { + if (family_ == AF_INET) { + nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_ROUTE); + } else { + nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_ROUTE); + } + } + efd_ = epoll_create1(EPOLL_CLOEXEC); if (efd_ < 0) { throw std::runtime_error("Can't create epoll"); @@ -228,7 +259,23 @@ void waybar::modules::Network::worker() { int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1); if (ec > 0) { for (auto i = 0; i < ec; i++) { - if (events[i].data.fd != nl_socket_get_fd(ev_sock_) || nl_recvmsgs_default(ev_sock_) < 0) { + if (events[i].data.fd == nl_socket_get_fd(ev_sock_)) { + int rc = 0; + // Read as many message as possible, until the socket blocks + while (true) { + errno = 0; + rc = nl_recvmsgs_default(ev_sock_); + if (rc == -NLE_AGAIN || errno == EAGAIN) { + rc = 0; + break; + } + } + if (rc < 0) { + spdlog::error("nl_recvmsgs_default error: {}", nl_geterror(-rc)); + thread_.stop(); + break; + } + } else { thread_.stop(); break; } @@ -245,6 +292,7 @@ const std::string waybar::modules::Network::getNetworkState() const { #endif return "disconnected"; } + if (!carrier_) return "disconnected"; if (ipaddr_.empty()) return "linked"; if (essid_.empty()) return "ethernet"; return "wifi"; @@ -342,349 +390,328 @@ auto waybar::modules::Network::update() -> void { ALabel::update(); } -// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 -int waybar::modules::Network::getExternalInterface(int skip_idx) const { - static const uint32_t route_buffer_size = 8192; - struct nlmsghdr * hdr = nullptr; - struct rtmsg * rt = nullptr; - char resp[route_buffer_size] = {0}; - int ifidx = -1; - - /* Prepare request. */ - constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt)); - char req[reqlen] = {0}; - - /* Build the RTM_GETROUTE request. */ - hdr = reinterpret_cast(req); - hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*rt)); - hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - hdr->nlmsg_type = RTM_GETROUTE; - rt = static_cast(NLMSG_DATA(hdr)); - rt->rtm_family = family_; - rt->rtm_table = RT_TABLE_MAIN; - - /* Issue the query. */ - if (netlinkRequest(req, reqlen) < 0) { - goto out; - } - - /* Read the response(s). - * - * WARNING: All the packets generated by the request must be consumed (as in, - * consume responses till NLMSG_DONE/NLMSG_ERROR is encountered). - */ - do { - auto len = netlinkResponse(resp, route_buffer_size); - if (len < 0) { - goto out; - } - - /* Parse the response payload into netlink messages. */ - for (hdr = reinterpret_cast(resp); NLMSG_OK(hdr, len); - hdr = NLMSG_NEXT(hdr, len)) { - if (hdr->nlmsg_type == NLMSG_DONE) { - goto out; - } - if (hdr->nlmsg_type == NLMSG_ERROR) { - /* Even if we found the interface index, something is broken with the - * netlink socket, so return an error. - */ - ifidx = -1; - goto out; - } - - /* If we found the correct answer, skip parsing the attributes. */ - if (ifidx != -1) { - continue; - } - - /* Find the message(s) concerting the main routing table, each message - * corresponds to a single routing table entry. - */ - rt = static_cast(NLMSG_DATA(hdr)); - if (rt->rtm_table != RT_TABLE_MAIN) { - continue; - } - - /* Parse all the attributes for a single routing table entry. */ - struct rtattr *attr = RTM_RTA(rt); - uint64_t attrlen = RTM_PAYLOAD(hdr); - bool has_gateway = false; - bool has_destination = false; - int temp_idx = -1; - for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) { - /* Determine if this routing table entry corresponds to the default - * route by seeing if it has a gateway, and if a destination addr is - * set, that it is all 0s. - */ - switch (attr->rta_type) { - case RTA_GATEWAY: - /* The gateway of the route. - * - * If someone every needs to figure out the gateway address as well, - * it's here as the attribute payload. - */ - has_gateway = true; - break; - case RTA_DST: { - /* The destination address. - * Should be either missing, or maybe all 0s. Accept both. - */ - const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16; - unsigned char c = 0; - size_t dstlen = RTA_PAYLOAD(attr); - if (dstlen != nr_zeroes) { - break; - } - for (uint32_t i = 0; i < dstlen; i += 1) { - c |= *((unsigned char *)RTA_DATA(attr) + i); - } - has_destination = (c == 0); - break; - } - case RTA_OIF: - /* The output interface index. */ - temp_idx = *static_cast(RTA_DATA(attr)); - break; - default: - break; - } - } - /* If this is the default route, and we know the interface index, - * we can stop parsing this message. - */ - if (has_gateway && !has_destination && temp_idx != -1 && temp_idx != skip_idx) { - ifidx = temp_idx; - break; - } - } - } while (true); - -out: - return ifidx; -} - -void waybar::modules::Network::getInterfaceAddress() { - struct ifaddrs *ifaddr, *ifa; - cidr_ = 0; - int success = getifaddrs(&ifaddr); - if (success != 0) { - return; - } - ifa = ifaddr; - while (ifa != nullptr) { - if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_ && - ifa->ifa_name == ifname_) { - char ipaddr[INET6_ADDRSTRLEN]; - char netmask[INET6_ADDRSTRLEN]; - unsigned int cidr = 0; - if (family_ == AF_INET) { - ipaddr_ = inet_ntop(AF_INET, - &reinterpret_cast(ifa->ifa_addr)->sin_addr, - ipaddr, - INET_ADDRSTRLEN); - auto net_addr = reinterpret_cast(ifa->ifa_netmask); - netmask_ = inet_ntop(AF_INET, &net_addr->sin_addr, netmask, INET_ADDRSTRLEN); - unsigned int cidrRaw = net_addr->sin_addr.s_addr; - while (cidrRaw) { - cidr += cidrRaw & 1; - cidrRaw >>= 1; - } - } else { - ipaddr_ = inet_ntop(AF_INET6, - &reinterpret_cast(ifa->ifa_addr)->sin6_addr, - ipaddr, - INET6_ADDRSTRLEN); - auto net_addr = reinterpret_cast(ifa->ifa_netmask); - netmask_ = inet_ntop(AF_INET6, &net_addr->sin6_addr, netmask, INET6_ADDRSTRLEN); - for (size_t i = 0; i < sizeof(net_addr->sin6_addr.s6_addr); ++i) { - unsigned char cidrRaw = net_addr->sin6_addr.s6_addr[i]; - while (cidrRaw) { - cidr += cidrRaw & 1; - cidrRaw >>= 1; - } - } - } - cidr_ = cidr; - break; - } - ifa = ifa->ifa_next; - } - freeifaddrs(ifaddr); -} - -int waybar::modules::Network::netlinkRequest(void *req, uint32_t reqlen, uint32_t groups) const { - struct sockaddr_nl sa = {}; - sa.nl_family = AF_NETLINK; - sa.nl_groups = groups; - struct iovec iov = {req, reqlen}; - struct msghdr msg = { - .msg_name = &sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - return sendmsg(nl_socket_get_fd(ev_sock_), &msg, 0); -} - -int waybar::modules::Network::netlinkResponse(void *resp, uint32_t resplen, uint32_t groups) const { - struct sockaddr_nl sa = {}; - sa.nl_family = AF_NETLINK; - sa.nl_groups = groups; - struct iovec iov = {resp, resplen}; - struct msghdr msg = { - .msg_name = &sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - auto ret = recvmsg(nl_socket_get_fd(ev_sock_), &msg, 0); - if (msg.msg_flags & MSG_TRUNC) { - return -1; - } - return ret; -} - -bool waybar::modules::Network::checkInterface(struct ifinfomsg *rtif, std::string name) { +bool waybar::modules::Network::checkInterface(std::string name) { if (config_["interface"].isString()) { return config_["interface"].asString() == name || wildcardMatch(config_["interface"].asString(), name); } - // getExternalInterface may need some delay to detect external interface - for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) { - auto external_iface = getExternalInterface(); - if (external_iface > 0) { - return external_iface == rtif->ifi_index; - } - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } return false; } -int waybar::modules::Network::getPreferredIface(int skip_idx, bool wait) const { - int ifid = -1; - if (config_["interface"].isString()) { - ifid = if_nametoindex(config_["interface"].asCString()); - if (ifid > 0) { - return ifid; - } else { - // Try with wildcard - struct ifaddrs *ifaddr, *ifa; - int success = getifaddrs(&ifaddr); - if (success != 0) { - return -1; - } - ifa = ifaddr; - ifid = -1; - while (ifa != nullptr) { - if (wildcardMatch(config_["interface"].asString(), ifa->ifa_name)) { - ifid = if_nametoindex(ifa->ifa_name); - break; - } - ifa = ifa->ifa_next; - } - freeifaddrs(ifaddr); - return ifid; - } - } - // getExternalInterface may need some delay to detect external interface - for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) { - ifid = getExternalInterface(skip_idx); - if (ifid > 0) { - return ifid; - } - if (wait) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - } - return -1; -} - void waybar::modules::Network::clearIface() { + ifid_ = -1; essid_.clear(); ipaddr_.clear(); netmask_.clear(); + carrier_ = false; cidr_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; frequency_ = 0; } -void waybar::modules::Network::checkNewInterface(struct ifinfomsg *rtif) { - auto new_iface = getPreferredIface(rtif->ifi_index); - if (new_iface != -1) { - ifid_ = new_iface; - char ifname[IF_NAMESIZE]; - if_indextoname(new_iface, ifname); - ifname_ = ifname; - getInterfaceAddress(); - thread_timer_.wake_up(); - } else { - ifid_ = -1; - dp.emit(); - } -} - int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { auto net = static_cast(data); std::lock_guard lock(net->mutex_); auto nh = nlmsg_hdr(msg); - auto ifi = static_cast(NLMSG_DATA(nh)); - if (nh->nlmsg_type == RTM_DELADDR) { - // Check for valid interface - if (ifi->ifi_index == net->ifid_) { - net->ipaddr_.clear(); - net->netmask_.clear(); - net->cidr_ = 0; - if (!(ifi->ifi_flags & IFF_RUNNING)) { - net->clearIface(); - // Check for a new interface and get info - net->checkNewInterface(ifi); - } else { - net->dp.emit(); - } + bool is_del_event = false; + + switch (nh->nlmsg_type) { + case RTM_DELLINK: + is_del_event = true; + case RTM_NEWLINK: { + struct ifinfomsg *ifi = static_cast(NLMSG_DATA(nh)); + ssize_t attrlen = IFLA_PAYLOAD(nh); + struct rtattr *ifla = IFLA_RTA(ifi); + const char *ifname = NULL; + size_t ifname_len = 0; + std::optional carrier; + + if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) { return NL_OK; } - } else if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK) { - char ifname[IF_NAMESIZE]; - if_indextoname(ifi->ifi_index, ifname); - // Check for valid interface - if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) { - net->ifname_ = ifname; - net->ifid_ = ifi->ifi_index; - // Get Iface and WIFI info - net->getInterfaceAddress(); - net->thread_timer_.wake_up(); - return NL_OK; - } else if (ifi->ifi_index == net->ifid_ && - (!(ifi->ifi_flags & IFF_RUNNING) || !(ifi->ifi_flags & IFF_UP) || - !net->checkInterface(ifi, ifname))) { + + for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) { + switch (ifla->rta_type) { + case IFLA_IFNAME: + ifname = static_cast(RTA_DATA(ifla)); + ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0 + break; + case IFLA_CARRIER: { + carrier = *(char*)RTA_DATA(ifla) == 1; + break; + } + } + } + + if (!is_del_event && ifi->ifi_index == net->ifid_) { + // Update inferface information + if (net->ifname_.empty() && ifname != NULL) { + std::string new_ifname (ifname, ifname_len); + net->ifname_ = new_ifname; + } + if (carrier.has_value()) { + if (net->carrier_ != *carrier) { + if (*carrier) { + // Ask for WiFi information + net->thread_timer_.wake_up(); + } else { + // clear state related to WiFi connection + net->essid_.clear(); + net->signal_strength_dbm_ = 0; + net->signal_strength_ = 0; + net->frequency_ = 0; + } + } + net->carrier_ = carrier.value(); + } + } else if (!is_del_event && net->ifid_ == -1) { + // Checking if it's an interface we care about. + std::string new_ifname (ifname, ifname_len); + if (net->checkInterface(new_ifname)) { + spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index); + + net->ifname_ = new_ifname; + net->ifid_ = ifi->ifi_index; + if (carrier.has_value()) { + net->carrier_ = carrier.value(); + } + net->thread_timer_.wake_up(); + /* An address for this new interface should be received via an + * RTM_NEWADDR event either because we ask for a dump of both links + * and addrs, or because this interface has just been created and + * the addr will be sent after the RTM_NEWLINK event. + * So we don't need to do anything. */ + } + } else if (is_del_event && net->ifid_ >= 0) { + // Our interface has been deleted, start looking/waiting for one we care. + spdlog::debug("network: interface {}/{} deleted", net->ifname_, net->ifid_); + net->clearIface(); - // Check for a new interface and get info - net->checkNewInterface(ifi); - return NL_OK; - } - } else { - char ifname[IF_NAMESIZE]; - if_indextoname(ifi->ifi_index, ifname); - // Auto detected network can also be assigned here - if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) { - // If iface is different, clear data - if (ifi->ifi_index != net->ifid_) { - net->clearIface(); - } - net->ifname_ = ifname; - net->ifid_ = ifi->ifi_index; - } - // Check for valid interface - if (ifi->ifi_index == net->ifid_) { - // Get Iface and WIFI info - net->getInterfaceAddress(); - net->thread_timer_.wake_up(); - return NL_OK; + net->dp.emit(); } + break; } - return NL_SKIP; + + case RTM_DELADDR: + is_del_event = true; + case RTM_NEWADDR: { + struct ifaddrmsg *ifa = static_cast(NLMSG_DATA(nh)); + ssize_t attrlen = IFA_PAYLOAD(nh); + struct rtattr *ifa_rta = IFA_RTA(ifa); + + if ((int)ifa->ifa_index != net->ifid_) { + return NL_OK; + } + + if (ifa->ifa_family != net->family_) { + return NL_OK; + } + + // We ignore address mark as scope for the link or host, + // which should leave scope global addresses. + if (ifa->ifa_scope >= RT_SCOPE_LINK) { + return NL_OK; + } + + for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) { + switch (ifa_rta->rta_type) { + case IFA_ADDRESS: { + char ipaddr[INET6_ADDRSTRLEN]; + if (!is_del_event) { + net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), + ipaddr, sizeof (ipaddr)); + net->cidr_ = ifa->ifa_prefixlen; + switch (ifa->ifa_family) { + case AF_INET: { + struct in_addr netmask; + netmask.s_addr = htonl(~0 << (32 - ifa->ifa_prefixlen)); + net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, + ipaddr, sizeof (ipaddr)); + } + case AF_INET6: { + struct in6_addr netmask; + for (int i = 0; i < 16; i++) { + int v = (i + 1) * 8 - ifa->ifa_prefixlen; + if (v < 0) v = 0; + if (v > 8) v = 8; + netmask.s6_addr[i] = ~0 << v; + } + net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, + ipaddr, sizeof (ipaddr)); + } + } + spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); + } else { + net->ipaddr_.clear(); + net->cidr_ = 0; + net->netmask_.clear(); + spdlog::debug("network: {} addr deleted {}/{}", + net->ifname_, + inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), + ipaddr, sizeof (ipaddr)), + ifa->ifa_prefixlen); + } + net->dp.emit(); + break; + } + } + } + break; + } + + case RTM_DELROUTE: + is_del_event = true; + case RTM_NEWROUTE: { + // Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 + // to find the interface used to reach the outside world + + struct rtmsg *rtm = static_cast(NLMSG_DATA(nh)); + ssize_t attrlen = RTM_PAYLOAD(nh); + struct rtattr *attr = RTM_RTA(rtm); + bool has_gateway = false; + bool has_destination = false; + int temp_idx = -1; + + /* If we found the correct answer, skip parsing the attributes. */ + if (!is_del_event && net->ifid_ != -1) { + return NL_OK; + } + + /* Find the message(s) concerting the main routing table, each message + * corresponds to a single routing table entry. + */ + if (rtm->rtm_table != RT_TABLE_MAIN) { + return NL_OK; + } + + /* Parse all the attributes for a single routing table entry. */ + for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) { + /* Determine if this routing table entry corresponds to the default + * route by seeing if it has a gateway, and if a destination addr is + * set, that it is all 0s. + */ + switch(attr->rta_type) { + case RTA_GATEWAY: + /* The gateway of the route. + * + * If someone every needs to figure out the gateway address as well, + * it's here as the attribute payload. + */ + has_gateway = true; + break; + case RTA_DST: { + /* The destination address. + * Should be either missing, or maybe all 0s. Accept both. + */ + const uint32_t nr_zeroes = (net->family_ == AF_INET) ? 4 : 16; + unsigned char c = 0; + size_t dstlen = RTA_PAYLOAD(attr); + if (dstlen != nr_zeroes) { + break; + } + for (uint32_t i = 0; i < dstlen; i += 1) { + c |= *((unsigned char *)RTA_DATA(attr) + i); + } + has_destination = (c == 0); + break; + } + case RTA_OIF: + /* The output interface index. */ + temp_idx = *static_cast(RTA_DATA(attr)); + break; + default: + break; + } + + /* If this is the default route, and we know the interface index, + * we can stop parsing this message. + */ + if (has_gateway && !has_destination && temp_idx != -1) { + if (!is_del_event) { + net->ifid_ = temp_idx; + + spdlog::debug("network: new default route via if{}", temp_idx); + + /* Ask ifname associated with temp_idx as well as carrier status */ + struct ifinfomsg ifinfo_hdr = { + .ifi_family = AF_UNSPEC, + .ifi_index = temp_idx, + }; + int err; + err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST, + &ifinfo_hdr, sizeof (ifinfo_hdr)); + if (err < 0) { + spdlog::error("network: failed to ask link info: {}", err); + /* Ask for a dump of all links instead */ + net->want_link_dump_ = true; + } + + /* Also ask for the address. Asking for a addresses of a specific + * interface doesn't seems to work so ask for a dump of all + * addresses. */ + net->want_addr_dump_ = true; + net->askForStateDump(); + net->thread_timer_.wake_up(); + } else if (is_del_event && temp_idx == net->ifid_) { + spdlog::debug("network: default route deleted {}/if{}", + net->ifname_, temp_idx); + + net->ifname_.clear(); + net->clearIface(); + net->dp.emit(); + /* Ask for a dump of all routes in case another one is already + * setup. If there's none, there'll be an event with new one + * later. */ + net->want_route_dump_ = true; + net->askForStateDump(); + } + } + } + break; + } + } + + return NL_OK; +} + +void waybar::modules::Network::askForStateDump(void) { + /* We need to wait until the current dump is done before sending new + * messages. handleEventsDone() is called when a dump is done. */ + if (dump_in_progress_) + return; + + struct rtgenmsg rt_hdr = { + .rtgen_family = AF_UNSPEC, + }; + + if (want_route_dump_) { + rt_hdr.rtgen_family = family_; + nl_send_simple(ev_sock_, RTM_GETROUTE, NLM_F_DUMP, + &rt_hdr, sizeof (rt_hdr)); + want_route_dump_ = false; + dump_in_progress_ = true; + + } else if (want_link_dump_) { + nl_send_simple(ev_sock_, RTM_GETLINK, NLM_F_DUMP, + &rt_hdr, sizeof (rt_hdr)); + want_link_dump_ = false; + dump_in_progress_ = true; + + } else if (want_addr_dump_) { + rt_hdr.rtgen_family = family_; + nl_send_simple(ev_sock_, RTM_GETADDR, NLM_F_DUMP, + &rt_hdr, sizeof (rt_hdr)); + want_addr_dump_ = false; + dump_in_progress_ = true; + } +} + +int waybar::modules::Network::handleEventsDone(struct nl_msg *msg, void *data) { + auto net = static_cast(data); + net->dump_in_progress_ = false; + net->askForStateDump(); + return NL_OK; } int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {