Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GPS module #3628

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions include/modules/gps.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include <fmt/format.h>
#include <sys/statvfs.h>

#include <fstream>
#include <gps.h>

#include "ALabel.hpp"
#include "util/format.hpp"
#include "util/sleeper_thread.hpp"

namespace waybar::modules {

class Gps : public ALabel {
public:
Gps(const std::string&, const Json::Value&);
virtual ~Gps();
auto update() -> void override;

private:
const std::string getFixModeName() const;
const std::string getFixModeString() const;

const std::string getFixStatusString() const;

util::SleeperThread thread_;
gps_data_t gps_data_;
std::string state_;

bool hideDisconnected = true;
bool hideNoFix = false;
};

} // namespace waybar::modules
108 changes: 108 additions & 0 deletions man/waybar-gps.5.scd
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
waybar-gps(5) "waybar-gps" "User Manual"

# NAME

waybar - gps module

# DESCRIPTION

*gps* module for gpsd.


# FILES

$XDG_CONFIG_HOME/waybar/config ++
Per user configuration file

# ADDITIONAL FILES

libgps lives in:

. /usr/lib/libgps.so or /usr/lib64/libgps.so
. /usr/lib/pkgconfig/libgps.pc or /usr/lib64/pkgconfig/libgps.pc
. /usr/include/gps

# CONFIGURATION

*format*: ++
typeof: string ++
default: {glyph} ++
The text format.

*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.

*tooltip-format*: ++
typeof: string ++
default: Games running: {glyph} ++
The text format of the tooltip.

*interval*: ++
typeof: integer ++
default: 5 ++
The interval in which the GPS information gets polled (e.g. fix status).

*hide-disconnected*: ++
typeof: bool ++
default: true ++
Defines if the module should be hidden if there is no GPS receiver.

*hide-no-fix*: ++
typeof: bool ++
default: false ++
Defines if the module should be hidden if there is no GPS fix.

# FORMAT REPLACEMENTS

*{mode}*: Fix mode

*{status}*: Technology used for GPS fix. Not all GPS receivers report this.

*{latitude}*: Latitude, decimal degrees. Can be NaN.

*{latitude_error}*: Latitude uncertainty, meters. Can be NaN.

*{longitude}*: Longitude, decimal degrees. Can be NaN.

*{longitude_error}*: Longitude uncertainty, meters. Can be NaN.

*{altitude_hae}*: Altitude, height above ellipsoid, meters. Can be NaN.

*{altitude_msl}*: Longitude, MSL, meters. Can be NaN.

*{altitude_error}*: Altitude uncertainty, meters. Can be NaN.

*{speed}*: Speed over ground, meters/sec. Can be NaN.

*{speed_error}*: Speed uncertainty, meters/sec. Can be NaN.

*{climb}*: Vertical speed, meters/sec. Can be NaN.

*{climb_error}*: Vertical speed uncertainty, meters/sec. Can be NaN.

*{satellites_visible}*: Number of satellites visible from the GPS receiver.

*{satellites_used}*: Number of satellites used for the GPS fix.

# EXAMPLES

```
"gps": {
"format": "{mode}",
"format-no-fix": "No fix",
"format-fix-3d": "{status}",
"tooltip-format": "{mode}",
"tooltip-format-no-fix": "{satellites_visible} satellites visible",
"tooltip-format-fix-2d": "{satellites_used}/{satellites_visible} satellites used",
"tooltip-format-fix-3d": "Altitude: {altitude_hae}m",
"hide-disconnected": false
}
```
# STYLE

- *#gps*
- *#gps.fix-none* Applied when GPS is present, but there is no fix.
- *#gps.fix-2d* Applied when there is a 2D fix.
- *#gps.fix-3d* Applied when there is a 3D fix.
10 changes: 9 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
xkbregistry = dependency('xkbregistry')
libjack = dependency('jack', required: get_option('jack'))
libwireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber'))
libgps = dependency('libgps', required: get_option('gps'))

libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found()
Expand Down Expand Up @@ -493,6 +494,12 @@ if cava.found()
man_files += files('man/waybar-cava.5.scd')
endif

if libgps.found()
add_project_arguments('-DHAVE_LIBGPS', language: 'cpp')
src_files += files('src/modules/gps.cpp')
man_files += files('man/waybar-gps.5.scd')
endif

subdir('protocol')

app_resources = []
Expand Down Expand Up @@ -531,7 +538,8 @@ executable(
libsndio,
tz_dep,
xkbregistry,
cava
cava,
libgps
],
include_directories: inc_dirs,
install: true,
Expand Down
1 change: 1 addition & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
option('niri', type: 'boolean', description: 'Enable support for niri')
option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps')
8 changes: 8 additions & 0 deletions src/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@
#ifdef HAVE_SYSTEMD_MONITOR
#include "modules/systemd_failed_units.hpp"
#endif
#ifdef HAVE_LIBGPS
#include "modules/gps.hpp"
#endif
#include "modules/cffi.hpp"
#include "modules/custom.hpp"
#include "modules/image.hpp"
Expand Down Expand Up @@ -331,6 +334,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
if (ref == "systemd-failed-units") {
return new waybar::modules::SystemdFailedUnits(id, config_[name]);
}
#endif
#ifdef HAVE_LIBCAVA
if (ref == "gps") {
return new waybar::modules::Gps(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
Expand Down
183 changes: 183 additions & 0 deletions src/modules/gps.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#include "modules/gps.hpp"
#include <spdlog/spdlog.h>

#include <cmath>
#include <cstdio>

// 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 = "{mode}";
} // namespace


waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config)
: ALabel(config, "gps", id, "{}", 5) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};

if (0 != gps_open("localhost", "2947", &gps_data_)) {
throw std::runtime_error("Can't open gpsd socket");
}

if (config_["hide-disconnected"].isBool()) {
hideDisconnected = config_["hide-disconnected"].asBool();
}

if (config_["hide-no-fix"].isBool()) {
hideNoFix = config_["hide-no-fix"].asBool();
}

gps_stream(&gps_data_, WATCH_ENABLE, NULL);
}

const std::string waybar::modules::Gps::getFixModeName() const {
switch (gps_data_.fix.mode) {
case MODE_NO_FIX:
return "fix-none";
case MODE_2D:
return "fix-2d";
case MODE_3D:
return "fix-3d";
default:
return "disconnected";
}
}

const std::string waybar::modules::Gps::getFixModeString() const {
switch (gps_data_.fix.mode) {
case MODE_NO_FIX:
return "No fix";
case MODE_2D:
return "2D Fix";
case MODE_3D:
return "3D Fix";
default:
return "Disconnected";
}
}

const std::string waybar::modules::Gps::getFixStatusString() const {
switch (gps_data_.fix.status) {
case STATUS_UNK:
return "Unknown";
case STATUS_GPS:
return "GPS";
case STATUS_DGPS:
return "DGPS";
case STATUS_RTK_FIX:
return "RTK Fixed";
case STATUS_RTK_FLT:
return "RTK Float";
case STATUS_DR:
return "Dead Reckoning";
case STATUS_GNSSDR:
return "GNSS + Dead Reckoning";
case STATUS_TIME:
return "Time Only";
case STATUS_PPS_FIX:
return "PPS Fix";
default:
return "Unknown";
}
}

auto waybar::modules::Gps::update() -> void {
if (gps_read(&gps_data_, NULL, 0) == -1) {
throw std::runtime_error("Can't read data from gpsd.");
}

if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) || (gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) {
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_) {
auto state = getFixModeName();
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("mode", getFixModeString()));
store.push_back(fmt::arg("status", getFixStatusString()));

store.push_back(fmt::arg("latitude", gps_data_.fix.latitude));
store.push_back(fmt::arg("latitude_error", gps_data_.fix.epy));

store.push_back(fmt::arg("longitude", gps_data_.fix.longitude));
store.push_back(fmt::arg("longitude_error", gps_data_.fix.epx));

store.push_back(fmt::arg("altitude_hae", gps_data_.fix.altHAE));
store.push_back(fmt::arg("altitude_msl", gps_data_.fix.altMSL));
store.push_back(fmt::arg("altitude_error", gps_data_.fix.epv));

store.push_back(fmt::arg("speed", gps_data_.fix.speed));
store.push_back(fmt::arg("speed_error", gps_data_.fix.eps));

store.push_back(fmt::arg("climb", gps_data_.fix.climb));
store.push_back(fmt::arg("climb_error", gps_data_.fix.epc));

store.push_back(fmt::arg("satellites_used", gps_data_.satellites_used));
store.push_back(fmt::arg("satellites_visible", gps_data_.satellites_visible));

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::Gps::~Gps() {
gps_stream(&gps_data_, WATCH_DISABLE, NULL);
gps_close(&gps_data_);
}