merge
This commit is contained in:
parent
9fa7bfc0cb
commit
1e560cf0c9
|
@ -13,6 +13,7 @@ jobs:
|
||||||
- name: Test in FreeBSD VM
|
- name: Test in FreeBSD VM
|
||||||
uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0
|
uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0
|
||||||
with:
|
with:
|
||||||
|
mem: 2048
|
||||||
usesh: true
|
usesh: true
|
||||||
prepare: |
|
prepare: |
|
||||||
export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio
|
export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
|
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
|
||||||
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
|
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
|
||||||
[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
|
[AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
|
||||||
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
|
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
|
||||||
|
|
||||||
#### Current features
|
#### Current features
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
#include <gtkmm/window.h>
|
#include <gtkmm/window.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "AModule.hpp"
|
#include "AModule.hpp"
|
||||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
@ -36,6 +39,19 @@ struct bar_margins {
|
||||||
int left = 0;
|
int left = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct bar_mode {
|
||||||
|
bar_layer layer;
|
||||||
|
bool exclusive;
|
||||||
|
bool passthrough;
|
||||||
|
bool visible;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef HAVE_SWAY
|
||||||
|
namespace modules::sway {
|
||||||
|
class BarIpcClient;
|
||||||
|
}
|
||||||
|
#endif // HAVE_SWAY
|
||||||
|
|
||||||
class BarSurface {
|
class BarSurface {
|
||||||
protected:
|
protected:
|
||||||
BarSurface() = default;
|
BarSurface() = default;
|
||||||
|
@ -54,38 +70,56 @@ class BarSurface {
|
||||||
|
|
||||||
class Bar {
|
class Bar {
|
||||||
public:
|
public:
|
||||||
|
using bar_mode_map = std::map<std::string_view, struct bar_mode>;
|
||||||
|
static const bar_mode_map PRESET_MODES;
|
||||||
|
static const std::string_view MODE_DEFAULT;
|
||||||
|
static const std::string_view MODE_INVISIBLE;
|
||||||
|
|
||||||
Bar(struct waybar_output *w_output, const Json::Value &);
|
Bar(struct waybar_output *w_output, const Json::Value &);
|
||||||
Bar(const Bar &) = delete;
|
Bar(const Bar &) = delete;
|
||||||
~Bar() = default;
|
~Bar();
|
||||||
|
|
||||||
|
void setMode(const std::string_view &);
|
||||||
void setVisible(bool visible);
|
void setVisible(bool visible);
|
||||||
void toggle();
|
void toggle();
|
||||||
void handleSignal(int);
|
void handleSignal(int);
|
||||||
|
|
||||||
struct waybar_output *output;
|
struct waybar_output *output;
|
||||||
Json::Value config;
|
Json::Value config;
|
||||||
struct wl_surface * surface;
|
struct wl_surface *surface;
|
||||||
bool exclusive = true;
|
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
bool vertical = false;
|
bool vertical = false;
|
||||||
Gtk::Window window;
|
Gtk::Window window;
|
||||||
|
|
||||||
|
#ifdef HAVE_SWAY
|
||||||
|
std::string bar_id;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onMap(GdkEventAny *);
|
void onMap(GdkEventAny *);
|
||||||
auto setupWidgets() -> void;
|
auto setupWidgets() -> void;
|
||||||
void getModules(const Factory &, const std::string &);
|
void getModules(const Factory &, const std::string &, Gtk::Box*);
|
||||||
void setupAltFormatKeyForModule(const std::string &module_name);
|
void setupAltFormatKeyForModule(const std::string &module_name);
|
||||||
void setupAltFormatKeyForModuleList(const char *module_list_name);
|
void setupAltFormatKeyForModuleList(const char *module_list_name);
|
||||||
|
void setMode(const bar_mode &);
|
||||||
|
|
||||||
|
/* Copy initial set of modes to allow customization */
|
||||||
|
bar_mode_map configured_modes = PRESET_MODES;
|
||||||
|
std::string last_mode_{MODE_DEFAULT};
|
||||||
|
|
||||||
std::unique_ptr<BarSurface> surface_impl_;
|
std::unique_ptr<BarSurface> surface_impl_;
|
||||||
bar_layer layer_;
|
|
||||||
Gtk::Box left_;
|
Gtk::Box left_;
|
||||||
Gtk::Box center_;
|
Gtk::Box center_;
|
||||||
Gtk::Box right_;
|
Gtk::Box right_;
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
std::vector<std::unique_ptr<waybar::AModule>> modules_left_;
|
std::vector<std::shared_ptr<waybar::AModule>> modules_left_;
|
||||||
std::vector<std::unique_ptr<waybar::AModule>> modules_center_;
|
std::vector<std::shared_ptr<waybar::AModule>> modules_center_;
|
||||||
std::vector<std::unique_ptr<waybar::AModule>> modules_right_;
|
std::vector<std::shared_ptr<waybar::AModule>> modules_right_;
|
||||||
|
#ifdef HAVE_SWAY
|
||||||
|
using BarIpcClient = modules::sway::BarIpcClient;
|
||||||
|
std::unique_ptr<BarIpcClient> _ipc_client;
|
||||||
|
#endif
|
||||||
|
std::vector<std::shared_ptr<waybar::AModule>> modules_all_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar
|
} // namespace waybar
|
||||||
|
|
|
@ -29,6 +29,7 @@ class Client {
|
||||||
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
|
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
|
||||||
std::vector<std::unique_ptr<Bar>> bars;
|
std::vector<std::unique_ptr<Bar>> bars;
|
||||||
Config config;
|
Config config;
|
||||||
|
std::string bar_id;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Client() = default;
|
Client() = default;
|
||||||
|
|
|
@ -51,6 +51,9 @@
|
||||||
#ifdef HAVE_LIBSNDIO
|
#ifdef HAVE_LIBSNDIO
|
||||||
#include "modules/sndio.hpp"
|
#include "modules/sndio.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_GIO_UNIX
|
||||||
|
#include "modules/inhibitor.hpp"
|
||||||
|
#endif
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "modules/custom.hpp"
|
#include "modules/custom.hpp"
|
||||||
#include "modules/temperature.hpp"
|
#include "modules/temperature.hpp"
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtkmm/widget.h>
|
||||||
|
#include <gtkmm/box.h>
|
||||||
|
#include <json/json.h>
|
||||||
|
#include "AModule.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "factory.hpp"
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
class Group : public AModule {
|
||||||
|
public:
|
||||||
|
Group(const std::string&, const Bar&, const Json::Value&);
|
||||||
|
~Group() = default;
|
||||||
|
auto update() -> void;
|
||||||
|
operator Gtk::Widget &();
|
||||||
|
Gtk::Box box;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar
|
|
@ -17,6 +17,8 @@ struct waybar_time {
|
||||||
date::zoned_seconds ztime;
|
date::zoned_seconds ztime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const std::string kCalendarPlaceholder = "calendar";
|
||||||
|
|
||||||
class Clock : public ALabel {
|
class Clock : public ALabel {
|
||||||
public:
|
public:
|
||||||
Clock(const std::string&, const Json::Value&);
|
Clock(const std::string&, const Json::Value&);
|
||||||
|
@ -26,18 +28,19 @@ class Clock : public ALabel {
|
||||||
private:
|
private:
|
||||||
util::SleeperThread thread_;
|
util::SleeperThread thread_;
|
||||||
std::locale locale_;
|
std::locale locale_;
|
||||||
const date::time_zone* time_zone_;
|
std::vector<const date::time_zone*> time_zones_;
|
||||||
bool fixed_time_zone_;
|
int current_time_zone_idx_;
|
||||||
int time_zone_idx_;
|
|
||||||
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
|
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
|
||||||
std::string cached_calendar_text_;
|
std::string cached_calendar_text_;
|
||||||
|
bool is_calendar_in_tooltip_;
|
||||||
|
|
||||||
bool handleScroll(GdkEventScroll* e);
|
bool handleScroll(GdkEventScroll* e);
|
||||||
|
|
||||||
auto calendar_text(const waybar_time& wtime) -> std::string;
|
auto calendar_text(const waybar_time& wtime) -> std::string;
|
||||||
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
|
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
|
||||||
auto first_day_of_week() -> date::weekday;
|
auto first_day_of_week() -> date::weekday;
|
||||||
bool setTimeZone(Json::Value zone_name);
|
const date::time_zone* current_timezone();
|
||||||
|
bool is_timezone_fixed();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "ALabel.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
class Inhibitor : public ALabel {
|
||||||
|
public:
|
||||||
|
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||||
|
~Inhibitor() override;
|
||||||
|
auto update() -> void;
|
||||||
|
auto activated() -> bool;
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto handleToggle(::GdkEventButton* const& e) -> bool;
|
||||||
|
|
||||||
|
const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_;
|
||||||
|
const std::string inhibitors_;
|
||||||
|
int handle_ = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules
|
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "modules/sway/ipc/client.hpp"
|
||||||
|
#include "util/SafeSignal.hpp"
|
||||||
|
#include "util/json.hpp"
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
class Bar;
|
||||||
|
|
||||||
|
namespace modules::sway {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Supported subset of i3/sway IPC barconfig object
|
||||||
|
*/
|
||||||
|
struct swaybar_config {
|
||||||
|
std::string id;
|
||||||
|
std::string mode;
|
||||||
|
std::string hidden_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* swaybar IPC client
|
||||||
|
*/
|
||||||
|
class BarIpcClient {
|
||||||
|
public:
|
||||||
|
BarIpcClient(waybar::Bar& bar);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onInitialConfig(const struct Ipc::ipc_response& res);
|
||||||
|
void onIpcEvent(const struct Ipc::ipc_response&);
|
||||||
|
void onConfigUpdate(const swaybar_config& config);
|
||||||
|
void onVisibilityUpdate(bool visible_by_modifier);
|
||||||
|
void update();
|
||||||
|
|
||||||
|
Bar& bar_;
|
||||||
|
util::JsonParser parser_;
|
||||||
|
Ipc ipc_;
|
||||||
|
|
||||||
|
swaybar_config bar_config_;
|
||||||
|
bool visible_by_modifier_ = false;
|
||||||
|
|
||||||
|
SafeSignal<bool> signal_visible_;
|
||||||
|
SafeSignal<swaybar_config> signal_config_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace modules::sway
|
||||||
|
} // namespace waybar
|
|
@ -32,6 +32,7 @@ class Language : public ALabel, public sigc::trackable {
|
||||||
std::string short_name;
|
std::string short_name;
|
||||||
std::string variant;
|
std::string variant;
|
||||||
std::string short_description;
|
std::string short_description;
|
||||||
|
std::string country_flag() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class XKBContext {
|
class XKBContext {
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glibmm/dispatcher.h>
|
||||||
|
#include <sigc++/signal.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe signal wrapper.
|
||||||
|
* Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
|
||||||
|
public:
|
||||||
|
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
|
||||||
|
|
||||||
|
template <typename... EmitArgs>
|
||||||
|
void emit(EmitArgs&&... args) {
|
||||||
|
if (main_tid_ == std::this_thread::get_id()) {
|
||||||
|
/*
|
||||||
|
* Bypass the queue if the method is called the main thread.
|
||||||
|
* Ensures that events emitted from the main thread are processed synchronously and saves a
|
||||||
|
* few CPU cycles on locking/queuing.
|
||||||
|
* As a downside, this makes main thread events prioritized over the other threads and
|
||||||
|
* disrupts chronological order.
|
||||||
|
*/
|
||||||
|
signal_t::emit(std::forward<EmitArgs>(args)...);
|
||||||
|
} else {
|
||||||
|
{
|
||||||
|
std::unique_lock lock(mutex_);
|
||||||
|
queue_.emplace(std::forward<EmitArgs>(args)...);
|
||||||
|
}
|
||||||
|
dp_.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... EmitArgs>
|
||||||
|
inline void operator()(EmitArgs&&... args) {
|
||||||
|
emit(std::forward<EmitArgs>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using signal_t = sigc::signal<void(std::decay_t<Args>...)>;
|
||||||
|
using slot_t = decltype(std::declval<signal_t>().make_slot());
|
||||||
|
using arg_tuple_t = std::tuple<std::decay_t<Args>...>;
|
||||||
|
// ensure that unwrapped methods are not accessible
|
||||||
|
using signal_t::emit_reverse;
|
||||||
|
using signal_t::make_slot;
|
||||||
|
|
||||||
|
void handle_event() {
|
||||||
|
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
|
||||||
|
auto args = queue_.front();
|
||||||
|
queue_.pop();
|
||||||
|
lock.unlock();
|
||||||
|
std::apply(cached_fn_, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::Dispatcher dp_;
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::queue<arg_tuple_t> queue_;
|
||||||
|
const std::thread::id main_tid_ = std::this_thread::get_id();
|
||||||
|
// cache functor for signal emission to avoid recreating it on each event
|
||||||
|
const slot_t cached_fn_ = make_slot();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar
|
|
@ -0,0 +1,92 @@
|
||||||
|
waybar-inhibitor(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - inhibitor module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *inhibitor* module allows to take an inhibitor lock that logind provides.
|
||||||
|
See *systemd-inhibit*(1) for more information.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
*what*: ++
|
||||||
|
typeof: string or array ++
|
||||||
|
The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*.
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The format, how the state should be displayed.
|
||||||
|
|
||||||
|
*format-icons*: ++
|
||||||
|
typeof: array ++
|
||||||
|
Based on the current state, the corresponding icon gets selected.
|
||||||
|
|
||||||
|
*rotate*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
Positive value to rotate the text label.
|
||||||
|
|
||||||
|
*max-length*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
The maximum length in character the module should display.
|
||||||
|
|
||||||
|
*min-length*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
The minimum length in characters the module should take up.
|
||||||
|
|
||||||
|
*align*: ++
|
||||||
|
typeof: float ++
|
||||||
|
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
|
||||||
|
|
||||||
|
*on-click*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when clicked on the module. A click also toggles the state
|
||||||
|
|
||||||
|
*on-click-middle*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when middle-clicked on the module using mousewheel.
|
||||||
|
|
||||||
|
*on-click-right*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when you right clicked on the module.
|
||||||
|
|
||||||
|
*on-update*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when the module is updated.
|
||||||
|
|
||||||
|
*on-scroll-up*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling up on the module.
|
||||||
|
|
||||||
|
*on-scroll-down*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling down on the module.
|
||||||
|
|
||||||
|
*smooth-scrolling-threshold*: ++
|
||||||
|
typeof: double ++
|
||||||
|
Threshold to be used when scrolling.
|
||||||
|
|
||||||
|
*tooltip*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: true ++
|
||||||
|
Option to disable tooltip on hover.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{status}*: status (*activated* or *deactivated*)
|
||||||
|
|
||||||
|
*{icon}*: Icon, as defined in *format-icons*
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"inhibitor": {
|
||||||
|
"what": "handle-lid-switch",
|
||||||
|
"format": "{icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"activated": "",
|
||||||
|
"deactivated": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -37,6 +37,8 @@ Addressed by *sway/language*
|
||||||
|
|
||||||
*{variant}*: Variant of layout (e.g. "dvorak").
|
*{variant}*: Variant of layout (e.g. "dvorak").
|
||||||
|
|
||||||
|
*{flag}*: Country flag of layout.
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -72,14 +72,19 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
Optional name added as a CSS class, for styling multiple waybars.
|
Optional name added as a CSS class, for styling multiple waybars.
|
||||||
|
|
||||||
|
*mode* ++
|
||||||
|
typeof: string ++
|
||||||
|
Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++
|
||||||
|
Note: *hide* and *invisible* modes may be not as useful without Sway IPC.
|
||||||
|
|
||||||
*exclusive* ++
|
*exclusive* ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: *true* unless the layer is set to *overlay* ++
|
default: *true* ++
|
||||||
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
|
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
|
||||||
|
|
||||||
*passthrough* ++
|
*passthrough* ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: *false* unless the layer is set to *overlay* ++
|
default: *false* ++
|
||||||
Option to pass any pointer events to the window under the bar.
|
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.
|
Intended to be used with either *top* or *overlay* layers and without exclusive zone.
|
||||||
|
|
||||||
|
@ -89,6 +94,16 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||||
Option to disable the use of gtk-layer-shell for popups.
|
Option to disable the use of gtk-layer-shell for popups.
|
||||||
Only functional if compiled with gtk-layer-shell support.
|
Only functional if compiled with gtk-layer-shell support.
|
||||||
|
|
||||||
|
*ipc* ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++
|
||||||
|
Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option.
|
||||||
|
|
||||||
|
*id* ++
|
||||||
|
typeof: string ++
|
||||||
|
*bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance.
|
||||||
|
|
||||||
*include* ++
|
*include* ++
|
||||||
typeof: string|array ++
|
typeof: string|array ++
|
||||||
Paths to additional configuration files.
|
Paths to additional configuration files.
|
||||||
|
@ -203,6 +218,28 @@ When positioning Waybar on the left or right side of the screen, sometimes it's
|
||||||
|
|
||||||
Valid options for the "rotate" property are: 0, 90, 180 and 270.
|
Valid options for the "rotate" property are: 0, 90, 180 and 270.
|
||||||
|
|
||||||
|
## Grouping modules
|
||||||
|
|
||||||
|
Module groups allow stacking modules in the direction orthogonal to the bar direction. When the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally.
|
||||||
|
|
||||||
|
A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"modules-right": ["group/hardware", "clock"],
|
||||||
|
|
||||||
|
"group/hardware": {
|
||||||
|
"modules": [
|
||||||
|
"cpu",
|
||||||
|
"memory",
|
||||||
|
"battery"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# SUPPORTED MODULES
|
# SUPPORTED MODULES
|
||||||
|
|
||||||
- *waybar-backlight(5)*
|
- *waybar-backlight(5)*
|
||||||
|
|
18
meson.build
18
meson.build
|
@ -86,7 +86,7 @@ wayland_cursor = dependency('wayland-cursor')
|
||||||
wayland_protos = dependency('wayland-protocols')
|
wayland_protos = dependency('wayland-protocols')
|
||||||
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
|
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
|
||||||
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
|
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
|
||||||
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk'))
|
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled()))
|
||||||
jsoncpp = dependency('jsoncpp')
|
jsoncpp = dependency('jsoncpp')
|
||||||
sigcpp = dependency('sigc++-2.0')
|
sigcpp = dependency('sigc++-2.0')
|
||||||
libepoll = dependency('epoll-shim', required: false)
|
libepoll = dependency('epoll-shim', required: false)
|
||||||
|
@ -150,6 +150,7 @@ src_files = files(
|
||||||
'src/bar.cpp',
|
'src/bar.cpp',
|
||||||
'src/client.cpp',
|
'src/client.cpp',
|
||||||
'src/config.cpp',
|
'src/config.cpp',
|
||||||
|
'src/group.cpp',
|
||||||
'src/util/ustring_clen.cpp'
|
'src/util/ustring_clen.cpp'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -177,6 +178,7 @@ endif
|
||||||
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
|
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
|
||||||
src_files += [
|
src_files += [
|
||||||
'src/modules/sway/ipc/client.cpp',
|
'src/modules/sway/ipc/client.cpp',
|
||||||
|
'src/modules/sway/bar.cpp',
|
||||||
'src/modules/sway/mode.cpp',
|
'src/modules/sway/mode.cpp',
|
||||||
'src/modules/sway/language.cpp',
|
'src/modules/sway/language.cpp',
|
||||||
'src/modules/sway/window.cpp',
|
'src/modules/sway/window.cpp',
|
||||||
|
@ -240,6 +242,11 @@ if libsndio.found()
|
||||||
src_files += 'src/modules/sndio.cpp'
|
src_files += 'src/modules/sndio.cpp'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if (giounix.found() and not get_option('logind').disabled())
|
||||||
|
add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp')
|
||||||
|
src_files += 'src/modules/inhibitor.cpp'
|
||||||
|
endif
|
||||||
|
|
||||||
if get_option('rfkill').enabled()
|
if get_option('rfkill').enabled()
|
||||||
if is_linux
|
if is_linux
|
||||||
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
|
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
|
||||||
|
@ -257,6 +264,10 @@ else
|
||||||
src_files += 'src/modules/simpleclock.cpp'
|
src_files += 'src/modules/simpleclock.cpp'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if get_option('experimental')
|
||||||
|
add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp')
|
||||||
|
endif
|
||||||
|
|
||||||
subdir('protocol')
|
subdir('protocol')
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
|
@ -341,6 +352,10 @@ if scdoc.found()
|
||||||
'waybar-sndio.5.scd',
|
'waybar-sndio.5.scd',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (giounix.found() and not get_option('logind').disabled())
|
||||||
|
man_files += 'waybar-inhibitor.5.scd'
|
||||||
|
endif
|
||||||
|
|
||||||
foreach file : man_files
|
foreach file : man_files
|
||||||
path = '@0@'.format(file)
|
path = '@0@'.format(file)
|
||||||
basename = path.split('/')[-1]
|
basename = path.split('/')[-1]
|
||||||
|
@ -383,3 +398,4 @@ if clangtidy.found()
|
||||||
'-p', meson.build_root()
|
'-p', meson.build_root()
|
||||||
] + src_files)
|
] + src_files)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
@ -10,4 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t
|
||||||
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
|
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
|
||||||
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
||||||
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
|
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
|
||||||
|
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
|
||||||
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
||||||
|
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
||||||
|
|
|
@ -110,6 +110,7 @@ def main():
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||||
|
|
||||||
for player in manager.props.player_names:
|
for player in manager.props.player_names:
|
||||||
if arguments.player is not None and arguments.player != player.name:
|
if arguments.player is not None and arguments.player != player.name:
|
||||||
|
|
242
src/bar.cpp
242
src/bar.cpp
|
@ -9,8 +9,13 @@
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
#include "factory.hpp"
|
#include "factory.hpp"
|
||||||
|
#include "group.hpp"
|
||||||
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SWAY
|
||||||
|
#include "modules/sway/bar.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace waybar {
|
namespace waybar {
|
||||||
static constexpr const char* MIN_HEIGHT_MSG =
|
static constexpr const char* MIN_HEIGHT_MSG =
|
||||||
"Requested height: {} is less than the minimum height: {} required by the modules";
|
"Requested height: {} is less than the minimum height: {} required by the modules";
|
||||||
|
@ -23,6 +28,84 @@ static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height:
|
||||||
static constexpr const char* SIZE_DEFINED =
|
static constexpr const char* SIZE_DEFINED =
|
||||||
"{} size is defined in the config file so it will stay like that";
|
"{} size is defined in the config file so it will stay like that";
|
||||||
|
|
||||||
|
const Bar::bar_mode_map Bar::PRESET_MODES = { //
|
||||||
|
{"default",
|
||||||
|
{// Special mode to hold the global bar configuration
|
||||||
|
.layer = bar_layer::BOTTOM,
|
||||||
|
.exclusive = true,
|
||||||
|
.passthrough = false,
|
||||||
|
.visible = true}},
|
||||||
|
{"dock",
|
||||||
|
{// Modes supported by the sway config; see man sway-bar(5)
|
||||||
|
.layer = bar_layer::BOTTOM,
|
||||||
|
.exclusive = true,
|
||||||
|
.passthrough = false,
|
||||||
|
.visible = true}},
|
||||||
|
{"hide",
|
||||||
|
{//
|
||||||
|
.layer = bar_layer::TOP,
|
||||||
|
.exclusive = false,
|
||||||
|
.passthrough = false,
|
||||||
|
.visible = true}},
|
||||||
|
{"invisible",
|
||||||
|
{//
|
||||||
|
.layer = bar_layer::BOTTOM,
|
||||||
|
.exclusive = false,
|
||||||
|
.passthrough = true,
|
||||||
|
.visible = false}},
|
||||||
|
{"overlay",
|
||||||
|
{//
|
||||||
|
.layer = bar_layer::TOP,
|
||||||
|
.exclusive = false,
|
||||||
|
.passthrough = true,
|
||||||
|
.visible = true}}};
|
||||||
|
|
||||||
|
const std::string_view Bar::MODE_DEFAULT = "default";
|
||||||
|
const std::string_view Bar::MODE_INVISIBLE = "invisible";
|
||||||
|
const std::string_view DEFAULT_BAR_ID = "bar-0";
|
||||||
|
|
||||||
|
/* Deserializer for enum bar_layer */
|
||||||
|
void from_json(const Json::Value& j, bar_layer& l) {
|
||||||
|
if (j == "bottom") {
|
||||||
|
l = bar_layer::BOTTOM;
|
||||||
|
} else if (j == "top") {
|
||||||
|
l = bar_layer::TOP;
|
||||||
|
} else if (j == "overlay") {
|
||||||
|
l = bar_layer::OVERLAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deserializer for struct bar_mode */
|
||||||
|
void from_json(const Json::Value& j, bar_mode& m) {
|
||||||
|
if (j.isObject()) {
|
||||||
|
if (auto v = j["layer"]; v.isString()) {
|
||||||
|
from_json(v, m.layer);
|
||||||
|
}
|
||||||
|
if (auto v = j["exclusive"]; v.isBool()) {
|
||||||
|
m.exclusive = v.asBool();
|
||||||
|
}
|
||||||
|
if (auto v = j["passthrough"]; v.isBool()) {
|
||||||
|
m.passthrough = v.asBool();
|
||||||
|
}
|
||||||
|
if (auto v = j["visible"]; v.isBool()) {
|
||||||
|
m.visible = v.asBool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deserializer for JSON Object -> map<string compatible type, Value>
|
||||||
|
* Assumes that all the values in the object are deserializable to the same type.
|
||||||
|
*/
|
||||||
|
template <typename Key, typename Value,
|
||||||
|
typename = std::enable_if_t<std::is_convertible<std::string_view, Key>::value>>
|
||||||
|
void from_json(const Json::Value& j, std::map<Key, Value>& m) {
|
||||||
|
if (j.isObject()) {
|
||||||
|
for (auto it = j.begin(); it != j.end(); ++it) {
|
||||||
|
from_json(*it, m[it.key().asString()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_GTK_LAYER_SHELL
|
#ifdef HAVE_GTK_LAYER_SHELL
|
||||||
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||||
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
|
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
|
||||||
|
@ -391,7 +474,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||||
: output(w_output),
|
: output(w_output),
|
||||||
config(w_config),
|
config(w_config),
|
||||||
window{Gtk::WindowType::WINDOW_TOPLEVEL},
|
window{Gtk::WindowType::WINDOW_TOPLEVEL},
|
||||||
layer_{bar_layer::BOTTOM},
|
|
||||||
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||||
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||||
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||||
|
@ -403,27 +485,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||||
window.get_style_context()->add_class(config["name"].asString());
|
window.get_style_context()->add_class(config["name"].asString());
|
||||||
window.get_style_context()->add_class(config["position"].asString());
|
window.get_style_context()->add_class(config["position"].asString());
|
||||||
|
|
||||||
if (config["layer"] == "top") {
|
|
||||||
layer_ = bar_layer::TOP;
|
|
||||||
} else if (config["layer"] == "overlay") {
|
|
||||||
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();
|
auto position = config["position"].asString();
|
||||||
|
|
||||||
if (position == "right" || position == "left") {
|
if (position == "right" || position == "left") {
|
||||||
|
@ -505,15 +566,43 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||||
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
|
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
|
||||||
}
|
}
|
||||||
|
|
||||||
surface_impl_->setLayer(layer_);
|
|
||||||
surface_impl_->setExclusiveZone(exclusive);
|
|
||||||
surface_impl_->setMargins(margins_);
|
surface_impl_->setMargins(margins_);
|
||||||
surface_impl_->setPassThrough(passthrough);
|
|
||||||
surface_impl_->setPosition(position);
|
surface_impl_->setPosition(position);
|
||||||
surface_impl_->setSize(width, height);
|
surface_impl_->setSize(width, height);
|
||||||
|
|
||||||
|
/* Read custom modes if available */
|
||||||
|
if (auto modes = config.get("modes", {}); modes.isObject()) {
|
||||||
|
from_json(modes, configured_modes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update "default" mode with the global bar options */
|
||||||
|
from_json(config, configured_modes[MODE_DEFAULT]);
|
||||||
|
|
||||||
|
if (auto mode = config.get("mode", {}); mode.isString()) {
|
||||||
|
setMode(config["mode"].asString());
|
||||||
|
} else {
|
||||||
|
setMode(MODE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
||||||
|
|
||||||
|
#if HAVE_SWAY
|
||||||
|
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
|
||||||
|
bar_id = Client::inst()->bar_id;
|
||||||
|
if (auto id = config["id"]; id.isString()) {
|
||||||
|
bar_id = id.asString();
|
||||||
|
}
|
||||||
|
if (bar_id.empty()) {
|
||||||
|
bar_id = DEFAULT_BAR_ID;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
_ipc_client = std::make_unique<BarIpcClient>(*this);
|
||||||
|
} catch (const std::exception& exc) {
|
||||||
|
spdlog::warn("Failed to open bar ipc connection: {}", exc.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
setupWidgets();
|
setupWidgets();
|
||||||
window.show_all();
|
window.show_all();
|
||||||
|
|
||||||
|
@ -528,6 +617,44 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Need to define it here because of forward declared members */
|
||||||
|
waybar::Bar::~Bar() = default;
|
||||||
|
|
||||||
|
void waybar::Bar::setMode(const std::string_view& mode) {
|
||||||
|
using namespace std::literals::string_literals;
|
||||||
|
|
||||||
|
auto style = window.get_style_context();
|
||||||
|
/* remove styles added by previous setMode calls */
|
||||||
|
style->remove_class("mode-"s + last_mode_);
|
||||||
|
|
||||||
|
auto it = configured_modes.find(mode);
|
||||||
|
if (it != configured_modes.end()) {
|
||||||
|
last_mode_ = mode;
|
||||||
|
style->add_class("mode-"s + last_mode_);
|
||||||
|
setMode(it->second);
|
||||||
|
} else {
|
||||||
|
spdlog::warn("Unknown mode \"{}\" requested", mode);
|
||||||
|
last_mode_ = MODE_DEFAULT;
|
||||||
|
style->add_class("mode-"s + last_mode_);
|
||||||
|
setMode(configured_modes.at(MODE_DEFAULT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::Bar::setMode(const struct bar_mode& mode) {
|
||||||
|
surface_impl_->setLayer(mode.layer);
|
||||||
|
surface_impl_->setExclusiveZone(mode.exclusive);
|
||||||
|
surface_impl_->setPassThrough(mode.passthrough);
|
||||||
|
|
||||||
|
if (mode.visible) {
|
||||||
|
window.get_style_context()->remove_class("hidden");
|
||||||
|
window.set_opacity(1);
|
||||||
|
} else {
|
||||||
|
window.get_style_context()->add_class("hidden");
|
||||||
|
window.set_opacity(0);
|
||||||
|
}
|
||||||
|
surface_impl_->commit();
|
||||||
|
}
|
||||||
|
|
||||||
void waybar::Bar::onMap(GdkEventAny*) {
|
void waybar::Bar::onMap(GdkEventAny*) {
|
||||||
/*
|
/*
|
||||||
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
|
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
|
||||||
|
@ -538,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) {
|
||||||
|
|
||||||
void waybar::Bar::setVisible(bool value) {
|
void waybar::Bar::setVisible(bool value) {
|
||||||
visible = value;
|
visible = value;
|
||||||
if (!visible) {
|
setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);
|
||||||
window.get_style_context()->add_class("hidden");
|
|
||||||
window.set_opacity(0);
|
|
||||||
surface_impl_->setLayer(bar_layer::BOTTOM);
|
|
||||||
} else {
|
|
||||||
window.get_style_context()->remove_class("hidden");
|
|
||||||
window.set_opacity(1);
|
|
||||||
surface_impl_->setLayer(layer_);
|
|
||||||
}
|
|
||||||
surface_impl_->setExclusiveZone(exclusive && visible);
|
|
||||||
surface_impl_->commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::Bar::toggle() { setVisible(!visible); }
|
void waybar::Bar::toggle() { setVisible(!visible); }
|
||||||
|
@ -594,19 +711,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::Bar::handleSignal(int signal) {
|
void waybar::Bar::handleSignal(int signal) {
|
||||||
for (auto& module : modules_left_) {
|
for (auto& module : modules_all_) {
|
||||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
|
||||||
if (custom != nullptr) {
|
|
||||||
custom->refresh(signal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto& module : modules_center_) {
|
|
||||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
|
||||||
if (custom != nullptr) {
|
|
||||||
custom->refresh(signal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto& module : modules_right_) {
|
|
||||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
||||||
if (custom != nullptr) {
|
if (custom != nullptr) {
|
||||||
custom->refresh(signal);
|
custom->refresh(signal);
|
||||||
|
@ -614,19 +719,36 @@ void waybar::Bar::handleSignal(int signal) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
|
void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) {
|
||||||
if (config[pos].isArray()) {
|
auto module_list = group ? config[pos]["modules"] : config[pos];
|
||||||
for (const auto& name : config[pos]) {
|
if (module_list.isArray()) {
|
||||||
|
for (const auto& name : module_list) {
|
||||||
try {
|
try {
|
||||||
auto module = factory.makeModule(name.asString());
|
auto ref = name.asString();
|
||||||
if (pos == "modules-left") {
|
AModule* module;
|
||||||
modules_left_.emplace_back(module);
|
|
||||||
|
if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
|
||||||
|
auto group_module = new waybar::Group(ref, *this, config[ref]);
|
||||||
|
getModules(factory, ref, &group_module->box);
|
||||||
|
module = group_module;
|
||||||
|
} else {
|
||||||
|
module = factory.makeModule(ref);
|
||||||
}
|
}
|
||||||
if (pos == "modules-center") {
|
|
||||||
modules_center_.emplace_back(module);
|
std::shared_ptr<AModule> module_sp(module);
|
||||||
}
|
modules_all_.emplace_back(module_sp);
|
||||||
if (pos == "modules-right") {
|
if (group) {
|
||||||
modules_right_.emplace_back(module);
|
group->pack_start(*module, false, false);
|
||||||
|
} else {
|
||||||
|
if (pos == "modules-left") {
|
||||||
|
modules_left_.emplace_back(module_sp);
|
||||||
|
}
|
||||||
|
if (pos == "modules-center") {
|
||||||
|
modules_center_.emplace_back(module_sp);
|
||||||
|
}
|
||||||
|
if (pos == "modules-right") {
|
||||||
|
modules_right_.emplace_back(module_sp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module->dp.connect([module, &name] {
|
module->dp.connect([module, &name] {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -199,7 +199,6 @@ int waybar::Client::main(int argc, char *argv[]) {
|
||||||
bool show_version = false;
|
bool show_version = false;
|
||||||
std::string config_opt;
|
std::string config_opt;
|
||||||
std::string style_opt;
|
std::string style_opt;
|
||||||
std::string bar_id;
|
|
||||||
std::string log_level;
|
std::string log_level;
|
||||||
auto cli = clara::detail::Help(show_help) |
|
auto cli = clara::detail::Help(show_help) |
|
||||||
clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |
|
clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |
|
||||||
|
|
|
@ -30,10 +30,12 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
if (ref == "wlr/taskbar") {
|
if (ref == "wlr/taskbar") {
|
||||||
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
|
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
|
||||||
}
|
}
|
||||||
|
#ifdef USE_EXPERIMENTAL
|
||||||
if (ref == "wlr/workspaces") {
|
if (ref == "wlr/workspaces") {
|
||||||
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
|
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
#ifdef HAVE_RIVER
|
#ifdef HAVE_RIVER
|
||||||
if (ref == "river/tags") {
|
if (ref == "river/tags") {
|
||||||
return new waybar::modules::river::Tags(id, bar_, config_[name]);
|
return new waybar::modules::river::Tags(id, bar_, config_[name]);
|
||||||
|
@ -92,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
if (ref == "sndio") {
|
if (ref == "sndio") {
|
||||||
return new waybar::modules::Sndio(id, config_[name]);
|
return new waybar::modules::Sndio(id, config_[name]);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_GIO_UNIX
|
||||||
|
if (ref == "inhibitor") {
|
||||||
|
return new waybar::modules::Inhibitor(id, bar_, 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]);
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include "group.hpp"
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <util/command.hpp>
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
Group::Group(const std::string& name, const Bar& bar, const Json::Value& config)
|
||||||
|
: AModule(config, name, "", false, false),
|
||||||
|
box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Group::update() -> void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
Group::operator Gtk::Widget&() { return box; }
|
||||||
|
|
||||||
|
} // namespace waybar
|
|
@ -14,17 +14,51 @@
|
||||||
using waybar::modules::waybar_time;
|
using waybar::modules::waybar_time;
|
||||||
|
|
||||||
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||||
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
|
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
|
||||||
|
current_time_zone_idx_(0),
|
||||||
|
is_calendar_in_tooltip_(false)
|
||||||
|
{
|
||||||
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
|
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
|
||||||
time_zone_idx_ = 0;
|
for (const auto& zone_name: config_["timezones"]) {
|
||||||
setTimeZone(config_["timezones"][time_zone_idx_]);
|
if (!zone_name.isString() || zone_name.asString().empty()) {
|
||||||
} else {
|
time_zones_.push_back(nullptr);
|
||||||
setTimeZone(config_["timezone"]);
|
continue;
|
||||||
|
}
|
||||||
|
time_zones_.push_back(
|
||||||
|
date::locate_zone(
|
||||||
|
zone_name.asString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) {
|
||||||
|
time_zones_.push_back(
|
||||||
|
date::locate_zone(
|
||||||
|
config_["timezone"].asString()
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (fixed_time_zone_) {
|
|
||||||
|
// If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown.
|
||||||
|
if (!time_zones_.size()) {
|
||||||
|
time_zones_.push_back(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_timezone_fixed()) {
|
||||||
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
|
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a particular placeholder is present in the tooltip format, to know what to calculate on update.
|
||||||
|
if (config_["tooltip-format"].isString()) {
|
||||||
|
std::string trimmed_format = config_["tooltip-format"].asString();
|
||||||
|
trimmed_format.erase(std::remove_if(trimmed_format.begin(),
|
||||||
|
trimmed_format.end(),
|
||||||
|
[](unsigned char x){return std::isspace(x);}),
|
||||||
|
trimmed_format.end());
|
||||||
|
if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) {
|
||||||
|
is_calendar_in_tooltip_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config_["locale"].isString()) {
|
if (config_["locale"].isString()) {
|
||||||
locale_ = std::locale(config_["locale"].asString());
|
locale_ = std::locale(config_["locale"].asString());
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,53 +74,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const date::time_zone* waybar::modules::Clock::current_timezone() {
|
||||||
|
return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool waybar::modules::Clock::is_timezone_fixed() {
|
||||||
|
return time_zones_[current_time_zone_idx_] != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
auto waybar::modules::Clock::update() -> void {
|
auto waybar::modules::Clock::update() -> void {
|
||||||
if (!fixed_time_zone_) {
|
auto time_zone = current_timezone();
|
||||||
// Time zone can change. Be sure to pick that.
|
auto now = std::chrono::system_clock::now();
|
||||||
time_zone_ = date::current_zone();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto now = std::chrono::system_clock::now();
|
|
||||||
waybar_time wtime = {locale_,
|
waybar_time wtime = {locale_,
|
||||||
date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))};
|
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
|
||||||
|
std::string text = "";
|
||||||
std::string text;
|
if (!is_timezone_fixed()) {
|
||||||
if (!fixed_time_zone_) {
|
|
||||||
// As date dep is not fully compatible, prefer fmt
|
// As date dep is not fully compatible, prefer fmt
|
||||||
tzset();
|
tzset();
|
||||||
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
||||||
text = fmt::format(format_, localtime);
|
text = fmt::format(format_, localtime);
|
||||||
label_.set_markup(text);
|
|
||||||
} else {
|
} else {
|
||||||
text = fmt::format(format_, wtime);
|
text = fmt::format(format_, wtime);
|
||||||
label_.set_markup(text);
|
|
||||||
}
|
}
|
||||||
|
label_.set_markup(text);
|
||||||
|
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
if (config_["tooltip-format"].isString()) {
|
if (config_["tooltip-format"].isString()) {
|
||||||
const auto calendar = calendar_text(wtime);
|
std::string calendar_lines = "";
|
||||||
auto tooltip_format = config_["tooltip-format"].asString();
|
if (is_calendar_in_tooltip_) {
|
||||||
auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar));
|
calendar_lines = calendar_text(wtime);
|
||||||
label_.set_tooltip_markup(tooltip_text);
|
}
|
||||||
} else {
|
auto tooltip_format = config_["tooltip-format"].asString();
|
||||||
label_.set_tooltip_markup(text);
|
text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label_.set_tooltip_markup(text);
|
||||||
// Call parent update
|
// Call parent update
|
||||||
ALabel::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) {
|
|
||||||
if (!zone_name.isString() || zone_name.asString().empty()) {
|
|
||||||
fixed_time_zone_ = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
time_zone_ = date::locate_zone(zone_name.asString());
|
|
||||||
fixed_time_zone_ = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
|
bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
|
||||||
// defer to user commands if set
|
// defer to user commands if set
|
||||||
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
||||||
|
@ -97,17 +124,18 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
|
||||||
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
|
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
|
if (time_zones_.size() == 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
auto nr_zones = config_["timezones"].size();
|
|
||||||
|
auto nr_zones = time_zones_.size();
|
||||||
if (dir == SCROLL_DIR::UP) {
|
if (dir == SCROLL_DIR::UP) {
|
||||||
size_t new_idx = time_zone_idx_ + 1;
|
size_t new_idx = current_time_zone_idx_ + 1;
|
||||||
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
||||||
} else {
|
} else {
|
||||||
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
|
current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
|
||||||
}
|
}
|
||||||
setTimeZone(config_["timezones"][time_zone_idx_]);
|
|
||||||
update();
|
update();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
#include "modules/inhibitor.hpp"
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <gio/gunixfdlist.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>;
|
||||||
|
|
||||||
|
auto dbus() -> DBus {
|
||||||
|
GError *error = nullptr;
|
||||||
|
GDBusConnection* connection =
|
||||||
|
g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
spdlog::error("g_bus_get_sync() failed: {}", error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
connection = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto destructor = [](GDBusConnection* connection) {
|
||||||
|
GError *error = nullptr;
|
||||||
|
g_dbus_connection_close_sync(connection, nullptr, &error);
|
||||||
|
if (error) {
|
||||||
|
spdlog::error(
|
||||||
|
"g_bus_connection_close_sync failed(): {}",
|
||||||
|
error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return DBus{connection, destructor};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getLocks(const DBus& bus, const std::string& inhibitors) -> int {
|
||||||
|
GError *error = nullptr;
|
||||||
|
GUnixFDList* fd_list;
|
||||||
|
int handle;
|
||||||
|
|
||||||
|
auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(),
|
||||||
|
"org.freedesktop.login1",
|
||||||
|
"/org/freedesktop/login1",
|
||||||
|
"org.freedesktop.login1.Manager",
|
||||||
|
"Inhibit",
|
||||||
|
g_variant_new(
|
||||||
|
"(ssss)",
|
||||||
|
inhibitors.c_str(),
|
||||||
|
"waybar",
|
||||||
|
"Asked by user",
|
||||||
|
"block"),
|
||||||
|
G_VARIANT_TYPE("(h)"),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
|
-1,
|
||||||
|
nullptr,
|
||||||
|
&fd_list,
|
||||||
|
nullptr,
|
||||||
|
&error);
|
||||||
|
if (error) {
|
||||||
|
spdlog::error(
|
||||||
|
"g_dbus_connection_call_with_unix_fd_list_sync() failed: {}",
|
||||||
|
error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
handle = -1;
|
||||||
|
} else {
|
||||||
|
gint index;
|
||||||
|
g_variant_get(reply, "(h)", &index);
|
||||||
|
g_variant_unref(reply);
|
||||||
|
handle = g_unix_fd_list_get(fd_list, index, nullptr);
|
||||||
|
g_object_unref(fd_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto checkInhibitor(const std::string& inhibitor) -> const std::string& {
|
||||||
|
static const auto inhibitors = std::array{
|
||||||
|
"idle",
|
||||||
|
"shutdown",
|
||||||
|
"sleep",
|
||||||
|
"handle-power-key",
|
||||||
|
"handle-suspend-key",
|
||||||
|
"handle-hibernate-key",
|
||||||
|
"handle-lid-switch"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor)
|
||||||
|
== inhibitors.end()) {
|
||||||
|
throw std::runtime_error("invalid logind inhibitor " + inhibitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inhibitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getInhibitors(const Json::Value& config) -> std::string {
|
||||||
|
std::string inhibitors = "idle";
|
||||||
|
|
||||||
|
if (config["what"].empty()) {
|
||||||
|
return inhibitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config["what"].isString()) {
|
||||||
|
return checkInhibitor(config["what"].asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config["what"].isArray()) {
|
||||||
|
inhibitors = checkInhibitor(config["what"][0].asString());
|
||||||
|
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
|
||||||
|
inhibitors += ":" + checkInhibitor(config["what"][i].asString());
|
||||||
|
}
|
||||||
|
return inhibitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inhibitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
Inhibitor::Inhibitor(const std::string& id, const Bar& bar,
|
||||||
|
const Json::Value& config)
|
||||||
|
: ALabel(config, "inhibitor", id, "{status}", true),
|
||||||
|
dbus_(::dbus()),
|
||||||
|
inhibitors_(::getInhibitors(config)) {
|
||||||
|
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||||
|
event_box_.signal_button_press_event().connect(
|
||||||
|
sigc::mem_fun(*this, &Inhibitor::handleToggle));
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Inhibitor::~Inhibitor() {
|
||||||
|
if (handle_ != -1) {
|
||||||
|
::close(handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Inhibitor::activated() -> bool {
|
||||||
|
return handle_ != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Inhibitor::update() -> void {
|
||||||
|
std::string status_text = activated() ? "activated" : "deactivated";
|
||||||
|
|
||||||
|
label_.get_style_context()->remove_class(
|
||||||
|
activated() ? "deactivated" : "activated");
|
||||||
|
label_.set_markup(
|
||||||
|
fmt::format(format_, fmt::arg("status", status_text),
|
||||||
|
fmt::arg("icon", getIcon(0, status_text))));
|
||||||
|
label_.get_style_context()->add_class(status_text);
|
||||||
|
|
||||||
|
if (tooltipEnabled()) {
|
||||||
|
label_.set_tooltip_text(status_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ALabel::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
|
||||||
|
if (e->button == 1) {
|
||||||
|
if (activated()) {
|
||||||
|
::close(handle_);
|
||||||
|
handle_ = -1;
|
||||||
|
} else {
|
||||||
|
handle_ = ::getLocks(dbus_, inhibitors_);
|
||||||
|
if (handle_ == -1) {
|
||||||
|
spdlog::error("cannot get inhibitor locks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ALabel::handleToggle(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // waybar::modules
|
|
@ -789,8 +789,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
|
||||||
// signalstrength in dBm from mBm
|
// signalstrength in dBm from mBm
|
||||||
signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
|
signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
|
||||||
|
|
||||||
// WiFi-hardware usually operates in the range -90 to -20dBm.
|
// WiFi-hardware usually operates in the range -90 to -30dBm.
|
||||||
const int hardwareMax = -20;
|
const int hardwareMax = -30;
|
||||||
const int hardwareMin = -90;
|
const int hardwareMin = -90;
|
||||||
const int strength =
|
const int strength =
|
||||||
((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100;
|
((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100;
|
||||||
|
|
|
@ -79,6 +79,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||||
if (dir == SCROLL_DIR::NONE) {
|
if (dir == SCROLL_DIR::NONE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (config_["reverse-scrolling"].asInt() == 1){
|
||||||
|
if (dir == SCROLL_DIR::UP) {
|
||||||
|
dir = SCROLL_DIR::DOWN;
|
||||||
|
} else if (dir == SCROLL_DIR::DOWN) {
|
||||||
|
dir = SCROLL_DIR::UP;
|
||||||
|
}
|
||||||
|
}
|
||||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||||
pa_volume_t change = volume_tick;
|
pa_volume_t change = volume_tick;
|
||||||
pa_cvolume pa_volume = pa_volume_;
|
pa_cvolume pa_volume = pa_volume_;
|
||||||
|
@ -211,7 +218,7 @@ static const std::array<std::string, 9> ports = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
||||||
std::vector<std::string> res = {default_source_name_};
|
std::vector<std::string> res = {current_sink_name_, default_source_name_};
|
||||||
std::string nameLC = port_name_ + form_factor_;
|
std::string nameLC = port_name_ + form_factor_;
|
||||||
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
||||||
for (auto const &port : ports) {
|
for (auto const &port : ports) {
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
#include "modules/sway/bar.hpp"
|
||||||
|
|
||||||
|
#include <fmt/ostream.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/sway/ipc/ipc.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::sway {
|
||||||
|
|
||||||
|
BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {
|
||||||
|
{
|
||||||
|
sigc::connection handle =
|
||||||
|
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig));
|
||||||
|
ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id);
|
||||||
|
|
||||||
|
handle.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate));
|
||||||
|
signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate));
|
||||||
|
|
||||||
|
ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])");
|
||||||
|
ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent));
|
||||||
|
// Launch worker
|
||||||
|
ipc_.setWorker([this] {
|
||||||
|
try {
|
||||||
|
ipc_.handleEvent();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("BarIpcClient::handleEvent {}", e.what());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct swaybar_config parseConfig(const Json::Value& payload) {
|
||||||
|
swaybar_config conf;
|
||||||
|
if (auto id = payload["id"]; id.isString()) {
|
||||||
|
conf.id = id.asString();
|
||||||
|
}
|
||||||
|
if (auto mode = payload["mode"]; mode.isString()) {
|
||||||
|
conf.mode = mode.asString();
|
||||||
|
}
|
||||||
|
if (auto hs = payload["hidden_state"]; hs.isString()) {
|
||||||
|
conf.hidden_state = hs.asString();
|
||||||
|
}
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) {
|
||||||
|
auto payload = parser_.parse(res.payload);
|
||||||
|
if (auto success = payload.get("success", true); !success.asBool()) {
|
||||||
|
auto err = payload.get("error", "Unknown error");
|
||||||
|
throw std::runtime_error(err.asString());
|
||||||
|
}
|
||||||
|
auto config = parseConfig(payload);
|
||||||
|
onConfigUpdate(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) {
|
||||||
|
try {
|
||||||
|
auto payload = parser_.parse(res.payload);
|
||||||
|
if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) {
|
||||||
|
spdlog::trace("swaybar ipc: ignore event for {}", id.asString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.isMember("visible_by_modifier")) {
|
||||||
|
// visibility change for hidden bar
|
||||||
|
signal_visible_(payload["visible_by_modifier"].asBool());
|
||||||
|
} else {
|
||||||
|
// configuration update
|
||||||
|
auto config = parseConfig(payload);
|
||||||
|
signal_config_(std::move(config));
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("BarIpcClient::onEvent {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::onConfigUpdate(const swaybar_config& config) {
|
||||||
|
spdlog::info("config update for {}: id {}, mode {}, hidden_state {}",
|
||||||
|
bar_.bar_id,
|
||||||
|
config.id,
|
||||||
|
config.mode,
|
||||||
|
config.hidden_state);
|
||||||
|
bar_config_ = config;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) {
|
||||||
|
spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier);
|
||||||
|
visible_by_modifier_ = visible_by_modifier;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::update() {
|
||||||
|
bool visible = visible_by_modifier_;
|
||||||
|
if (bar_config_.mode == "invisible") {
|
||||||
|
visible = false;
|
||||||
|
} else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") {
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::sway
|
|
@ -99,7 +99,8 @@ auto Language::update() -> void {
|
||||||
fmt::arg("short", layout_.short_name),
|
fmt::arg("short", layout_.short_name),
|
||||||
fmt::arg("shortDescription", layout_.short_description),
|
fmt::arg("shortDescription", layout_.short_description),
|
||||||
fmt::arg("long", layout_.full_name),
|
fmt::arg("long", layout_.full_name),
|
||||||
fmt::arg("variant", layout_.variant)));
|
fmt::arg("variant", layout_.variant),
|
||||||
|
fmt::arg("flag", layout_.country_flag())));
|
||||||
label_.set_markup(display_layout);
|
label_.set_markup(display_layout);
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
if (tooltip_format_ != "") {
|
if (tooltip_format_ != "") {
|
||||||
|
@ -107,7 +108,8 @@ auto Language::update() -> void {
|
||||||
fmt::arg("short", layout_.short_name),
|
fmt::arg("short", layout_.short_name),
|
||||||
fmt::arg("shortDescription", layout_.short_description),
|
fmt::arg("shortDescription", layout_.short_description),
|
||||||
fmt::arg("long", layout_.full_name),
|
fmt::arg("long", layout_.full_name),
|
||||||
fmt::arg("variant", layout_.variant)));
|
fmt::arg("variant", layout_.variant),
|
||||||
|
fmt::arg("flag", layout_.country_flag())));
|
||||||
label_.set_tooltip_markup(tooltip_display_layout);
|
label_.set_tooltip_markup(tooltip_display_layout);
|
||||||
} else {
|
} else {
|
||||||
label_.set_tooltip_markup(display_layout);
|
label_.set_tooltip_markup(display_layout);
|
||||||
|
@ -212,4 +214,15 @@ Language::XKBContext::~XKBContext() {
|
||||||
rxkb_context_unref(context_);
|
rxkb_context_unref(context_);
|
||||||
delete layout_;
|
delete layout_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Language::Layout::country_flag() const {
|
||||||
|
if (short_name.size() != 2) return "";
|
||||||
|
unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00";
|
||||||
|
result[3] = short_name[0] + 0x45;
|
||||||
|
result[7] = short_name[1] + 0x45;
|
||||||
|
// Check if both emojis are in A-Z symbol bounds
|
||||||
|
if (result[3] < 0xa6 || result[3] > 0xbf) return "";
|
||||||
|
if (result[7] < 0xa6 || result[7] > 0xbf) return "";
|
||||||
|
return std::string{reinterpret_cast<char*>(result)};
|
||||||
|
}
|
||||||
} // namespace waybar::modules::sway
|
} // namespace waybar::modules::sway
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
#include <glibmm/main.h>
|
||||||
|
/**
|
||||||
|
* Minimal Glib application to be used for tests that require Glib main loop
|
||||||
|
*/
|
||||||
|
class GlibTestsFixture : public sigc::trackable {
|
||||||
|
public:
|
||||||
|
GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {}
|
||||||
|
|
||||||
|
void setTimeout(int timeout) {
|
||||||
|
Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); },
|
||||||
|
timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(std::function<void()> fn) {
|
||||||
|
Glib::signal_idle().connect_once(fn);
|
||||||
|
main_loop_->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void quit() { main_loop_->quit(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Glib::RefPtr<Glib::MainLoop> main_loop_;
|
||||||
|
};
|
|
@ -0,0 +1,145 @@
|
||||||
|
#define CATCH_CONFIG_RUNNER
|
||||||
|
#include "util/SafeSignal.hpp"
|
||||||
|
|
||||||
|
#include <glibmm.h>
|
||||||
|
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "GlibTestsFixture.hpp"
|
||||||
|
|
||||||
|
using namespace waybar;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic sanity test for SafeSignal:
|
||||||
|
* check that type deduction works, events are delivered and the order is right
|
||||||
|
* Running this with -fsanitize=thread should not fail
|
||||||
|
*/
|
||||||
|
TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][thread][util]") {
|
||||||
|
const int NUM_EVENTS = 100;
|
||||||
|
int count = 0;
|
||||||
|
int last_value = 0;
|
||||||
|
|
||||||
|
SafeSignal<int, std::string> test_signal;
|
||||||
|
|
||||||
|
const auto main_tid = std::this_thread::get_id();
|
||||||
|
std::thread producer;
|
||||||
|
|
||||||
|
// timeout the test in 500ms
|
||||||
|
setTimeout(500);
|
||||||
|
|
||||||
|
test_signal.connect([&](auto val, auto str) {
|
||||||
|
static_assert(std::is_same<int, decltype(val)>::value);
|
||||||
|
static_assert(std::is_same<std::string, decltype(str)>::value);
|
||||||
|
// check that we're in the same thread as the main loop
|
||||||
|
REQUIRE(std::this_thread::get_id() == main_tid);
|
||||||
|
// check event order
|
||||||
|
REQUIRE(val == last_value + 1);
|
||||||
|
|
||||||
|
last_value = val;
|
||||||
|
if (++count >= NUM_EVENTS) {
|
||||||
|
this->quit();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
run([&]() {
|
||||||
|
// check that events from the same thread are delivered and processed synchronously
|
||||||
|
test_signal.emit(1, "test");
|
||||||
|
REQUIRE(count == 1);
|
||||||
|
|
||||||
|
// start another thread and generate events
|
||||||
|
producer = std::thread([&]() {
|
||||||
|
for (auto i = 2; i <= NUM_EVENTS; ++i) {
|
||||||
|
test_signal.emit(i, "test");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
producer.join();
|
||||||
|
REQUIRE(count == NUM_EVENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct TestObject {
|
||||||
|
T value;
|
||||||
|
unsigned copied = 0;
|
||||||
|
unsigned moved = 0;
|
||||||
|
|
||||||
|
TestObject(const T& v) : value(v){};
|
||||||
|
~TestObject() = default;
|
||||||
|
|
||||||
|
TestObject(const TestObject& other)
|
||||||
|
: value(other.value), copied(other.copied + 1), moved(other.moved) {}
|
||||||
|
|
||||||
|
TestObject(TestObject&& other) noexcept
|
||||||
|
: value(std::move(other.value)),
|
||||||
|
copied(std::exchange(other.copied, 0)),
|
||||||
|
moved(std::exchange(other.moved, 0) + 1) {}
|
||||||
|
|
||||||
|
TestObject& operator=(const TestObject& other) {
|
||||||
|
value = other.value;
|
||||||
|
copied = other.copied + 1;
|
||||||
|
moved = other.moved;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestObject& operator=(TestObject&& other) noexcept {
|
||||||
|
value = std::move(other.value);
|
||||||
|
copied = std::exchange(other.copied, 0);
|
||||||
|
moved = std::exchange(other.moved, 0) + 1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(T other) const { return value == other; }
|
||||||
|
operator T() const { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check the number of copies/moves performed on the object passed through SafeSignal
|
||||||
|
*/
|
||||||
|
TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thread][util]") {
|
||||||
|
const int NUM_EVENTS = 3;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
SafeSignal<TestObject<int>> test_signal;
|
||||||
|
|
||||||
|
std::thread producer;
|
||||||
|
|
||||||
|
// timeout the test in 500ms
|
||||||
|
setTimeout(500);
|
||||||
|
|
||||||
|
test_signal.connect([&](auto& val) {
|
||||||
|
static_assert(std::is_same<TestObject<int>, remove_cvref_t<decltype(val)>>::value);
|
||||||
|
|
||||||
|
/* explicit move in the producer thread */
|
||||||
|
REQUIRE(val.moved <= 1);
|
||||||
|
/* copy within the SafeSignal queuing code */
|
||||||
|
REQUIRE(val.copied <= 1);
|
||||||
|
|
||||||
|
if (++count >= NUM_EVENTS) {
|
||||||
|
this->quit();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
run([&]() {
|
||||||
|
test_signal.emit(1);
|
||||||
|
REQUIRE(count == 1);
|
||||||
|
producer = std::thread([&]() {
|
||||||
|
for (auto i = 2; i <= NUM_EVENTS; ++i) {
|
||||||
|
TestObject<int> t{i};
|
||||||
|
// check that signal.emit accepts moved objects
|
||||||
|
test_signal.emit(std::move(t));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
producer.join();
|
||||||
|
REQUIRE(count == NUM_EVENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
Glib::init();
|
||||||
|
return Catch::Session().run(argc, argv);
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ test_inc = include_directories('../include')
|
||||||
test_dep = [
|
test_dep = [
|
||||||
catch2,
|
catch2,
|
||||||
fmt,
|
fmt,
|
||||||
|
gtkmm,
|
||||||
jsoncpp,
|
jsoncpp,
|
||||||
spdlog,
|
spdlog,
|
||||||
]
|
]
|
||||||
|
@ -14,8 +15,21 @@ config_test = executable(
|
||||||
include_directories: test_inc,
|
include_directories: test_inc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
safesignal_test = executable(
|
||||||
|
'safesignal_test',
|
||||||
|
'SafeSignal.cpp',
|
||||||
|
dependencies: test_dep,
|
||||||
|
include_directories: test_inc,
|
||||||
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Configuration test',
|
'Configuration test',
|
||||||
config_test,
|
config_test,
|
||||||
workdir: meson.source_root(),
|
workdir: meson.source_root(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'SafeSignal test',
|
||||||
|
safesignal_test,
|
||||||
|
workdir: meson.source_root(),
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue