Merge pull request #4557 from adryzz/wwan-module

WWAN module (ModemManager)
This commit is contained in:
Alexis Rouillard
2026-07-04 01:48:42 +02:00
committed by GitHub
8 changed files with 520 additions and 1 deletions
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <fmt/format.h>
#include <libmm-glib/libmm-glib.h>
#include <sys/statvfs.h>
#include <fstream>
#include "ALabel.hpp"
#include "util/format.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Wwan : public ALabel {
public:
Wwan(const std::string&, const Json::Value&);
virtual ~Wwan();
auto update() -> void override;
private:
void updateCurrentModem();
util::SleeperThread thread_;
std::string state_;
GDBusConnection* connection = nullptr;
MMManager* manager = nullptr;
MMModem* current_modem = nullptr;
bool hideDisconnected = true;
const std::string dbus_name = "org.freedesktop.ModemManager1";
const std::string dbus_obj_path = "/org/freedesktop/ModemManager1/";
const std::string dbus_modems_path = "/org/freedesktop/ModemManager1/Modems/";
};
} // namespace waybar::modules
+156
View File
@@ -0,0 +1,156 @@
waybar-wwan(5) "waybar-wwan" "User Manual"
# NAME
waybar - wwan module
# DESCRIPTION
*wwan* module for ModemManager.
# FILES
$XDG_CONFIG_HOME/waybar/config ++
Per user configuration file
# ADDITIONAL FILES
libmm-glib lives in:
. /usr/lib/libmm-glib.so or /usr/lib64/libmm-glib.so
. /usr/lib/pkgconfig/libmm-glib.pc or /usr/lib64/pkgconfig/libmm-glib.pc
. /usr/include/libmm-glib
# CONFIGURATION
*format*: ++
typeof: string ++
default: *{state}* ++
The format, how information should be displayed. This format is used when other formats aren't specified.
*format-failed*: ++
typeof: string ++
This format is used when the modem cannot start.
*format-locked*: ++
typeof: string ++
This format is used when the SIM on the modem is locked.
*format-disabled*: ++
typeof: string ++
This format is used when the modem is disabled.
*format-enabled*: ++
typeof: string ++
This format is used when the modem is enabled, but isn't registered to any carrier network.
*format-searching*: ++
typeof: string ++
This format is used when the modem is looking for a carrier network to register to.
*format-registered*: ++
typeof: string ++
This format is used when the modem is registered to a network, but no data connection is active.
*format-connected*: ++
typeof: string ++
This format is used when the modem is registered to a network, and there is an active data connection.
*format-icons*: ++
typeof: array/object ++
Based on the current signal strength, the corresponding icon gets selected. ++
The order is *low* to *high*. Or by the state if it is an object.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: strirng ++
default: {state} ++
The format, how information should be displayed in the tooltip. This format is used when other formats aren't specified.
*tooltip-format-failed*: ++
typeof: string ++
This format is used when the modem cannot start.
*tooltip-format-locked*: ++
typeof: string ++
This format is used when the SIM on the modem is locked.
*tooltip-format-disabled*: ++
typeof: string ++
This format is used when the modem is disabled.
*tooltip-format-enabled*: ++
typeof: string ++
This format is used when the modem is enabled, but isn't registered to any carrier network.
*tooltip-format-searching*: ++
typeof: string ++
This format is used when the modem is looking for a carrier network to register to.
*tooltip-format-registered*: ++
typeof: string ++
This format is used when the modem is registered to a network, but no data connection is active.
*tooltip-format-connected*: ++
typeof: string ++
This format is used when the modem is registered to a network, and there is an active data connection.
*interval*: ++
typeof: integer ++
default: 5 ++
The interval in which the WWAN information gets polled (e.g. carrier name).
*imei*: ++
typeof: string ++
Select only the modem with the corresponding imei.
*path*: ++
typeof: string ++
Select only the modem with the corresponding hardware path.
*hide-disconnected*: ++
typeof: bool ++
default: true ++
Defines if the module should be hidden while the modem does not have an active data connection.
# FORMAT REPLACEMENTS
*{state}*: Modem state.
*{current_modes}*: List of all the currently active connection modes.
*{preferred_mode}*: The preferred connection mode in use.
*{signal_quality}*: Signal quality (0-100).
*{operator_name}*: Name of the network operator.
# EXAMPLES
```
"wwan": {
"format": "{state}",
"format-disabled": "", // an empty format will hide the module
"format-locked": "SIM Locked",
"format-connected": "{operator_name} {preferred_mode} | {signal_quality}%"
"tooltip-format": "{mode}",
"tooltip-format-connected": "{current_modes}",
"hide-disconnected": false
}
```
# STYLE
- *#wwan*
- *#wwan.failed* Applied when the modem cannot start.
- *#wwan.locked* Applied when the SIM on the modem is locked.
- *#wwan.disabled* Applied when the modem is disabled.
- *#wwan.enabled* Applied when the modem is enabled, but isn't registered to any carrier network.
- *#wwan.searching* Applied when the modem is looking for a carrier network to register to.
- *#wwan.registered* Applied when the modem is registered to a network, but no data connection is active.
- *#wwan.connected* Applied when the modem is registered to a network, and there is an active data connection.
+9 -1
View File
@@ -94,6 +94,7 @@ xkbregistry = dependency('xkbregistry')
libjack = dependency('jack', required: get_option('jack')) libjack = dependency('jack', required: get_option('jack'))
libwireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber')) libwireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber'))
libgps = dependency('libgps', required: get_option('gps')) libgps = dependency('libgps', required: get_option('gps'))
libmm_glib = dependency('mm-glib', required: get_option('wwan'))
libsndio = compiler.find_library('sndio', required: get_option('sndio')) libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found() if libsndio.found()
@@ -557,6 +558,12 @@ if libgps.found()
man_files += files('man/waybar-gps.5.scd') man_files += files('man/waybar-gps.5.scd')
endif endif
if libmm_glib.found()
add_project_arguments('-DHAVE_LIBMM_GLIB', language: 'cpp')
src_files += files('src/modules/wwan.cpp')
man_files += files('man/waybar-wwan.5.scd')
endif
subdir('protocol') subdir('protocol')
app_resources = [] app_resources = []
@@ -598,7 +605,8 @@ executable(
xkbregistry, xkbregistry,
cava, cava,
eproxy, eproxy,
libgps libgps,
libmm_glib
], ],
include_directories: inc_dirs, include_directories: inc_dirs,
install: true, install: true,
+1
View File
@@ -23,3 +23,4 @@ option('niri', type: 'boolean', description: 'Enable support for niri')
option('mango', type: 'boolean', description: 'Enable support for mango') option('mango', type: 'boolean', description: 'Enable support for mango')
option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface') option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface')
option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps') option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps')
option('wwan', type: 'feature', value: 'auto', description: 'Enable support for WWAN')
+1
View File
@@ -36,6 +36,7 @@ waybar.overrideAttrs (oldAttrs: {
++ [ ++ [
pkgs.wireplumber pkgs.wireplumber
pkgs.gpsd pkgs.gpsd
pkgs.modemmanager
]; ];
postUnpack = '' postUnpack = ''
+8
View File
@@ -123,6 +123,9 @@
#include "modules/gps.hpp" #include "modules/gps.hpp"
#endif #endif
#include "modules/cava/cava_frontend.hpp" #include "modules/cava/cava_frontend.hpp"
#ifdef HAVE_LIBMM_GLIB
#include "modules/wwan.hpp"
#endif
#include "modules/cffi.hpp" #include "modules/cffi.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/custom_graph.hpp" #include "modules/custom_graph.hpp"
@@ -380,6 +383,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
if (ref == "gps") { if (ref == "gps") {
return new waybar::modules::Gps(id, config_[name]); return new waybar::modules::Gps(id, config_[name]);
} }
#endif
#ifdef HAVE_LIBMM_GLIB
if (ref == "wwan") {
return new waybar::modules::Wwan(id, config_[name]);
}
#endif #endif
if (ref == "temperature") { if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]); return new waybar::modules::Temperature(id, config_[name]);
+308
View File
@@ -0,0 +1,308 @@
#include "modules/wwan.hpp"
#include <spdlog/spdlog.h>
#include <cmath>
#include <cstdio>
#include "ModemManager.h"
#include "libmm-glib.h"
// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif
namespace {
using namespace waybar::util;
constexpr const char* DEFAULT_FORMAT = "{state}";
} // namespace
waybar::modules::Wwan::Wwan(const std::string& id, const Json::Value& config)
: ALabel(config, "wwan", id, "{}", 5) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
GError* error = nullptr;
connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error);
if (error) {
g_error_free(error);
return;
}
manager = mm_manager_new_sync(connection, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
nullptr, // cancellable
&error);
if (error) {
spdlog::error("Failed to create ModemManager proxy: " + std::string(error->message));
g_error_free(error);
g_object_unref(connection);
return;
}
updateCurrentModem();
if (config_["hide-disconnected"].isBool()) {
hideDisconnected = config_["hide-disconnected"].asBool();
}
}
void waybar::modules::Wwan::updateCurrentModem() {
// get 1st modem (or modem with the specified hardware path)
GList* modems = g_dbus_object_manager_get_objects(G_DBUS_OBJECT_MANAGER(manager));
if (!modems) {
return;
}
for (GList* m = modems; m; m = g_list_next(m)) {
MMObject* modem_obj = MM_OBJECT(m->data);
MMModem* modem = mm_object_get_modem(modem_obj);
if (config_["path"].isString()) {
std::string path = config_["path"].asString();
gchar* physdev = mm_modem_dup_physdev(modem);
std::string other = physdev ? physdev : "";
g_free(physdev);
if (path != other) {
g_object_unref(modem);
modem = nullptr;
continue;
}
}
if (config_["imei"].isString()) {
std::string imei = config_["imei"].asString();
gchar* equipment_id = mm_modem_dup_equipment_identifier(modem);
std::string other = equipment_id ? equipment_id : "";
g_free(equipment_id);
if (imei != other) {
g_object_unref(modem);
modem = nullptr;
continue;
}
}
current_modem = modem;
g_list_free_full(modems, g_object_unref);
return;
}
// no modems found
current_modem = nullptr;
g_list_free_full(modems, g_object_unref);
}
std::string getModemStateString(MMModem* modem) {
switch (mm_modem_get_state(modem)) {
case MMModemState::MM_MODEM_STATE_FAILED:
return "Failed";
case MMModemState::MM_MODEM_STATE_INITIALIZING:
return "Initializing";
case MMModemState::MM_MODEM_STATE_LOCKED:
return "Locked";
case MMModemState::MM_MODEM_STATE_DISABLED:
return "Disabled";
case MMModemState::MM_MODEM_STATE_DISABLING:
return "Disabling";
case MMModemState::MM_MODEM_STATE_ENABLED:
return "Enabled";
case MMModemState::MM_MODEM_STATE_ENABLING:
return "Enabling";
case MMModemState::MM_MODEM_STATE_SEARCHING:
return "Searching";
case MMModemState::MM_MODEM_STATE_REGISTERED:
return "Registered";
case MMModemState::MM_MODEM_STATE_DISCONNECTING:
return "Disconnecting";
case MMModemState::MM_MODEM_STATE_CONNECTING:
return "Connecting";
case MMModemState::MM_MODEM_STATE_CONNECTED:
return "Connected";
default:
return "Unknown";
}
}
std::string getModemStateFormatString(MMModem* modem) {
switch (mm_modem_get_state(modem)) {
case MMModemState::MM_MODEM_STATE_FAILED:
return "failed";
case MMModemState::MM_MODEM_STATE_LOCKED:
return "locked";
case MMModemState::MM_MODEM_STATE_DISABLED:
return "disabled";
case MMModemState::MM_MODEM_STATE_ENABLED:
return "enabled";
case MMModemState::MM_MODEM_STATE_SEARCHING:
return "searching";
case MMModemState::MM_MODEM_STATE_REGISTERED:
return "registered";
case MMModemState::MM_MODEM_STATE_CONNECTED:
return "connected";
default:
return "";
}
}
std::string getPreferredModeString(MMModem* modem) {
MMModemModeCombination combination;
mm_modem_get_current_modes(modem, &combination.allowed, &combination.preferred);
switch (combination.preferred) {
case MMModemMode::MM_MODEM_MODE_CS:
return "CS";
case MMModemMode::MM_MODEM_MODE_2G:
return "2G";
case MMModemMode::MM_MODEM_MODE_3G:
return "3G";
case MMModemMode::MM_MODEM_MODE_4G:
return "4G";
case MMModemMode::MM_MODEM_MODE_5G:
return "5G";
default:
return "No Service";
}
}
std::string getCurrentModesString(MMModem* modem) {
MMModemModeCombination combination;
mm_modem_get_current_modes(modem, &combination.allowed, &combination.preferred);
std::string buffer;
if (combination.allowed & MMModemMode::MM_MODEM_MODE_CS) buffer += "CS | ";
if (combination.allowed & MMModemMode::MM_MODEM_MODE_2G) buffer += "2G | ";
if (combination.allowed & MMModemMode::MM_MODEM_MODE_3G) buffer += "3G | ";
if (combination.allowed & MMModemMode::MM_MODEM_MODE_4G) buffer += "4G | ";
if (combination.allowed & MMModemMode::MM_MODEM_MODE_5G) buffer += "5G | ";
if (buffer.length() > 2) {
buffer.erase(buffer.length() - 2);
}
return buffer;
}
std::string getPowerStateString(MMModem* modem) {
MMModemPowerState state = mm_modem_get_power_state(modem);
switch (state) {
case MM_MODEM_POWER_STATE_ON:
return "On";
case MM_MODEM_POWER_STATE_OFF:
return "Off";
case MM_MODEM_POWER_STATE_LOW:
return "Low";
default:
return "Unknown";
}
}
std::string getOperatorNameString(MMModem* modem) {
MMSim* sim = mm_modem_get_sim_sync(modem, nullptr, nullptr);
if (sim == nullptr) {
return "No SIM";
}
gchar* operator_name = mm_sim_dup_operator_name(sim);
std::string name = operator_name ? operator_name : "";
g_free(operator_name);
g_object_unref(sim);
return name;
}
auto waybar::modules::Wwan::update() -> void {
if (current_modem == nullptr) {
event_box_.set_visible(false);
updateCurrentModem();
return;
}
if (hideDisconnected && mm_modem_get_state(current_modem) != MM_MODEM_STATE_CONNECTED) {
event_box_.set_visible(false);
return;
}
// Show the module
if (!event_box_.get_visible()) event_box_.set_visible(true);
std::string tooltip_format;
if (!alt_) {
std::string state = getModemStateFormatString(current_modem);
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
label_.get_style_context()->remove_class(state_);
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
} else if (config_["format"].isString()) {
default_format_ = config_["format"].asString();
} else {
default_format_ = DEFAULT_FORMAT;
}
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
}
auto format = format_;
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("state", getModemStateString(current_modem)));
store.push_back(fmt::arg("current_modes", getCurrentModesString(current_modem)));
store.push_back(fmt::arg("preferred_mode", getPreferredModeString(current_modem)));
int percentage = mm_modem_get_signal_quality(current_modem, nullptr);
store.push_back(fmt::arg("signal_quality", percentage));
store.push_back(fmt::arg("icon", getIcon(percentage)));
store.push_back(fmt::arg("power_state", getPowerStateString(current_modem)));
gchar* imei_raw = mm_modem_dup_equipment_identifier(current_modem);
std::string imei = imei_raw ? imei_raw : "";
g_free(imei_raw);
store.push_back(fmt::arg("imei", imei));
store.push_back(fmt::arg("operator_name", getOperatorNameString(current_modem)));
auto text = fmt::vformat(format, store);
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
auto tooltip_text = fmt::vformat(tooltip_format, store);
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_markup(text);
}
}
label_.set_markup(text);
// Call parent update
ALabel::update();
}
waybar::modules::Wwan::~Wwan() {
g_object_unref(current_modem);
g_object_unref(manager);
g_object_unref(connection);
}
View File