Merge branch 'master' into battery-custom-tooltip

This commit is contained in:
Marek Černoch 2021-01-18 12:22:44 +01:00 committed by GitHub
commit fdaba72974
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 2527 additions and 733 deletions

22
.github/workflows/freebsd.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: freebsd
on: [ push, pull_request ]
jobs:
clang:
runs-on: macos-latest # until https://github.com/actions/runner/issues/385
steps:
- uses: actions/checkout@v2
- name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v0.0.9 # aka FreeBSD 12.2
with:
usesh: true
prepare: |
export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio
sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
pkg install -y git # subprojects/date
pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio \
libfmt libmpdclient libudev-devd meson pkgconf pulseaudio scdoc spdlog
run: |
meson build -Dman-pages=enabled
ninja -C build

View File

@ -29,7 +29,9 @@ jobs:
compiler: clang
env:
before_install:
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu
- export CPPFLAGS+=-isystem/usr/local/include LDFLAGS+=-L/usr/local/lib # sndio
- sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio
libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog
script:
- meson build -Dman-pages=enabled

View File

@ -2,4 +2,4 @@
FROM alpine:latest
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev scdoc
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc

View File

@ -67,6 +67,7 @@ libnl [Network module]
libappindicator-gtk3 [Tray module]
libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module]
libsndio [sndio module]
```
**Build dependencies**

View File

@ -19,7 +19,6 @@ class ALabel : public AModule {
protected:
Gtk::Label label_;
std::string format_;
std::string click_param;
const std::chrono::seconds interval_;
bool alt_ = false;
std::string default_format_;

View File

@ -7,9 +7,8 @@
#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include <json/json.h>
#include "AModule.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar {
@ -18,18 +17,48 @@ class Factory;
struct waybar_output {
Glib::RefPtr<Gdk::Monitor> monitor;
std::string name;
std::string identifier;
std::unique_ptr<struct zxdg_output_v1, decltype(&zxdg_output_v1_destroy)> xdg_output = {
nullptr, &zxdg_output_v1_destroy};
};
enum class bar_layer : uint8_t {
BOTTOM,
TOP,
OVERLAY,
};
struct bar_margins {
int top = 0;
int right = 0;
int bottom = 0;
int left = 0;
};
class BarSurface {
protected:
BarSurface() = default;
public:
virtual void setExclusiveZone(bool enable) = 0;
virtual void setLayer(bar_layer layer) = 0;
virtual void setMargins(const struct bar_margins &margins) = 0;
virtual void setPosition(const std::string_view &position) = 0;
virtual void setSize(uint32_t width, uint32_t height) = 0;
virtual void commit(){};
virtual ~BarSurface() = default;
};
class Bar {
public:
Bar(struct waybar_output *w_output, const Json::Value &);
Bar(const Bar &) = delete;
~Bar() = default;
auto toggle() -> void;
void setVisible(bool visible);
void toggle();
void handleSignal(int);
struct waybar_output *output;
@ -40,48 +69,14 @@ class Bar {
Gtk::Window window;
private:
static constexpr const char *MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules";
static constexpr const char *MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules";
static constexpr const char *BAR_SIZE_MSG =
"Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char *SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
static void layerSurfaceHandleConfigure(void *, struct zwlr_layer_surface_v1 *, uint32_t,
uint32_t, uint32_t);
static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *);
#ifdef HAVE_GTK_LAYER_SHELL
/* gtk-layer-shell code */
void initGtkLayerShell();
void onConfigureGLS(GdkEventConfigure *ev);
void onMapGLS(GdkEventAny *ev);
#endif
/* fallback layer-surface code */
void onConfigure(GdkEventConfigure *ev);
void onRealize();
void onMap(GdkEventAny *ev);
void setSurfaceSize(uint32_t width, uint32_t height);
/* common code */
void setExclusiveZone(uint32_t width, uint32_t height);
void onMap(GdkEventAny *);
auto setupWidgets() -> void;
void getModules(const Factory &, const std::string &);
void setupAltFormatKeyForModule(const std::string &module_name);
void setupAltFormatKeyForModuleList(const char *module_list_name);
struct margins {
int top = 0;
int right = 0;
int bottom = 0;
int left = 0;
} margins_;
struct zwlr_layer_surface_v1 *layer_surface_;
// use gtk-layer-shell instead of handling layer surfaces directly
bool use_gls_ = false;
uint32_t width_ = 0;
uint32_t height_ = 1;
uint8_t anchor_;
std::unique_ptr<BarSurface> surface_impl_;
bar_layer layer_;
Gtk::Box left_;
Gtk::Box center_;
Gtk::Box right_;

View File

@ -6,14 +6,20 @@
#include <unistd.h>
#include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp"
struct zwlr_layer_shell_v1;
struct zwp_idle_inhibitor_v1;
struct zwp_idle_inhibit_manager_v1;
namespace waybar {
class Client {
public:
static Client *inst();
int main(int argc, char *argv[]);
void reset();
Glib::RefPtr<Gtk::Application> gtk_app;
Glib::RefPtr<Gdk::Display> gdk_display;
@ -40,7 +46,9 @@ class Client {
static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version);
static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name);
static void handleOutputDone(void *, struct zxdg_output_v1 *);
static void handleOutputName(void *, struct zxdg_output_v1 *, const char *);
static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *);
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);

View File

@ -1,11 +1,16 @@
#pragma once
#include <json/json.h>
#ifdef HAVE_LIBDATE
#include "modules/clock.hpp"
#else
#include "modules/simpleclock.hpp"
#endif
#ifdef HAVE_SWAY
#include "modules/sway/mode.hpp"
#include "modules/sway/window.hpp"
#include "modules/sway/workspaces.hpp"
#include "modules/sway/language.hpp"
#endif
#ifdef HAVE_WLR
#include "modules/wlr/taskbar.hpp"
@ -37,7 +42,10 @@
#include "modules/pulseaudio.hpp"
#endif
#ifdef HAVE_LIBMPDCLIENT
#include "modules/mpd.hpp"
#include "modules/mpd/mpd.hpp"
#endif
#ifdef HAVE_LIBSNDIO
#include "modules/sndio.hpp"
#endif
#include "bar.hpp"
#include "modules/custom.hpp"

View File

@ -31,19 +31,22 @@ class Battery : public ALabel {
private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
void getBatteries();
void refreshBatteries();
void worker();
const std::string getAdapterStatus(uint8_t capacity) const;
const std::tuple<uint8_t, float, std::string> getInfos() const;
const std::tuple<uint8_t, float, std::string> getInfos();
const std::string formatTimeRemaining(float hoursRemaining);
std::vector<fs::path> batteries_;
int global_watch;
std::map<fs::path,int> batteries_;
fs::path adapter_;
int fd_;
std::vector<int> wds_;
int battery_watch_fd_;
int global_watch_fd_;
std::mutex battery_list_mutex_;
std::string old_status_;
util::SleeperThread thread_;
util::SleeperThread thread_battery_update_;
util::SleeperThread thread_timer_;
};

View File

@ -29,7 +29,7 @@ class Clock : public ALabel {
const date::time_zone* time_zone_;
bool fixed_time_zone_;
int time_zone_idx_;
date::year_month_day cached_calendar_ymd_;
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
std::string cached_calendar_text_;
bool handleScroll(GdkEventScroll* e);

View File

@ -22,6 +22,7 @@ class Custom : public ALabel {
void continuousWorker();
void parseOutputRaw();
void parseOutputJson();
void handleEvent();
bool handleScroll(GdkEventScroll* e);
bool handleToggle(GdkEventButton* const& e);

View File

@ -12,12 +12,13 @@ class IdleInhibitor : public ALabel {
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~IdleInhibitor();
auto update() -> void;
static std::list<waybar::AModule*> modules;
static bool status;
private:
bool handleToggle(GdkEventButton* const& e);
const Bar& bar_;
std::string status_;
struct zwp_idle_inhibitor_v1* idle_inhibitor_;
int pid_;
};

View File

@ -1,74 +0,0 @@
#pragma once
#include <fmt/format.h>
#include <mpd/client.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
namespace waybar::modules {
class MPD : public ALabel {
public:
MPD(const std::string&, const Json::Value&);
auto update() -> void;
private:
std::thread periodic_updater();
std::string getTag(mpd_tag_type type, unsigned idx = 0);
void setLabel();
std::string getStateIcon();
std::string getOptionIcon(std::string optionName, bool activated);
std::thread event_listener();
// Assumes `connection_lock_` is locked
void tryConnect();
// If checking errors on the main connection, make sure to lock it using
// `connection_lock_` before calling checkErrors
void checkErrors(mpd_connection* conn);
// Assumes `connection_lock_` is locked
void fetchState();
void waitForEvent();
bool handlePlayPause(GdkEventButton* const&);
bool stopped();
bool playing();
bool paused();
const std::string module_name_;
using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;
using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;
using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;
// Not using unique_ptr since we don't manage the pointer
// (It's either nullptr, or from the config)
const char* server_;
const unsigned port_;
unsigned timeout_;
// We need a mutex here because we can trigger updates from multiple thread:
// the event based updates, the periodic updates needed for the elapsed time,
// and the click play/pause feature
std::mutex connection_lock_;
unique_connection connection_;
// The alternate connection will be used to wait for events: since it will
// be blocking (idle) we can't send commands via this connection
//
// No lock since only used in the event listener thread
unique_connection alternate_connection_;
// Protect them using the `connection_lock_`
unique_status status_;
mpd_state state_;
unique_song song_;
// To make sure the previous periodic_updater stops before creating a new one
std::mutex periodic_lock_;
};
} // namespace waybar::modules

View File

@ -0,0 +1,67 @@
#pragma once
#include <fmt/format.h>
#include <mpd/client.h>
#include <spdlog/spdlog.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
#include "modules/mpd/state.hpp"
namespace waybar::modules {
class MPD : public ALabel {
friend class detail::Context;
// State machine
detail::Context context_{this};
const std::string module_name_;
// Not using unique_ptr since we don't manage the pointer
// (It's either nullptr, or from the config)
const char* server_;
const unsigned port_;
const std::string password_;
unsigned timeout_;
detail::unique_connection connection_;
detail::unique_status status_;
mpd_state state_;
detail::unique_song song_;
public:
MPD(const std::string&, const Json::Value&);
virtual ~MPD() noexcept = default;
auto update() -> void;
private:
std::string getTag(mpd_tag_type type, unsigned idx = 0) const;
void setLabel();
std::string getStateIcon() const;
std::string getOptionIcon(std::string optionName, bool activated) const;
// GUI-side methods
bool handlePlayPause(GdkEventButton* const&);
void emit() { dp.emit(); }
// MPD-side, Non-GUI methods.
void tryConnect();
void checkErrors(mpd_connection* conn);
void fetchState();
void queryMPD();
inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; }
inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; }
inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; }
};
#if !defined(MPD_NOINLINE)
#include "modules/mpd/state.inl.hpp"
#endif
} // namespace waybar::modules

View File

@ -0,0 +1,217 @@
#pragma once
#include <mpd/client.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
namespace waybar::modules {
class MPD;
} // namespace waybar::modules
namespace waybar::modules::detail {
using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;
using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;
using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;
class Context;
/// This state machine loosely follows a non-hierarchical, statechart
/// pattern, and includes ENTRY and EXIT actions.
///
/// The State class is the base class for all other states. The
/// entry and exit methods are automatically called when entering
/// into a new state and exiting from the current state. This
/// includes initially entering (Disconnected class) and exiting
/// Waybar.
///
/// The following nested "top-level" states are represented:
/// 1. Idle - await notification of MPD activity.
/// 2. All Non-Idle states:
/// 1. Playing - An active song is producing audio output.
/// 2. Paused - The current song is paused.
/// 3. Stopped - No song is actively playing.
/// 3. Disconnected - periodically attempt MPD (re-)connection.
///
/// NOTE: Since this statechart is non-hierarchical, the above
/// states are flattened into a set.
class State {
public:
virtual ~State() noexcept = default;
virtual void entry() noexcept { spdlog::debug("mpd: ignore entry action"); }
virtual void exit() noexcept { spdlog::debug("mpd: ignore exit action"); }
virtual void play() { spdlog::debug("mpd: ignore play state transition"); }
virtual void stop() { spdlog::debug("mpd: ignore stop state transition"); }
virtual void pause() { spdlog::debug("mpd: ignore pause state transition"); }
/// Request state update the GUI.
virtual void update() noexcept { spdlog::debug("mpd: ignoring update method request"); }
};
class Idle : public State {
Context* const ctx_;
sigc::connection idle_connection_;
public:
Idle(Context* const ctx) : ctx_{ctx} {}
virtual ~Idle() noexcept { this->exit(); };
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void stop() override;
void pause() override;
void update() noexcept override;
private:
Idle(const Idle&) = delete;
Idle& operator=(const Idle&) = delete;
bool on_io(Glib::IOCondition const&);
};
class Playing : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Playing(Context* const ctx) : ctx_{ctx} {}
virtual ~Playing() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void pause() override;
void stop() override;
void update() noexcept override;
private:
Playing(Playing const&) = delete;
Playing& operator=(Playing const&) = delete;
bool on_timer();
};
class Paused : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Paused(Context* const ctx) : ctx_{ctx} {}
virtual ~Paused() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void stop() override;
void update() noexcept override;
private:
Paused(Paused const&) = delete;
Paused& operator=(Paused const&) = delete;
bool on_timer();
};
class Stopped : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Stopped(Context* const ctx) : ctx_{ctx} {}
virtual ~Stopped() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void pause() override;
void update() noexcept override;
private:
Stopped(Stopped const&) = delete;
Stopped& operator=(Stopped const&) = delete;
bool on_timer();
};
class Disconnected : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Disconnected(Context* const ctx) : ctx_{ctx} {}
virtual ~Disconnected() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void update() noexcept override;
private:
Disconnected(Disconnected const&) = delete;
Disconnected& operator=(Disconnected const&) = delete;
void arm_timer(int interval) noexcept;
void disarm_timer() noexcept;
bool on_timer();
};
class Context {
std::unique_ptr<State> state_;
waybar::modules::MPD* mpd_module_;
friend class State;
friend class Playing;
friend class Paused;
friend class Stopped;
friend class Disconnected;
friend class Idle;
protected:
void setState(std::unique_ptr<State>&& new_state) noexcept {
if (state_.get() != nullptr) {
state_->exit();
}
state_ = std::move(new_state);
state_->entry();
}
bool is_connected() const;
bool is_playing() const;
bool is_paused() const;
bool is_stopped() const;
constexpr std::size_t interval() const;
void tryConnect() const;
void checkErrors(mpd_connection*) const;
void do_update();
void queryMPD() const;
void fetchState() const;
constexpr mpd_state state() const;
void emit() const;
[[nodiscard]] unique_connection& connection();
public:
explicit Context(waybar::modules::MPD* const mpd_module)
: state_{std::make_unique<Disconnected>(this)}, mpd_module_{mpd_module} {
state_->entry();
}
void play() { state_->play(); }
void stop() { state_->stop(); }
void pause() { state_->pause(); }
void update() noexcept { state_->update(); }
};
} // namespace waybar::modules::detail

View File

@ -0,0 +1,24 @@
#pragma once
namespace detail {
inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; }
inline bool Context::is_playing() const { return mpd_module_->playing(); }
inline bool Context::is_paused() const { return mpd_module_->paused(); }
inline bool Context::is_stopped() const { return mpd_module_->stopped(); }
constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); }
inline void Context::tryConnect() const { mpd_module_->tryConnect(); }
inline unique_connection& Context::connection() { return mpd_module_->connection_; }
constexpr inline mpd_state Context::state() const { return mpd_module_->state_; }
inline void Context::do_update() {
mpd_module_->setLabel();
}
inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); }
inline void Context::queryMPD() const { mpd_module_->queryMPD(); }
inline void Context::fetchState() const { mpd_module_->fetchState(); }
inline void Context::emit() const { mpd_module_->emit(); }
} // namespace detail

View File

@ -54,6 +54,8 @@ class Network : public ALabel {
struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sock_ = nullptr;
struct nl_sock* ev_sock_ = nullptr;
int efd_;
int ev_fd_;
int nl80211_id_;
std::mutex mutex_;

View File

@ -0,0 +1,24 @@
#pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Clock : public ALabel {
public:
Clock(const std::string&, const Json::Value&);
~Clock() = default;
auto update() -> void;
private:
util::SleeperThread thread_;
};
} // namespace waybar::modules

30
include/modules/sndio.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <sndio.h>
#include <vector>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Sndio : public ALabel {
public:
Sndio(const std::string&, const Json::Value&);
~Sndio();
auto update() -> void;
auto set_desc(struct sioctl_desc *, unsigned int) -> void;
auto put_val(unsigned int, unsigned int) -> void;
bool handleScroll(GdkEventScroll *);
bool handleToggle(GdkEventButton* const&);
private:
auto connect_to_sndio() -> void;
util::SleeperThread thread_;
struct sioctl_hdl *hdl_;
std::vector<struct pollfd> pfds_;
unsigned int addr_;
unsigned int volume_, old_volume_, maxval_;
bool muted_;
};
} // namespace waybar::modules

View File

@ -0,0 +1,28 @@
#pragma once
#include <fmt/format.h>
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
#include "util/json.hpp"
namespace waybar::modules::sway {
class Language : public ALabel, public sigc::trackable {
public:
Language(const std::string& id, const Json::Value& config);
~Language() = default;
auto update() -> void;
private:
void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&);
std::string lang_;
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
};
} // namespace waybar::modules::sway

View File

@ -70,7 +70,7 @@ class Task
std::string title_;
std::string app_id_;
uint32_t state_;
uint32_t state_ = 0;
private:
std::string repr() const;

View File

@ -20,7 +20,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y
*full-at*: ++
typeof: integer ++
Define the max percentage of the battery, useful for an old battery, e.g. 96
Define the max percentage of the battery, for when you've set the battery to stop charging at a lower level to save it. For example, if you've set the battery to stop at 80% that will become the new 100%.
*interval*: ++
typeof: integer ++

View File

@ -22,6 +22,12 @@ Addressed by *custom/<name>*
The path to a script, which determines if the script in *exec* should be executed.
*exec* will be executed if the exit code of *exec-if* equals 0.
*exec-on-event*: ++
typeof: bool ++
default: true ++
If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after
executing the event command.
*return-type*: ++
typeof: string ++
See *return-type*

View File

@ -31,6 +31,10 @@ Addressed by *disk*
typeof: integer ++
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of disk utilization states which get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.

View File

@ -20,6 +20,10 @@ Addressed by *mpd*
typeof: integer ++
The port MPD listens to. If empty, use the default port.
*password*: ++
typeof: string ++
The password required to connect to the MPD server. If empty, no password is sent to MPD.
*interval*: ++
typeof: integer++
default: 5 ++
@ -148,6 +152,10 @@ Addressed by *mpd*
*{totalTime}*: The length of the current song. To format as a date/time (see example configuration)
*{songPosition}*: The position of the current song.
*{queueLength}*: The length of the current queue.
*{stateIcon}*: The icon corresponding the playing or paused status of the player (see *state-icons* option)
*{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option)

View File

@ -17,6 +17,10 @@ Addressed by *river/tags*
default: 9 ++
The number of tags that should be displayed.
*tag-labels*: ++
typeof: array ++
The label to display for each tag.
# EXAMPLE
```

83
man/waybar-sndio.5.scd Normal file
View File

@ -0,0 +1,83 @@
waybar-sndio(5)
# NAME
waybar - sndio module
# DESCRIPTION
The *sndio* module displays the current volume reported by sndio(7).
Additionally, you can control the volume by scrolling *up* or *down* while the
cursor is over the module, and clicking on the module toggles mute.
# CONFIGURATION
*format*: ++
typeof: string ++
default: {volume}% ++
The format for how information should be displayed.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*scroll-step*: ++
typeof: int ++
default: 5 ++
The speed in which to change the volume when scrolling.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
This replaces the default behaviour of toggling mute.
*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.
This replaces the default behaviour of volume control.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
This replaces the default behaviour of volume control.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
# FORMAT REPLACEMENTS
*{volume}*: Volume in percentage.
*{raw_value}*: Volume as value reported by sndio.
# EXAMPLES
```
"sndio": {
"format": "{raw_value} 🎜",
"scroll-step": 3
}
```
# STYLE
- *#sndio*
- *#sndio.muted*

View File

@ -13,7 +13,7 @@ apply a class when the value matches the declared state value.
Each class gets activated when the current capacity is equal or below the configured *<value>*.
- Also each state can have its own *format*.
Those con be configured via *format-<name>*.
Those can be configured via *format-<name>*.
Or if you want to differentiate a bit more even as *format-<status>-<state>*.
# EXAMPLE

View File

@ -0,0 +1,72 @@
waybar-sway-language(5)
# NAME
waybar - sway language module
# DESCRIPTION
The *language* module displays the current keyboard layout in Sway
# CONFIGURATION
Addressed by *sway/language*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} data gets inserted.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*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.
# EXAMPLES
```
"sway/language": {
"format": "{}",
"max-length": 50
}
```
# STYLE
- *#language*

View File

@ -50,6 +50,11 @@ Addressed by *temperature*
typeof: array ++
Based on the current temperature (Celsius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*.
*tooltip-format*: ++
typeof: string ++
default: {temperatureC}°C ++
The format for the tooltip
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.

View File

@ -32,6 +32,11 @@ Addressed by *wlr/taskbar*
default: 16 ++
The size of the icon.
*markup*: ++
typeof: bool ++
default: false ++
If set to true, pango markup will be accepted in format and tooltip-format.
*tooltip*: ++
typeof: bool ++
default: true ++
@ -78,9 +83,10 @@ Addressed by *wlr/taskbar*
# CLICK ACTIONS
*activate*: Bring the application into foreground.
*minimize*: Minimize the application.
*maximize*: Maximize the application.
*fullscreen*: Set the application to fullscreen.
*minimize*: Toggle application's minimized state.
*minimize-raise*: Bring the application into foreground or toggle its minimized state.
*maximize*: Toggle application's maximized state.
*fullscreen*: Toggle application's fullscreen state.
*close*: Close the application.
# EXAMPLES

View File

@ -14,6 +14,7 @@ Valid locations for this file are:
- *~/.config/waybar/config*
- *~/waybar/config*
- */etc/xdg/waybar/config*
- *@sysconfdir@/xdg/waybar/config*
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config
Also a minimal example configuration can be found on the at the bottom of this man page.

View File

@ -1,7 +1,8 @@
project(
'waybar', 'cpp', 'c',
version: '0.9.3',
version: '0.9.5',
license: 'MIT',
meson_version: '>= 0.49.0',
default_options : [
'cpp_std=c++17',
'buildtype=release',
@ -79,7 +80,7 @@ is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdlog_dep'])
spdlog = dependency('spdlog', version : ['>=1.8.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
@ -94,11 +95,28 @@ libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found()
if not compiler.has_function('sioctl_open', prefix: '#include <sndio.h>', dependencies: libsndio)
if get_option('sndio').enabled()
error('libsndio is too old, required >=1.7.0')
else
warning('libsndio is too old, required >=1.7.0')
libsndio = dependency('', required: false)
endif
endif
endif
gtk_layer_shell = dependency('gtk-layer-shell-0',
required: get_option('gtk-layer-shell'),
fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep'])
systemd = dependency('systemd', required: get_option('systemd'))
tz_dep = dependency('date', default_options : [ 'use_system_tzdb=true' ], modules : [ 'date::date', 'date::date-tz' ], fallback: [ 'date', 'tz_dep' ])
tz_dep = dependency('date',
required: false,
default_options : [ 'use_system_tzdb=true' ],
modules : [ 'date::date', 'date::date-tz' ],
fallback: [ 'date', 'tz_dep' ])
prefix = get_option('prefix')
sysconfdir = get_option('sysconfdir')
@ -122,7 +140,6 @@ src_files = files(
'src/factory.cpp',
'src/AModule.cpp',
'src/ALabel.cpp',
'src/modules/clock.cpp',
'src/modules/custom.cpp',
'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp',
@ -157,6 +174,7 @@ add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [
'src/modules/sway/ipc/client.cpp',
'src/modules/sway/mode.cpp',
'src/modules/sway/language.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
]
@ -198,13 +216,19 @@ endif
if libmpdclient.found()
add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')
src_files += 'src/modules/mpd.cpp'
src_files += 'src/modules/mpd/mpd.cpp'
src_files += 'src/modules/mpd/state.cpp'
endif
if gtk_layer_shell.found()
add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp')
endif
if libsndio.found()
add_project_arguments('-DHAVE_LIBSNDIO', language: 'cpp')
src_files += 'src/modules/sndio.cpp'
endif
if get_option('rfkill').enabled()
if is_linux
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
@ -215,6 +239,13 @@ if get_option('rfkill').enabled()
endif
endif
if tz_dep.found()
add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')
src_files += 'src/modules/clock.cpp'
else
src_files += 'src/modules/simpleclock.cpp'
endif
subdir('protocol')
executable(
@ -239,6 +270,7 @@ executable(
libepoll,
libmpdclient,
gtk_layer_shell,
libsndio,
tz_dep
],
include_directories: [include_directories('include')],
@ -256,9 +288,20 @@ scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_opti
if scdoc.found()
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
sh = find_program('sh', native: true)
main_manpage = configure_file(
input: 'man/waybar.5.scd.in',
output: 'waybar.5.scd',
configuration: {
'sysconfdir': join_paths(prefix, sysconfdir)
}
)
main_manpage_path = join_paths(meson.build_root(), '@0@'.format(main_manpage))
mandir = get_option('mandir')
man_files = [
'waybar.5.scd',
main_manpage_path,
'waybar-backlight.5.scd',
'waybar-battery.5.scd',
'waybar-clock.5.scd',
@ -279,16 +322,21 @@ if scdoc.found()
'waybar-states.5.scd',
'waybar-wlr-taskbar.5.scd',
'waybar-bluetooth.5.scd',
'waybar-sndio.5.scd',
]
foreach filename : man_files
topic = filename.split('.')[-3].split('/')[-1]
section = filename.split('.')[-2]
foreach file : man_files
path = '@0@'.format(file)
basename = path.split('/')[-1]
topic = basename.split('.')[-3]
section = basename.split('.')[-2]
output = '@0@.@1@'.format(topic, section)
custom_target(
output,
input: 'man/@0@'.format(filename),
# drops the 'man' if `path` is an absolute path
input: join_paths('man', path),
output: output,
command: [
sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output)

View File

@ -8,3 +8,4 @@ option('man-pages', type: 'feature', value: 'auto', description: 'Generate and i
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
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('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')

View File

@ -25,7 +25,7 @@
THIS SOFTWARE.
</copyright>
<interface name="zwlr_foreign_toplevel_manager_v1" version="2">
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
<description summary="list and control opened apps">
The purpose of this protocol is to enable the creation of taskbars
and docks by providing them with a list of opened applications and
@ -68,7 +68,7 @@
</event>
</interface>
<interface name="zwlr_foreign_toplevel_handle_v1" version="2">
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
<description summary="an opened toplevel">
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
window. Each app may have multiple opened toplevels.
@ -255,5 +255,16 @@
actually changes, this will be indicated by the state event.
</description>
</request>
<!-- Version 3 additions -->
<event name="parent" since="3">
<description summary="parent change">
This event is emitted whenever the parent of the toplevel changes.
No event is emitted when the parent handle is destroyed by the client.
</description>
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
</event>
</interface>
</protocol>

View File

@ -6,7 +6,7 @@
// Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "sway/language", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration
// "sway/workspaces": {
// "disable-scroll": true,
@ -27,7 +27,7 @@
"format": "<span style=\"italic\">{}</span>"
},
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ",
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ ",
"format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A",

View File

@ -41,19 +41,19 @@ window#waybar.chromium {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
border-bottom: 3px solid transparent;
/* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button:hover {
background: rgba(0, 0, 0, 0.2);
box-shadow: inherit;
border-bottom: 3px solid #ffffff;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.focused {
background-color: #64727D;
border-bottom: 3px solid #ffffff;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.urgent {
@ -83,6 +83,21 @@ window#waybar.chromium {
color: #ffffff;
}
#window,
#workspaces {
margin: 0 4px;
}
/* If workspaces is the leftmost module, omit left margin */
.modules-left > widget:first-child > #workspaces {
margin-left: 0;
}
/* If workspaces is the rightmost module, omit right margin */
.modules-right > widget:last-child > #workspaces {
margin-right: 0;
}
#clock {
background-color: #64727D;
}
@ -200,3 +215,11 @@ label:focus {
#mpd.paused {
background-color: #51a37a;
}
#language {
background: #00b093;
color: #740864;
padding: 0 5px;
margin: 0 5px;
min-width: 16px;
}

View File

@ -6,6 +6,7 @@ After=graphical-session.target
[Service]
ExecStart=@prefix@/bin/waybar
Restart=on-failure
[Install]
WantedBy=graphical-session.target

View File

@ -60,14 +60,14 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts, uint16_t max) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
std::string _alt = "default";
for (const auto& alt : alts) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
format_icons = format_icons[alt];
_alt = alt;
break;
} else {
format_icons = format_icons["default"];
}
}
format_icons = format_icons[_alt];
}
if (format_icons.isArray()) {
auto size = format_icons.size();

View File

@ -44,9 +44,9 @@ bool AModule::handleToggle(GdkEventButton* const& e) {
format = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && e->button == 3) {
format = config_["on-click-right"].asString();
} else if (config_["on-click-forward"].isString() && e->button == 8) {
} else if (config_["on-click-backward"].isString() && e->button == 8) {
format = config_["on-click-backward"].asString();
} else if (config_["on-click-backward"].isString() && e->button == 9) {
} else if (config_["on-click-forward"].isString() && e->button == 9) {
format = config_["on-click-forward"].asString();
}
if (!format.empty()) {

View File

@ -2,18 +2,364 @@
#include <gtk-layer-shell.h>
#endif
#include <spdlog/spdlog.h>
#include <type_traits>
#include "bar.hpp"
#include "client.hpp"
#include "factory.hpp"
#include <spdlog/spdlog.h>
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
namespace waybar {
static constexpr const char* MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules";
static constexpr const char* MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules";
static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char* SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
#ifdef HAVE_GTK_LAYER_SHELL
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
output_name_ = output.name;
// this has to be executed before GtkWindow.realize
gtk_layer_init_for_window(window_.gobj());
gtk_layer_set_keyboard_interactivity(window.gobj(), FALSE);
gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj());
gtk_layer_set_namespace(window_.gobj(), "waybar");
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure));
}
void setExclusiveZone(bool enable) override {
if (enable) {
gtk_layer_auto_exclusive_zone_enable(window_.gobj());
} else {
gtk_layer_set_exclusive_zone(window_.gobj(), 0);
}
}
void setMargins(const struct bar_margins& margins) override {
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom);
}
void setLayer(bar_layer value) override {
auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM;
if (value == bar_layer::TOP) {
layer = GTK_LAYER_SHELL_LAYER_TOP;
} else if (value == bar_layer::OVERLAY) {
layer = GTK_LAYER_SHELL_LAYER_OVERLAY;
}
gtk_layer_set_layer(window_.gobj(), layer);
}
void setPosition(const std::string_view& position) override {
auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM;
vertical_ = false;
if (position == "bottom") {
unanchored = GTK_LAYER_SHELL_EDGE_TOP;
} else if (position == "left") {
unanchored = GTK_LAYER_SHELL_EDGE_RIGHT;
vertical_ = true;
} else if (position == "right") {
vertical_ = true;
unanchored = GTK_LAYER_SHELL_EDGE_LEFT;
}
for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT,
GTK_LAYER_SHELL_EDGE_RIGHT,
GTK_LAYER_SHELL_EDGE_TOP,
GTK_LAYER_SHELL_EDGE_BOTTOM}) {
gtk_layer_set_anchor(window_.gobj(), edge, unanchored != edge);
}
}
void setSize(uint32_t width, uint32_t height) override {
width_ = width;
height_ = height;
window_.set_size_request(width_, height_);
};
private:
Gtk::Window& window_;
std::string output_name_;
uint32_t width_;
uint32_t height_;
bool vertical_ = false;
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
* Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell
* code. This event handler only updates stored size of the window and prints some warnings.
*
* Note: forced resizing to a window smaller than required by GTK would not work with
* gtk-layer-shell.
*/
if (vertical_) {
if (width_ > 1 && ev->width > static_cast<int>(width_)) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
} else {
if (height_ > 1 && ev->height > static_cast<int>(height_)) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
}
width_ = ev->width;
height_ = ev->height;
spdlog::info(BAR_SIZE_MSG, width_, height_, output_name_);
}
};
#endif
struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
RawSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
output_name_ = output.name;
window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onMap));
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &RawSurfaceImpl::onConfigure));
if (window.get_realized()) {
onRealize();
}
}
void setExclusiveZone(bool enable) override {
exclusive_zone_ = enable;
if (layer_surface_) {
auto zone = 0;
if (enable) {
// exclusive zone already includes margin for anchored edge,
// only opposite margin should be added
if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
zone += width_;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
} else {
zone += height_;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
}
}
spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_.get(), zone);
}
}
void setLayer(bar_layer layer) override {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
if (layer == bar_layer::TOP) {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
} else if (layer == bar_layer::OVERLAY) {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
}
// updating already mapped window
if (layer_surface_) {
if (zwlr_layer_surface_v1_get_version(layer_surface_.get()) >=
ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) {
zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_);
} else {
spdlog::warn("Unable to change layer: layer-shell implementation is too old");
}
}
}
void setMargins(const struct bar_margins& margins) override {
margins_ = margins;
// updating already mapped window
if (layer_surface_) {
zwlr_layer_surface_v1_set_margin(
layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
}
}
void setPosition(const std::string_view& position) override {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (position == "bottom") {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (position == "left") {
anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (position == "right") {
anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
// updating already mapped window
if (layer_surface_) {
zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
}
}
void setSize(uint32_t width, uint32_t height) override {
configured_width_ = width_ = width;
configured_height_ = height_ = height;
// layer_shell.configure handler should update exclusive zone if size changes
window_.set_size_request(width, height);
};
void commit() override {
if (surface_) {
wl_surface_commit(surface_);
}
}
private:
constexpr static uint8_t VERTICAL_ANCHOR =
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
constexpr static uint8_t HORIZONTAL_ANCHOR =
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
template <auto fn>
using deleter_fn = std::integral_constant<decltype(fn), fn>;
using layer_surface_ptr =
std::unique_ptr<zwlr_layer_surface_v1, deleter_fn<zwlr_layer_surface_v1_destroy>>;
Gtk::Window& window_;
std::string output_name_;
uint32_t configured_width_ = 0;
uint32_t configured_height_ = 0;
uint32_t width_ = 0;
uint32_t height_ = 0;
uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
bool exclusive_zone_ = true;
struct bar_margins margins_;
zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
struct wl_output* output_ = nullptr; // owned by GTK
struct wl_surface* surface_ = nullptr; // owned by GTK
layer_surface_ptr layer_surface_;
void onRealize() {
auto gdk_window = window_.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void onMap(GdkEventAny* ev) {
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = onSurfaceConfigure,
.closed = onSurfaceClosed,
};
auto client = Client::inst();
auto gdk_window = window_.get_window()->gobj();
surface_ = gdk_wayland_window_get_wl_surface(gdk_window);
layer_surface_.reset(zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface_, output_, layer_, "waybar"));
zwlr_layer_surface_v1_add_listener(layer_surface_.get(), &layer_surface_listener, this);
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_.get(), false);
zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
zwlr_layer_surface_v1_set_margin(
layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
setSurfaceSize(width_, height_);
setExclusiveZone(exclusive_zone_);
commit();
wl_display_roundtrip(client->wl_display);
}
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
*
* Prefer configured size if it's non-default.
* If the size is not set and the window is smaller than requested by GTK, request resize from
* layer surface.
*/
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ > 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (configured_height_ > 1) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ > 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (configured_width_ > 1) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
commit();
}
}
void setSurfaceSize(uint32_t width, uint32_t height) {
/* If the client is anchored to two opposite edges, layer_surface.configure will return
* size without margins for the axis.
* layer_surface.set_size, however, expects size with margins for the anchored axis.
* This is not specified by wlr-layer-shell and based on actual behavior of sway.
*
* If the size for unanchored axis is not set (0), change request to 1 to avoid automatic
* assignment by the compositor.
*/
if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
width = width > 0 ? width : 1;
if (height > 1) {
height += margins_.top + margins_.bottom;
}
} else {
height = height > 0 ? height : 1;
if (width > 1) {
width += margins_.right + margins_.left;
}
}
spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_);
zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height);
}
static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial,
uint32_t width, uint32_t height) {
auto o = static_cast<RawSurfaceImpl*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window_.set_size_request(o->width_, o->height_);
o->window_.resize(o->width_, o->height_);
o->setExclusiveZone(o->exclusive_zone_);
spdlog::info(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output_name_);
o->commit();
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) {
auto o = static_cast<RawSurfaceImpl*>(data);
o->layer_surface_.reset();
}
};
}; // namespace waybar
waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
: output(w_output),
config(w_config),
surface(nullptr),
window{Gtk::WindowType::WINDOW_TOPLEVEL},
layer_surface_(nullptr),
anchor_(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP),
layer_{bar_layer::BOTTOM},
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
@ -25,26 +371,15 @@ 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["position"].asString());
if (config["position"] == "right" || config["position"] == "left") {
height_ = 0;
width_ = 1;
if (config["layer"] == "top") {
layer_ = bar_layer::TOP;
} else if (config["layer"] == "overlay") {
layer_ = bar_layer::OVERLAY;
}
height_ = config["height"].isUInt() ? config["height"].asUInt() : height_;
width_ = config["width"].isUInt() ? config["width"].asUInt() : width_;
if (config["position"] == "bottom") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (config["position"] == "left") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (config["position"] == "right") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
auto position = config["position"].asString();
if (position == "right" || position == "left") {
left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
@ -52,6 +387,15 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
vertical = true;
}
left_.get_style_context()->add_class("modules-left");
center_.get_style_context()->add_class("modules-center");
right_.get_style_context()->add_class("modules-right");
uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0;
uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0;
struct bar_margins margins_;
if (config["margin-top"].isInt() || config["margin-right"].isInt() ||
config["margin-bottom"].isInt() || config["margin-left"].isInt()) {
margins_ = {
@ -98,207 +442,62 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
}
#ifdef HAVE_GTK_LAYER_SHELL
use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls_) {
initGtkLayerShell();
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMapGLS));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigureGLS));
}
#endif
if (!use_gls_) {
window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure));
}
window.set_size_request(width_, height_);
setupWidgets();
if (!use_gls_ && window.get_realized()) {
onRealize();
}
window.show_all();
}
#ifdef HAVE_GTK_LAYER_SHELL
void waybar::Bar::initGtkLayerShell() {
auto gtk_window = window.gobj();
// this has to be executed before GtkWindow.realize
gtk_layer_init_for_window(gtk_window);
gtk_layer_set_keyboard_interactivity(gtk_window, FALSE);
auto layer = config["layer"] == "top" ? GTK_LAYER_SHELL_LAYER_TOP : GTK_LAYER_SHELL_LAYER_BOTTOM;
gtk_layer_set_layer(gtk_window, layer);
gtk_layer_set_monitor(gtk_window, output->monitor->gobj());
gtk_layer_set_namespace(gtk_window, "waybar");
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_TOP, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom);
if (width_ > 1 && height_ > 1) {
/* configure events are not emitted if the bar is using initial size */
setExclusiveZone(width_, height_);
}
}
void waybar::Bar::onConfigureGLS(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
* Actual resizing is done within the gtk-layer-shell code; the only remaining action is to apply
* exclusive zone.
* gtk_layer_auto_exclusive_zone_enable() could handle even that, but at the cost of ignoring
* margins on unanchored edge.
*
* Note: forced resizing to a window smaller than required by GTK would not work with
* gtk-layer-shell.
*/
if (vertical) {
if (width_ > 1 && ev->width > static_cast<int>(width_)) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
} else {
if (!vertical && height_ > 1 && ev->height > static_cast<int>(height_)) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
}
width_ = ev->width;
height_ = ev->height;
spdlog::info(BAR_SIZE_MSG, width_, height_, output->name);
setExclusiveZone(width_, height_);
}
void waybar::Bar::onMapGLS(GdkEventAny* ev) {
/*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
*/
auto gdk_window = window.get_window();
surface = gdk_wayland_window_get_wl_surface(gdk_window->gobj());
}
#endif
void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
*
* Prefer configured size if it's non-default.
* If the size is not set and the window is smaller than requested by GTK, request resize from
* layer surface.
*/
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ > 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (config["height"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ > 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (config["width"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
}
}
void waybar::Bar::onRealize() {
auto gdk_window = window.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void waybar::Bar::onMap(GdkEventAny* ev) {
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
auto client = waybar::Client::inst();
// owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj());
auto layer =
config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
layer_surface_ = zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface, wl_output, layer, "waybar");
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false);
zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_);
zwlr_layer_surface_v1_set_margin(
layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left);
setSurfaceSize(width_, height_);
setExclusiveZone(width_, height_);
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure,
.closed = layerSurfaceHandleClosed,
};
zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this);
wl_surface_commit(surface);
wl_display_roundtrip(client->wl_display);
}
void waybar::Bar::setExclusiveZone(uint32_t width, uint32_t height) {
auto zone = 0;
if (visible) {
// exclusive zone already includes margin for anchored edge,
// only opposite margin should be added
if (vertical) {
zone += width;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
} else {
zone += height;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
}
}
spdlog::debug("Set exclusive zone {} for output {}", zone, output->name);
#ifdef HAVE_GTK_LAYER_SHELL
if (use_gls_) {
gtk_layer_set_exclusive_zone(window.gobj(), zone);
bool use_gls = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls) {
surface_impl_ = std::make_unique<GLSSurfaceImpl>(window, *output);
} else
#endif
{
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone);
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
}
surface_impl_->setLayer(layer_);
surface_impl_->setExclusiveZone(true);
surface_impl_->setMargins(margins_);
surface_impl_->setPosition(position);
surface_impl_->setSize(width, height);
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
setupWidgets();
window.show_all();
if (spdlog::should_log(spdlog::level::debug)) {
// Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.
char* gtk_tree = gtk_style_context_to_string(
window.get_style_context()->gobj(),
(GtkStyleContextPrintFlags)(GTK_STYLE_CONTEXT_PRINT_RECURSE |
GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE));
spdlog::debug("GTK widget tree:\n{}", gtk_tree);
g_free(gtk_tree);
}
}
void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) {
/* If the client is anchored to two opposite edges, layer_surface.configure will return
* size without margins for the axis.
* layer_surface.set_size, however, expects size with margins for the anchored axis.
* This is not specified by wlr-layer-shell and based on actual behavior of sway.
void waybar::Bar::onMap(GdkEventAny*) {
/*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
*/
if (vertical && height > 1) {
height += margins_.top + margins_.bottom;
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
}
if (!vertical && width > 1) {
width += margins_.right + margins_.left;
void waybar::Bar::setVisible(bool value) {
visible = value;
if (!visible) {
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_);
}
spdlog::debug("Set surface size {}x{} for output {}", width, height, output->name);
zwlr_layer_surface_v1_set_size(layer_surface_, width, height);
surface_impl_->setExclusiveZone(visible);
surface_impl_->commit();
}
void waybar::Bar::toggle() { setVisible(!visible); }
// Converting string to button code rn as to avoid doing it later
void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
if (config.isMember(module_name)) {
@ -360,50 +559,6 @@ void waybar::Bar::handleSignal(int signal) {
}
}
void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surface_v1* surface,
uint32_t serial, uint32_t width, uint32_t height) {
auto o = static_cast<waybar::Bar*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_);
o->setExclusiveZone(width, height);
spdlog::info(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output->name);
wl_surface_commit(o->surface);
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) {
auto o = static_cast<waybar::Bar*>(data);
if (o->layer_surface_) {
zwlr_layer_surface_v1_destroy(o->layer_surface_);
o->layer_surface_ = nullptr;
}
o->modules_left_.clear();
o->modules_center_.clear();
o->modules_right_.clear();
}
auto waybar::Bar::toggle() -> void {
visible = !visible;
if (!visible) {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
} else {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
}
setExclusiveZone(width_, height_);
if (!use_gls_) {
wl_surface_commit(surface);
}
}
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
if (config[pos].isArray()) {
for (const auto& name : config[pos]) {

View File

@ -1,10 +1,15 @@
#include "client.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <iostream>
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp"
#include "util/json.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
waybar::Client *waybar::Client::inst() {
static auto c = new Client();
@ -32,6 +37,8 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint
const char *interface, uint32_t version) {
auto client = static_cast<Client *>(data);
if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
// limit version to a highest supported by the client protocol file
version = std::min<uint32_t>(version, zwlr_layer_shell_v1_interface.version);
client->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>(
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version));
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
@ -53,9 +60,9 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {},
.logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {},
.done = [](void *, struct zxdg_output_v1 *) {},
.done = &handleOutputDone,
.name = &handleOutputName,
.description = [](void *, struct zxdg_output_v1 *, const char *) {},
.description = &handleOutputDescription,
};
// owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
@ -66,18 +73,20 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() && output_conf.asString() == output.name) {
if (output_conf.isString() &&
(output_conf.asString() == output.name || output_conf.asString() == output.identifier)) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output_name = config["output"].asString();
if (!config_output_name.empty()) {
if (config_output_name.substr(0, 1) == "!") {
return config_output_name.substr(1) != output.name;
auto config_output = config["output"].asString();
if (!config_output.empty()) {
if (config_output.substr(0, 1) == "!") {
return config_output.substr(1) != output.name &&
config_output.substr(1) != output.identifier;
}
return config_output_name == output.name;
return config_output == output.name || config_output == output.identifier;
}
}
@ -107,16 +116,12 @@ std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &
return configs;
}
void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/,
const char *name) {
void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) {
auto client = waybar::Client::inst();
try {
auto &output = client->getOutput(data);
output.name = name;
spdlog::debug("Output detected: {} ({} {})",
name,
output.monitor->get_manufacturer(),
output.monitor->get_model());
spdlog::debug("Output detection done: {} ({})", output.name, output.identifier);
auto configs = client->getOutputConfigs(output);
if (configs.empty()) {
output.xdg_output.reset();
@ -134,6 +139,32 @@ void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 *
}
}
void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/,
const char *name) {
auto client = waybar::Client::inst();
try {
auto &output = client->getOutput(data);
output.name = name;
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/,
const char *description) {
auto client = waybar::Client::inst();
try {
auto & output = client->getOutput(data);
const char *open_paren = strrchr(description, '(');
// Description format: "identifier (name)"
size_t identifier_length = open_paren - description;
output.identifier = std::string(description, identifier_length - 1);
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
void waybar::Client::handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor) {
auto &output = outputs_.emplace_back();
output.monitor = monitor;
@ -162,6 +193,7 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
"$XDG_CONFIG_HOME/waybar/config",
"$HOME/.config/waybar/config",
"$HOME/waybar/config",
"/etc/xdg/waybar/config",
SYSCONFDIR "/xdg/waybar/config",
"./resources/config",
})
@ -170,6 +202,7 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
"$XDG_CONFIG_HOME/waybar/style.css",
"$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css",
"/etc/xdg/waybar/style.css",
SYSCONFDIR "/xdg/waybar/style.css",
"./resources/style.css",
})
@ -253,7 +286,8 @@ int waybar::Client::main(int argc, char *argv[]) {
if (!log_level.empty()) {
spdlog::set_level(spdlog::level::from_str(log_level));
}
gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE);
gtk_app = Gtk::Application::create(
argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE);
gdk_display = Gdk::Display::get_default();
if (!gdk_display) {
throw std::runtime_error("Can't find display");
@ -269,10 +303,9 @@ int waybar::Client::main(int argc, char *argv[]) {
gtk_app->hold();
gtk_app->run();
bars.clear();
zxdg_output_manager_v1_destroy(xdg_output_manager);
zwlr_layer_shell_v1_destroy(layer_shell);
zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager);
wl_registry_destroy(registry);
wl_display_disconnect(wl_display);
return 0;
}
void waybar::Client::reset() {
gtk_app->quit();
}

View File

@ -22,6 +22,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sway/window") {
return new waybar::modules::sway::Window(id, bar_, config_[name]);
}
if (ref == "sway/language") {
return new waybar::modules::sway::Language(id, config_[name]);
}
#endif
#ifdef HAVE_WLR
if (ref == "wlr/taskbar") {
@ -76,6 +79,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "mpd") {
return new waybar::modules::MPD(id, config_[name]);
}
#endif
#ifdef HAVE_LIBSNDIO
if (ref == "sndio") {
return new waybar::modules::Sndio(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);

View File

@ -8,6 +8,7 @@
std::mutex reap_mtx;
std::list<pid_t> reap;
volatile bool reload;
void* signalThread(void* args) {
int err, signum;
@ -70,12 +71,19 @@ void startSignalThread(void) {
int main(int argc, char* argv[]) {
try {
auto client = waybar::Client::inst();
std::signal(SIGUSR1, [](int /*signal*/) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->toggle();
}
});
std::signal(SIGUSR2, [](int /*signal*/) {
spdlog::info("Reloading...");
reload = true;
waybar::Client::inst()->reset();
});
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) {
@ -85,7 +93,12 @@ int main(int argc, char* argv[]) {
}
startSignalThread();
auto ret = client->main(argc, argv);
auto ret = 0;
do {
reload = false;
ret = client->main(argc, argv);
} while (reload);
delete client;
return ret;
} catch (const std::exception& e) {

View File

@ -4,46 +4,82 @@
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60) {
getBatteries();
fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) {
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
for (auto const& bat : batteries_) {
auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
if (wd != -1) {
wds_.push_back(wd);
global_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (global_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
// Watch the directory for any added or removed batteries
global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE);
if (global_watch < 0) {
throw std::runtime_error("Could not watch for battery plug/unplug");
}
refreshBatteries();
worker();
}
waybar::modules::Battery::~Battery() {
for (auto wd : wds_) {
inotify_rm_watch(fd_, wd);
std::lock_guard<std::mutex> guard(battery_list_mutex_);
if (global_watch >= 0) {
inotify_rm_watch(global_watch_fd_, global_watch);
}
close(fd_);
close(global_watch_fd_);
for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) {
auto watch_id = (*it).second;
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
}
batteries_.erase(it);
}
close(battery_watch_fd_);
}
void waybar::modules::Battery::worker() {
thread_timer_ = [this] {
// Make sure we eventually update the list of batteries even if we miss an
// inotify event for some reason
refreshBatteries();
dp.emit();
thread_timer_.sleep_for(interval_);
};
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
int nbytes = read(battery_watch_fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
// TODO: don't stop timer for now since there is some bugs :?
// thread_timer_.stop();
dp.emit();
};
thread_battery_update_ = [this] {
struct inotify_event event = {0};
int nbytes = read(global_watch_fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
refreshBatteries();
dp.emit();
};
}
void waybar::modules::Battery::getBatteries() {
void waybar::modules::Battery::refreshBatteries() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
// Mark existing list of batteries as not necessarily found
std::map<fs::path, bool> check_map;
for (auto const& bat : batteries_) {
check_map[bat.first] = false;
}
try {
for (auto& node : fs::directory_iterator(data_dir_)) {
if (!fs::is_directory(node)) {
@ -58,7 +94,17 @@ void waybar::modules::Battery::getBatteries() {
std::ifstream(node.path() / "type") >> type;
if (!type.compare("Battery")){
batteries_.push_back(node.path());
check_map[node.path()] = true;
auto search = batteries_.find(node.path());
if (search == batteries_.end()) {
// We've found a new battery save it and start listening for events
auto event_path = (node.path() / "uevent");
auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);
if (wd < 0) {
throw std::runtime_error("Could not watch events for " + node.path().string());
}
batteries_[node.path()] = wd;
}
}
}
auto adap_defined = config_["adapter"].isString();
@ -76,22 +122,33 @@ void waybar::modules::Battery::getBatteries() {
}
throw std::runtime_error("No batteries.");
}
// Remove any batteries that are no longer present and unwatch them
for (auto const& check : check_map) {
if (!check.second) {
auto watch_id = batteries_[check.first];
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
}
batteries_.erase(check.first);
}
}
}
const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos() const {
const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
try {
uint16_t total = 0;
uint32_t total_power = 0; // μW
uint32_t total_energy = 0; // μWh
uint32_t total_energy_full = 0;
std::string status = "Unknown";
for (auto const& bat : batteries_) {
uint16_t capacity;
for (auto const& item : batteries_) {
auto bat = item.first;
uint32_t power_now;
uint32_t energy_full;
uint32_t energy_now;
std::string _status;
std::ifstream(bat / "capacity") >> capacity;
std::ifstream(bat / "status") >> _status;
auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now";
std::ifstream(bat / rate_path) >> power_now;
@ -102,7 +159,6 @@ const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos
if (_status != "Unknown") {
status = _status;
}
total += capacity;
total_power += power_now;
total_energy += energy_now;
total_energy_full += energy_full;
@ -119,19 +175,33 @@ const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos
time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && total_power != 0) {
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
if (time_remaining > 0.0f) {
// If we've turned positive it means the battery is past 100% and so
// just report that as no time remaining
time_remaining = 0.0f;
}
uint16_t capacity = total / batteries_.size();
}
float capacity = ((float)total_energy * 100.0f / (float) total_energy_full);
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
if (full_at < 100) {
capacity = 100.f * capacity / full_at;
if (capacity > full_at) {
capacity = full_at;
}
}
if (capacity > 100.f) {
// This can happen when the battery is calibrating and goes above 100%
// Handle it gracefully by clamping at 100%
capacity = 100.f;
}
return {capacity, time_remaining, status};
uint8_t cap = round(capacity);
if (cap == 100) {
// If we've reached 100% just mark as full as some batteries can stay
// stuck reporting they're still charging but not yet done
status = "Full";
}
return {cap, time_remaining, status};
} catch (const std::exception& e) {
spdlog::error("Battery: {}", e.what());
return {0, 0, "Unknown"};
@ -158,6 +228,10 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
uint16_t full_hours = static_cast<uint16_t>(hoursRemaining);
uint16_t minutes = static_cast<uint16_t>(60 * (hoursRemaining - full_hours));
auto format = std::string("{H} h {M} min");
if (full_hours == 0 && minutes == 0) {
// Migh as well not show "0h 0min"
return "";
}
if (config_["format-time"].isString()) {
format = config_["format-time"].asString();
}

View File

@ -85,13 +85,11 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
return true;
}
auto nr_zones = config_["timezones"].size();
int new_idx = time_zone_idx_ + ((dir == SCROLL_DIR::UP) ? 1 : -1);
if (new_idx < 0) {
time_zone_idx_ = nr_zones - 1;
} else if (new_idx >= nr_zones) {
time_zone_idx_ = 0;
if (dir == SCROLL_DIR::UP) {
size_t new_idx = time_zone_idx_ + 1;
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
} else {
time_zone_idx_ = new_idx;
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
}
auto zone_name = config_["timezones"][time_zone_idx_];
if (!zone_name.isString() || zone_name.empty()) {

View File

@ -15,8 +15,19 @@ auto waybar::modules::Cpu::update() -> void {
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
label_.set_markup(fmt::format(format_, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
getState(cpu_usage);
auto format = format_;
auto state = getState(cpu_usage);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
}
// Call parent update
ALabel::update();
}

View File

@ -50,7 +50,6 @@ void waybar::modules::Custom::continuousWorker() {
thread_ = [this, cmd] {
char* buff = nullptr;
size_t len = 0;
bool restart = false;
if (getline(&buff, &len, fp_) == -1) {
int exit_code = 1;
if (fp_) {
@ -63,8 +62,8 @@ void waybar::modules::Custom::continuousWorker() {
spdlog::error("{} stopped unexpectedly, is it endless?", name_);
}
if (config_["restart-interval"].isUInt()) {
restart = true;
pid_ = -1;
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
fp_ = util::command::open(cmd, pid_);
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
@ -83,9 +82,6 @@ void waybar::modules::Custom::continuousWorker() {
output_ = {0, output};
dp.emit();
}
if (restart) {
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
}
};
}
@ -95,15 +91,21 @@ void waybar::modules::Custom::refresh(int sig) {
}
}
void waybar::modules::Custom::handleEvent() {
if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) {
thread_.wake_up();
}
}
bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {
auto ret = ALabel::handleScroll(e);
thread_.wake_up();
handleEvent();
return ret;
}
bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
auto ret = ALabel::handleToggle(e);
thread_.wake_up();
handleEvent();
return ret;
}
@ -129,11 +131,15 @@ auto waybar::modules::Custom::update() -> void {
label_.set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
}
} else {
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_);
}
}
}
auto classes = label_.get_style_context()->list_classes();
for (auto const& c : classes) {
label_.get_style_context()->remove_class(c);

View File

@ -47,16 +47,29 @@ auto waybar::modules::Disk::update() -> void {
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true);
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks;
label_.set_markup(fmt::format(format_
auto format = format_;
auto state = getState(percentage_used);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format
, stats.f_bavail * 100 / stats.f_blocks
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks)
, fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
}
if (tooltipEnabled()) {
std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)";
if (config_["tooltip-format"].isString()) {
@ -67,12 +80,11 @@ auto waybar::modules::Disk::update() -> void {
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks)
, fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
}
event_box_.show();
// Call parent update
ALabel::update();
}

View File

@ -1,16 +1,24 @@
#include "modules/idle_inhibitor.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/command.hpp"
std::list<waybar::AModule*> waybar::modules::IdleInhibitor::modules;
bool waybar::modules::IdleInhibitor::status = false;
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: ALabel(config, "idle_inhibitor", id, "{status}"),
bar_(bar),
status_("deactivated"),
idle_inhibitor_(nullptr),
pid_(-1) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
// Add this to the modules list
waybar::modules::IdleInhibitor::modules.push_back(this);
dp.emit();
}
@ -19,6 +27,10 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
// Remove this from the modules list
waybar::modules::IdleInhibitor::modules.remove(this);
if (pid_ != -1) {
kill(-pid_, 9);
pid_ = -1;
@ -26,11 +38,27 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
}
auto waybar::modules::IdleInhibitor::update() -> void {
// Check status
if (status) {
label_.get_style_context()->remove_class("deactivated");
if (idle_inhibitor_ == nullptr) {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
}
} else {
label_.get_style_context()->remove_class("activated");
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
}
std::string status_text = status ? "activated" : "deactivated";
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_), fmt::arg("icon", getIcon(0, status_))));
label_.get_style_context()->add_class(status_);
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_);
label_.set_tooltip_text(status_text);
}
// Call parent update
ALabel::update();
@ -38,18 +66,16 @@ auto waybar::modules::IdleInhibitor::update() -> void {
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
if (e->button == 1) {
label_.get_style_context()->remove_class(status_);
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
status_ = "deactivated";
} else {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
status_ = "activated";
status = !status;
// Make all other idle inhibitor modules update
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
if (module != this) {
module->update();
}
click_param = status_;
}
}
ALabel::handleToggle(e);
return true;
}

View File

@ -28,17 +28,37 @@ auto waybar::modules::Memory::update() -> void {
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree / std::pow(1024, 2);
getState(used_ram_percentage);
label_.set_markup(fmt::format(format_,
auto format = format_;
auto state = getState(used_ram_percentage);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
}
event_box_.show();
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(tooltip_format,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
} else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
} else {
event_box_.hide();
}

View File

@ -1,16 +1,23 @@
#include "modules/mpd.hpp"
#include "modules/mpd/mpd.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include "modules/mpd/state.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
#include "modules/mpd/state.inl.hpp"
} // namespace waybar::modules
#endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
password_(config_["password"].empty() ? "" : config_["password"].asString()),
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
connection_(nullptr, &mpd_connection_free),
alternate_connection_(nullptr, &mpd_connection_free),
status_(nullptr, &mpd_status_free),
song_(nullptr, &mpd_song_free) {
if (!config_["port"].isNull() && !config_["port"].isUInt()) {
@ -28,73 +35,33 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
server_ = config["server"].asCString();
}
event_listener().detach();
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause));
}
auto waybar::modules::MPD::update() -> void {
std::lock_guard guard(connection_lock_);
tryConnect();
if (connection_ != nullptr) {
try {
bool wasPlaying = playing();
if(!wasPlaying) {
// Wait until the periodic_updater has stopped
std::lock_guard periodic_guard(periodic_lock_);
}
fetchState();
if (!wasPlaying && playing()) {
periodic_updater().detach();
}
} catch (const std::exception& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
}
setLabel();
context_.update();
// Call parent update
ALabel::update();
}
std::thread waybar::modules::MPD::event_listener() {
return std::thread([this] {
while (true) {
void waybar::modules::MPD::queryMPD() {
if (connection_ != nullptr) {
spdlog::debug("{}: fetching state information", module_name_);
try {
if (connection_ == nullptr) {
// Retry periodically if no connection
dp.emit();
std::this_thread::sleep_for(interval_);
} else {
waitForEvent();
dp.emit();
}
} catch (const std::exception& e) {
if (strcmp(e.what(), "Connection to MPD closed") == 0) {
spdlog::debug("{}: {}", module_name_, e.what());
} else {
spdlog::warn("{}: {}", module_name_, e.what());
}
}
}
});
fetchState();
spdlog::debug("{}: fetch complete", module_name_);
} catch (std::exception const& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
std::thread waybar::modules::MPD::periodic_updater() {
return std::thread([this] {
std::lock_guard guard(periodic_lock_);
while (connection_ != nullptr && playing()) {
dp.emit();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) {
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const {
std::string result =
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
const char* tag = mpd_song_get_tag(song_.get(), type, idx);
@ -133,6 +100,7 @@ void waybar::modules::MPD::setLabel() {
auto format = format_;
std::string artist, album_artist, album, title, date;
int song_pos = 0, queue_length = 0;
std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = "";
@ -148,8 +116,8 @@ void waybar::modules::MPD::setLabel() {
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
} else if (paused()) {
format =
config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString();
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
: config_["format"].asString();
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
}
@ -161,6 +129,8 @@ void waybar::modules::MPD::setLabel() {
album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE);
song_pos = mpd_status_get_song_pos(status_.get());
queue_length = mpd_status_get_queue_length(status_.get());
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));
}
@ -174,7 +144,7 @@ void waybar::modules::MPD::setLabel() {
bool singleActivated = mpd_status_get_single(status_.get());
std::string singleIcon = getOptionIcon("single", singleActivated);
// TODO: format can fail
try {
label_.set_markup(
fmt::format(format,
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
@ -184,32 +154,45 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon)));
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what());
}
if (tooltipEnabled()) {
std::string tooltip_format;
tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
: "MPD (connected)";
try {
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("artist", artist),
fmt::arg("albumArtist", album_artist),
fmt::arg("album", album),
fmt::arg("title", title),
fmt::arg("date", date),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
}
}
}
std::string waybar::modules::MPD::getStateIcon() {
std::string waybar::modules::MPD::getStateIcon() const {
if (!config_["state-icons"].isObject()) {
return "";
}
@ -231,7 +214,7 @@ std::string waybar::modules::MPD::getStateIcon() {
}
}
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) {
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const {
if (!config_[optionName + "-icons"].isObject()) {
return "";
}
@ -254,25 +237,30 @@ void waybar::modules::MPD::tryConnect() {
}
connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
alternate_connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
if (connection_ == nullptr || alternate_connection_ == nullptr) {
if (connection_ == nullptr) {
spdlog::error("{}: Failed to connect to MPD", module_name_);
connection_.reset();
alternate_connection_.reset();
return;
}
try {
checkErrors(connection_.get());
spdlog::debug("{}: Connected to MPD", module_name_);
if (!password_.empty()) {
bool res = mpd_run_password(connection_.get(), password_.c_str());
if (!res) {
spdlog::error("{}: Wrong MPD password", module_name_);
connection_.reset();
return;
}
checkErrors(connection_.get());
}
} catch (std::runtime_error& e) {
spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what());
connection_.reset();
alternate_connection_.reset();
}
}
@ -285,51 +273,34 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
case MPD_ERROR_CLOSED:
mpd_connection_clear_error(conn);
connection_.reset();
alternate_connection_.reset();
state_ = MPD_STATE_UNKNOWN;
throw std::runtime_error("Connection to MPD closed");
default:
if (conn) {
auto error_message = mpd_connection_get_error_message(conn);
std::string error(error_message);
mpd_connection_clear_error(conn);
throw std::runtime_error(std::string(error_message));
throw std::runtime_error(error);
}
throw std::runtime_error("Invalid connection");
}
}
void waybar::modules::MPD::fetchState() {
if (connection_ == nullptr) {
spdlog::error("{}: Not connected to MPD", module_name_);
return;
}
auto conn = connection_.get();
status_ = unique_status(mpd_run_status(conn), &mpd_status_free);
status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free);
checkErrors(conn);
state_ = mpd_status_get_state(status_.get());
checkErrors(conn);
song_ = unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
void waybar::modules::MPD::waitForEvent() {
auto conn = alternate_connection_.get();
// Wait for a player (play/pause), option (random, shuffle, etc.), or playlist
// change
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
checkErrors(conn);
return;
}
// alternate_idle_ = true;
// See issue #277:
// https://github.com/Alexays/Waybar/issues/277
mpd_recv_idle(conn, /* disable_timeout = */ false);
// See issue #281:
// https://github.com/Alexays/Waybar/issues/281
std::lock_guard guard(connection_lock_);
checkErrors(conn);
mpd_response_finish(conn);
song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
@ -339,24 +310,13 @@ bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) {
}
if (e->button == 1) {
std::lock_guard guard(connection_lock_);
if (stopped()) {
mpd_run_play(connection_.get());
} else {
mpd_run_toggle_pause(connection_.get());
}
if (state_ == MPD_STATE_PLAY)
context_.pause();
else
context_.play();
} else if (e->button == 3) {
std::lock_guard guard(connection_lock_);
mpd_run_stop(connection_.get());
context_.stop();
}
return true;
}
bool waybar::modules::MPD::stopped() {
return connection_ == nullptr || state_ == MPD_STATE_UNKNOWN || state_ == MPD_STATE_STOP || status_ == nullptr;
}
bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; }
bool waybar::modules::MPD::paused() { return connection_ != nullptr && state_ == MPD_STATE_PAUSE; }

383
src/modules/mpd/state.cpp Normal file
View File

@ -0,0 +1,383 @@
#include "modules/mpd/state.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include "modules/mpd/mpd.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
#include "modules/mpd/state.inl.hpp"
} // namespace waybar::modules
#endif
namespace waybar::modules::detail {
#define IDLE_RUN_NOIDLE_AND_CMD(...) \
if (idle_connection_.connected()) { \
idle_connection_.disconnect(); \
auto conn = ctx_->connection().get(); \
if (!mpd_run_noidle(conn)) { \
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \
spdlog::error("mpd: Idle: failed to unregister for IDLE events"); \
ctx_->checkErrors(conn); \
} \
} \
__VA_ARGS__; \
}
void Idle::play() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn));
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Idle::pause() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true));
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Idle::stop() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn));
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
#undef IDLE_RUN_NOIDLE_AND_CMD
void Idle::update() noexcept {
// This is intentionally blank.
}
void Idle::entry() noexcept {
auto conn = ctx_->connection().get();
assert(conn != nullptr);
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
ctx_->checkErrors(conn);
spdlog::error("mpd: Idle: failed to register for IDLE events");
} else {
spdlog::debug("mpd: Idle: watching FD");
sigc::slot<bool, Glib::IOCondition const&> idle_slot = sigc::mem_fun(*this, &Idle::on_io);
idle_connection_ =
Glib::signal_io().connect(idle_slot,
mpd_connection_get_fd(conn),
Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP);
}
}
void Idle::exit() noexcept {
if (idle_connection_.connected()) {
idle_connection_.disconnect();
spdlog::debug("mpd: Idle: unwatching FD");
}
}
bool Idle::on_io(Glib::IOCondition const&) {
auto conn = ctx_->connection().get();
// callback should do this:
enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false);
spdlog::debug("mpd: Idle: recv_idle events -> {}", events);
mpd_response_finish(conn);
try {
ctx_->checkErrors(conn);
} catch (std::exception const& e) {
spdlog::warn("mpd: Idle: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
mpd_state state = ctx_->state();
if (state == MPD_STATE_STOP) {
ctx_->emit();
ctx_->setState(std::make_unique<Stopped>(ctx_));
} else if (state == MPD_STATE_PLAY) {
ctx_->emit();
ctx_->setState(std::make_unique<Playing>(ctx_));
} else if (state == MPD_STATE_PAUSE) {
ctx_->emit();
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->emit();
// self transition
ctx_->setState(std::make_unique<Idle>(ctx_));
}
return false;
}
void Playing::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000);
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
}
void Playing::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Playing: disabled 1 second periodic timer.");
}
}
bool Playing::on_timer() {
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
if (!ctx_->is_playing()) {
if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
return false;
}
ctx_->queryMPD();
ctx_->emit();
} catch (std::exception const& e) {
spdlog::warn("mpd: Playing: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
return true;
}
void Playing::stop() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_stop(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
void Playing::pause() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_pause(ctx_->connection().get(), true);
}
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Playing::update() noexcept { ctx_->do_update(); }
void Paused::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Paused::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);
spdlog::debug("mpd: Paused: enabled 200 ms periodic timer.");
}
void Paused::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Paused: disabled 200 ms periodic timer.");
}
}
bool Paused::on_timer() {
bool rc = true;
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Idle>(ctx_));
rc = false;
} else if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
rc = false;
} else if (ctx_->is_stopped()) {
ctx_->setState(std::make_unique<Stopped>(ctx_));
rc = false;
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Paused: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
rc = false;
}
return rc;
}
void Paused::play() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_play(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Paused::stop() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_stop(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
void Paused::update() noexcept { ctx_->do_update(); }
void Stopped::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Stopped::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);
spdlog::debug("mpd: Stopped: enabled 200 ms periodic timer.");
}
void Stopped::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Stopped: disabled 200 ms periodic timer.");
}
}
bool Stopped::on_timer() {
bool rc = true;
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_stopped()) {
ctx_->setState(std::make_unique<Idle>(ctx_));
rc = false;
} else if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
rc = false;
} else if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
rc = false;
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Stopped: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
rc = false;
}
return rc;
}
void Stopped::play() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_play(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Stopped::pause() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_pause(ctx_->connection().get(), true);
}
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Stopped::update() noexcept { ctx_->do_update(); }
void Disconnected::arm_timer(int interval) noexcept {
// unregister timer, if present
disarm_timer();
// register timer
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer);
timer_connection_ =
Glib::signal_timeout().connect(timer_slot, interval);
spdlog::debug("mpd: Disconnected: enabled interval timer.");
}
void Disconnected::disarm_timer() noexcept {
// unregister timer, if present
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Disconnected: disabled interval timer.");
}
}
void Disconnected::entry() noexcept {
ctx_->emit();
arm_timer(1'000);
}
void Disconnected::exit() noexcept {
disarm_timer();
}
bool Disconnected::on_timer() {
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (ctx_->is_connected()) {
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
} else if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
return false; // do not rearm timer
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Disconnected: error: {}", e.what());
}
arm_timer(ctx_->interval() * 1'000);
return false;
}
void Disconnected::update() noexcept { ctx_->do_update(); }
} // namespace waybar::modules::detail

View File

@ -3,6 +3,7 @@
#include <sys/eventfd.h>
#include <fstream>
#include <cassert>
#include <optional>
#include "util/format.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
@ -83,6 +84,8 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
: ALabel(config, "network", id, "{ifname}", 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
ev_fd_(-1),
cidr_(-1),
signal_strength_dbm_(0),
signal_strength_(0),
@ -119,6 +122,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
}
waybar::modules::Network::~Network() {
if (ev_fd_ > -1) {
close(ev_fd_);
}
if (efd_ > -1) {
close(efd_);
}
if (ev_sock_ != nullptr) {
nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK);
if (family_ == AF_INET) {
@ -150,6 +159,30 @@ void waybar::modules::Network::createEventSocket() {
} else {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
}
efd_ = epoll_create1(EPOLL_CLOEXEC);
if (efd_ < 0) {
throw std::runtime_error("Can't create epoll");
}
{
ev_fd_ = eventfd(0, EFD_NONBLOCK);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET;
event.data.fd = ev_fd_;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
{
auto fd = nl_socket_get_fd(ev_sock_);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
event.data.fd = fd;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
}
void waybar::modules::Network::createInfoSocket() {
@ -192,6 +225,19 @@ void waybar::modules::Network::worker() {
#else
spdlog::warn("Waybar has been built without rfkill support.");
#endif
thread_ = [this] {
std::array<struct epoll_event, EPOLL_MAX> events{};
int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1);
if (ec > 0) {
for (auto i = 0; i < ec; i++) {
if (events[i].data.fd != nl_socket_get_fd(ev_sock_) || nl_recvmsgs_default(ev_sock_) < 0) {
thread_.stop();
break;
}
}
}
};
}
const std::string waybar::modules::Network::getNetworkState() const {

View File

@ -3,6 +3,8 @@
#include <spdlog/spdlog.h>
#include <wayland-client.h>
#include <algorithm>
#include "client.hpp"
#include "modules/river/tags.hpp"
#include "river-status-unstable-v1-client-protocol.h"
@ -64,8 +66,20 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
// Default to 9 tags
const uint32_t num_tags = config["num-tags"].isUInt() ? config_["num-tags"].asUInt() : 9;
for (uint32_t tag = 1; tag <= num_tags; ++tag) {
Gtk::Button &button = buttons_.emplace_back(std::to_string(tag));
std::vector<std::string> tag_labels(num_tags);
for (uint32_t tag = 0; tag < num_tags; ++tag) {
tag_labels[tag] = std::to_string(tag+1);
}
const Json::Value custom_labels = config["tag-labels"];
if (custom_labels.isArray() && !custom_labels.empty()) {
for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) {
tag_labels[tag] = custom_labels[tag].asString();
}
}
for (const auto &tag_label : tag_labels) {
Gtk::Button &button = buttons_.emplace_back(tag_label);
button.set_relief(Gtk::RELIEF_NONE);
box_.pack_start(button, false, false, 0);
button.show();

View File

@ -0,0 +1,33 @@
#include "modules/simpleclock.hpp"
#include <time.h>
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60) {
thread_ = [this] {
dp.emit();
auto now = std::chrono::system_clock::now();
auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count());
thread_.sleep_until(timeout - diff);
};
}
auto waybar::modules::Clock::update() -> void {
tzset(); // Update timezone information
auto now = std::chrono::system_clock::now();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
auto text = fmt::format(format_, localtime);
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, localtime);
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(text);
}
}
// Call parent update
ALabel::update();
}

201
src/modules/sndio.cpp Normal file
View File

@ -0,0 +1,201 @@
#include "modules/sndio.hpp"
#include <algorithm>
#include <cstdlib>
#include <poll.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
namespace waybar::modules {
void ondesc(void *arg, struct sioctl_desc *d, int curval) {
auto self = static_cast<Sndio*>(arg);
if (d == NULL) {
// d is NULL when the list is done
return;
}
self->set_desc(d, curval);
}
void onval(void *arg, unsigned int addr, unsigned int val) {
auto self = static_cast<Sndio*>(arg);
self->put_val(addr, val);
}
auto Sndio::connect_to_sndio() -> void {
hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
if (hdl_ == nullptr) {
throw std::runtime_error("sioctl_open() failed.");
}
if (sioctl_ondesc(hdl_, ondesc, this) == 0) {
throw std::runtime_error("sioctl_ondesc() failed.");
}
if (sioctl_onval(hdl_, onval, this) == 0) {
throw std::runtime_error("sioctl_onval() failed.");
}
pfds_.reserve(sioctl_nfds(hdl_));
}
Sndio::Sndio(const std::string &id, const Json::Value &config)
: ALabel(config, "sndio", id, "{volume}%", 1),
hdl_(nullptr),
pfds_(0),
addr_(0),
volume_(0),
old_volume_(0),
maxval_(0),
muted_(false) {
connect_to_sndio();
event_box_.show();
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::BUTTON_PRESS_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Sndio::handleScroll));
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &Sndio::handleToggle));
thread_ = [this] {
dp.emit();
int nfds = sioctl_pollfd(hdl_, pfds_.data(), POLLIN);
if (nfds == 0) {
throw std::runtime_error("sioctl_pollfd() failed.");
}
while (poll(pfds_.data(), nfds, -1) < 0) {
if (errno != EINTR) {
throw std::runtime_error("poll() failed.");
}
}
int revents = sioctl_revents(hdl_, pfds_.data());
if (revents & POLLHUP) {
spdlog::warn("sndio disconnected!");
sioctl_close(hdl_);
hdl_ = nullptr;
// reconnection loop
while (thread_.isRunning()) {
try {
connect_to_sndio();
} catch(std::runtime_error const& e) {
// avoid leaking hdl_
if (hdl_) {
sioctl_close(hdl_);
hdl_ = nullptr;
}
// rate limiting for the retries
thread_.sleep_for(interval_);
continue;
}
spdlog::warn("sndio reconnected!");
break;
}
}
};
}
Sndio::~Sndio() {
sioctl_close(hdl_);
}
auto Sndio::update() -> void {
auto format = format_;
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_);
if (volume_ == 0) {
label_.get_style_context()->add_class("muted");
} else {
label_.get_style_context()->remove_class("muted");
}
label_.set_markup(fmt::format(format,
fmt::arg("volume", vol),
fmt::arg("raw_value", volume_)));
ALabel::update();
}
auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void {
std::string name{d->func};
std::string node_name{d->node0.name};
if (name == "level" && node_name == "output" && d->type == SIOCTL_NUM) {
// store addr for output.level value, used in put_val
addr_ = d->addr;
maxval_ = d->maxval;
volume_ = val;
}
}
auto Sndio::put_val(unsigned int addr, unsigned int val) -> void {
if (addr == addr_) {
volume_ = val;
}
}
bool Sndio::handleScroll(GdkEventScroll *e) {
// change the volume only when no user provided
// events are configured
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
// only try to talk to sndio if connected
if (hdl_ == nullptr) return true;
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
}
int step = 5;
if (config_["scroll-step"].isInt()) {
step = config_["scroll-step"].asInt();
}
int new_volume = volume_;
if (muted_) {
new_volume = old_volume_;
}
if (dir == SCROLL_DIR::UP) {
new_volume += step;
} else if (dir == SCROLL_DIR::DOWN) {
new_volume -= step;
}
new_volume = std::clamp(new_volume, 0, static_cast<int>(maxval_));
// quits muted mode if volume changes
muted_ = false;
sioctl_setval(hdl_, addr_, new_volume);
return true;
}
bool Sndio::handleToggle(GdkEventButton* const& e) {
// toggle mute only when no user provided events are configured
if (config_["on-click"].isString()) {
return AModule::handleToggle(e);
}
// only try to talk to sndio if connected
if (hdl_ == nullptr) return true;
muted_ = !muted_;
if (muted_) {
// store old volume to be able to restore it later
old_volume_ = volume_;
sioctl_setval(hdl_, addr_, 0);
} else {
sioctl_setval(hdl_, addr_, old_volume_);
}
return true;
}
} /* namespace waybar::modules */

View File

@ -0,0 +1,70 @@
#include "modules/sway/language.hpp"
#include <spdlog/spdlog.h>
namespace waybar::modules::sway {
Language::Language(const std::string& id, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true) {
ipc_.subscribe(R"(["input"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
ipc_.sendCmd(IPC_GET_INPUTS);
// Launch worker
ipc_.setWorker([this] {
try {
ipc_.handleEvent();
} catch (const std::exception& e) {
spdlog::error("Language: {}", e.what());
}
});
dp.emit();
}
void Language::onCmd(const struct Ipc::ipc_response& res) {
try {
auto payload = parser_.parse(res.payload);
//Display current layout of a device with a maximum count of layouts, expecting that all will be OK
Json::Value::ArrayIndex maxId = 0, max = 0;
for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) {
if(payload[i]["xkb_layout_names"].size() > max) {
max = payload[i]["xkb_layout_names"].size();
maxId = i;
}
}
auto layout_name = payload[maxId]["xkb_active_layout_name"].asString().substr(0,2);
lang_ = Glib::Markup::escape_text(layout_name);
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Language: {}", e.what());
}
}
void Language::onEvent(const struct Ipc::ipc_response& res) {
try {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload)["input"];
if (payload["type"].asString() == "keyboard") {
auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2);
lang_ = Glib::Markup::escape_text(layout_name);
}
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Language: {}", e.what());
}
}
auto Language::update() -> void {
if (lang_.empty()) {
event_box_.hide();
} else {
label_.set_markup(fmt::format(format_, lang_));
if (tooltipEnabled()) {
label_.set_tooltip_text(lang_);
}
event_box_.show();
}
// Call parent update
ALabel::update();
}
} // namespace waybar::modules::sway

View File

@ -64,29 +64,51 @@ auto Window::update() -> void {
ALabel::update();
}
std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
const Json::Value& nodes, std::string& output) {
int leafNodesInWorkspace(const Json::Value& node) {
auto const& nodes = node["nodes"];
if(nodes.empty()) {
if(node["type"] == "workspace")
return 0;
else
return 1;
}
int sum = 0;
for(auto const& node : nodes)
sum += leafNodesInWorkspace(node);
return sum;
}
std::tuple<std::size_t, int, std::string, std::string> gfnWithWorkspace(
const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace) {
for(auto const& node : nodes) {
if (node["output"].isString()) {
output = node["output"].asString();
}
// found node
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
config_["all-outputs"].asBool()) {
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
return {nodes.size(),
int nb = node.size();
if(parentWorkspace != 0)
nb = leafNodesInWorkspace(parentWorkspace);
return {nb,
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id};
}
}
auto [nb, id, name, app_id] = getFocusedNode(node["nodes"], output);
// iterate
if(node["type"] == "workspace")
parentWorkspace = node;
auto [nb, id, name, app_id] = gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id};
}
// Search for floating node
std::tie(nb, id, name, app_id) = getFocusedNode(node["floating_nodes"], output);
std::tie(nb, id, name, app_id) = gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id};
}
@ -94,6 +116,12 @@ std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
return {0, -1, "", ""};
}
std::tuple<std::size_t, int, std::string, std::string> Window::getFocusedNode(
const Json::Value& nodes, std::string& output) {
Json::Value placeholder = 0;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
}
void Window::getTree() {
try {
ipc_.sendCmd(IPC_GET_TREE);

View File

@ -283,12 +283,20 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node
return config_["format-icons"]["persistent"].asString();
} else if (config_["format-icons"][key].isString()) {
return config_["format-icons"][key].asString();
} else if (config_["format-icons"][trimWorkspaceName(key)].isString()) {
return config_["format-icons"][trimWorkspaceName(key)].asString();
}
}
return name;
}
bool Workspaces::handleScroll(GdkEventScroll *e) {
if (gdk_event_get_pointer_emulated((GdkEvent *)e)) {
/**
* Ignore emulated scroll events on window
*/
return false;
}
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;

View File

@ -40,6 +40,16 @@ auto waybar::modules::Temperature::update() -> void {
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
if (tooltipEnabled()) {
std::string tooltip_format = "{temperatureC}°C";
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format,
fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k)));
}
// Call parent update
ALabel::update();
}

View File

@ -49,8 +49,8 @@ static std::vector<std::string> search_prefix()
auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS");
if (!xdg_data_dirs) {
prefixes.push_back("/usr/share/");
prefixes.push_back("/usr/local/share/");
prefixes.emplace_back("/usr/share/");
prefixes.emplace_back("/usr/local/share/");
} else {
std::string xdg_data_dirs_str(xdg_data_dirs);
size_t start = 0, end = 0;
@ -95,14 +95,14 @@ static std::string get_from_desktop_app_info(const std::string &app_id)
if (!app_info)
app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
if (app_info)
if (app_info && app_info->get_icon())
return app_info->get_icon()->to_string();
return "";
}
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */
static std::string get_from_icon_theme(Glib::RefPtr<Gtk::IconTheme> icon_theme,
static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id) {
if (icon_theme->lookup_icon(app_id, 24))
@ -111,7 +111,7 @@ static std::string get_from_icon_theme(Glib::RefPtr<Gtk::IconTheme> icon_theme,
return "";
}
static bool image_load_icon(Gtk::Image& image, Glib::RefPtr<Gtk::IconTheme> icon_theme,
static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id_list, int size)
{
std::string app_id;
@ -187,6 +187,12 @@ static void tl_handle_done(void *data, struct zwlr_foreign_toplevel_handle_v1 *h
return static_cast<Task*>(data)->handle_done();
}
static void tl_handle_parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
struct zwlr_foreign_toplevel_handle_v1 *parent)
{
/* This is explicitly left blank */
}
static void tl_handle_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
{
return static_cast<Task*>(data)->handle_closed();
@ -200,6 +206,7 @@ static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_imp
.state = tl_handle_state,
.done = tl_handle_done,
.closed = tl_handle_closed,
.parent = tl_handle_parent,
};
Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
@ -231,13 +238,13 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
auto icon_pos = format.find("{icon}");
if (icon_pos == 0) {
with_icon_ = true;
format_after_ = trim(format.substr(6));
format_after_ = format.substr(6);
} else if (icon_pos == std::string::npos) {
format_before_ = format;
} else {
with_icon_ = true;
format_before_ = trim(format.substr(0, icon_pos));
format_after_ = trim(format.substr(icon_pos + 6));
format_before_ = format.substr(0, icon_pos);
format_after_ = format.substr(icon_pos + 6);
}
} else {
/* The default is to only show the icon */
@ -306,7 +313,7 @@ std::string Task::state_string(bool shortened) const
void Task::handle_title(const char *title)
{
title_ = Glib::Markup::escape_text(title);
title_ = title;
}
void Task::handle_app_id(const char *app_id)
@ -360,16 +367,16 @@ void Task::handle_output_leave(struct wl_output *output)
void Task::handle_state(struct wl_array *state)
{
state_ = 0;
for (uint32_t* entry = static_cast<uint32_t*>(state->data);
entry < static_cast<uint32_t*>(state->data) + state->size;
entry++) {
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)
size_t size = state->size / sizeof(uint32_t);
for (size_t i = 0; i < size; ++i) {
auto entry = static_cast<uint32_t*>(state->data)[i];
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)
state_ |= MAXIMIZED;
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)
state_ |= MINIMIZED;
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)
state_ |= ACTIVE;
if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)
if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)
state_ |= FULLSCREEN;
}
}
@ -436,6 +443,14 @@ bool Task::handle_clicked(GdkEventButton *bt)
activate();
else if (action == "minimize")
minimize(!minimized());
else if (action == "minimize-raise"){
if (minimized())
minimize(false);
else if (active())
minimize(true);
else
activate();
}
else if (action == "maximize")
maximize(!maximized());
else if (action == "fullscreen")
@ -460,38 +475,51 @@ bool Task::operator!=(const Task &o) const
void Task::update()
{
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
std::string title = title_;
std::string app_id = app_id_;
if (markup) {
title = Glib::Markup::escape_text(title);
app_id = Glib::Markup::escape_text(app_id);
}
if (!format_before_.empty()) {
text_before_.set_label(
fmt::format(format_before_,
fmt::arg("title", title_),
fmt::arg("app_id", app_id_),
auto txt = fmt::format(format_before_,
fmt::arg("title", title),
fmt::arg("app_id", app_id),
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
)
);
if (markup)
text_before_.set_markup(txt);
else
text_before_.set_label(txt);
text_before_.show();
}
if (!format_after_.empty()) {
text_after_.set_label(
fmt::format(format_after_,
fmt::arg("title", title_),
fmt::arg("app_id", app_id_),
auto txt = fmt::format(format_after_,
fmt::arg("title", title),
fmt::arg("app_id", app_id),
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
)
);
if (markup)
text_after_.set_markup(txt);
else
text_after_.set_label(txt);
text_after_.show();
}
if (!format_tooltip_.empty()) {
button_.set_tooltip_markup(
fmt::format(format_tooltip_,
fmt::arg("title", title_),
fmt::arg("app_id", app_id_),
auto txt = fmt::format(format_tooltip_,
fmt::arg("title", title),
fmt::arg("app_id", app_id),
fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))
)
);
if (markup)
button_.set_tooltip_markup(txt);
else
button_.set_tooltip_text(txt);
}
}
@ -518,6 +546,11 @@ void Task::activate()
void Task::fullscreen(bool set)
{
if (zwlr_foreign_toplevel_handle_v1_get_version(handle_) < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) {
spdlog::warn("Foreign toplevel manager server does not support for set/unset fullscreen.");
return;
}
if (set)
zwlr_foreign_toplevel_handle_v1_set_fullscreen(handle_, nullptr);
else
@ -604,10 +637,22 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
Taskbar::~Taskbar()
{
if (manager_) {
struct wl_display *display = Client::inst()->wl_display;
/*
* Send `stop` request and wait for one roundtrip.
* This is not quite correct as the protocol encourages us to wait for the .finished event,
* but it should work with wlroots foreign toplevel manager implementation.
*/
zwlr_foreign_toplevel_manager_v1_stop(manager_);
wl_display_roundtrip(display);
if (manager_) {
spdlog::warn("Foreign toplevel manager destroyed before .finished event");
zwlr_foreign_toplevel_manager_v1_destroy(manager_);
manager_ = nullptr;
}
}
}
void Taskbar::update()
{
@ -640,10 +685,14 @@ void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint
spdlog::warn("Register foreign toplevel manager again although already existing!");
return;
}
if (version != 2) {
spdlog::warn("Using different foreign toplevel manager protocol version: {}", version);
if (version < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) {
spdlog::warn("Foreign toplevel manager server does not have the appropriate version."
" To be able to use all features, you need at least version 2, but server is version {}", version);
}
// limit version to a highest supported by the client protocol file
version = std::min<uint32_t>(version, zwlr_foreign_toplevel_manager_v1_interface.version);
manager_ = static_cast<struct zwlr_foreign_toplevel_manager_v1 *>(wl_registry_bind(registry, name,
&zwlr_foreign_toplevel_manager_v1_interface, version));
@ -659,6 +708,7 @@ void Taskbar::register_seat(struct wl_registry *registry, uint32_t name, uint32_
spdlog::warn("Register seat again although already existing!");
return;
}
version = std::min<uint32_t>(version, wl_seat_interface.version);
seat_ = static_cast<wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));
}
@ -709,9 +759,7 @@ bool Taskbar::show_output(struct wl_output *output) const
bool Taskbar::all_outputs() const
{
static bool result = config_["all-outputs"].isBool() ? config_["all-outputs"].asBool() : false;
return result;
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
}
std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const

View File

@ -20,8 +20,8 @@
#include <fcntl.h>
#include <linux/rfkill.h>
#include <poll.h>
#include <stdlib.h>
#include <sys/poll.h>
#include <unistd.h>
#include <cerrno>

View File

@ -1,10 +1,13 @@
[wrap-file]
directory = fmt-5.3.0
directory = fmt-7.1.3
source_url = https://github.com/fmtlib/fmt/archive/5.3.0.tar.gz
source_filename = fmt-5.3.0.tar.gz
source_hash = defa24a9af4c622a7134076602070b45721a43c51598c8456ec6f2c4dbb51c89
source_url = https://github.com/fmtlib/fmt/archive/7.1.3.tar.gz
source_filename = fmt-7.1.3.tar.gz
source_hash = 5cae7072042b3043e12d53d50ef404bbb76949dad1de368d7f993a15c8c05ecc
patch_url = https://github.com/mesonbuild/fmt/releases/download/5.3.0-1/fmt.zip
patch_filename = fmt-5.3.0-1-wrap.zip
patch_hash = 18f21a3b8833949c35d4ac88a7059577d5fa24b98786e4b1b2d3d81bb811440f
patch_url = https://github.com/mesonbuild/fmt/releases/download/7.1.3-1/fmt.zip
patch_filename = fmt-7.1.3-1-wrap.zip
patch_hash = 6eb951a51806fd6ffd596064825c39b844c1fe1799840ef507b61a53dba08213
[provide]
fmt = fmt_dep

View File

@ -1,5 +1,5 @@
[wrap-file]
directory = gtk-layer-shell-0.3.0
source_filename = gtk-layer-shell-0.3.0.tar.gz
source_hash = edd5e31279d494df66da9e9190c219fa295da547f5538207685e98468dbc134d
source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.3.0/gtk-layer-shell-0.3.0.tar.gz
directory = gtk-layer-shell-0.4.0
source_filename = gtk-layer-shell-0.4.0.tar.gz
source_hash = 52fd74d3161fefa5528585ca5a523c3150934961f2284ad010ae54336dad097e
source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.4.0/gtk-layer-shell-0.4.0.tar.gz

View File

@ -1,10 +1,13 @@
[wrap-file]
directory = spdlog-1.3.1
directory = spdlog-1.8.1
source_url = https://github.com/gabime/spdlog/archive/v1.3.1.tar.gz
source_filename = v1.3.1.tar.gz
source_hash = 160845266e94db1d4922ef755637f6901266731c4cb3b30b45bf41efa0e6ab70
source_url = https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz
source_filename = v1.8.1.tar.gz
source_hash = 5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb
patch_url = https://github.com/mesonbuild/spdlog/releases/download/1.3.1-1/spdlog.zip
patch_filename = spdlog-1.3.1-1-wrap.zip
patch_hash = 715a0229781019b853d409cc0bf891ee4b9d3a17bec0cf87f4ad30b28bbecc87
patch_url = https://github.com/mesonbuild/spdlog/releases/download/1.8.1-1/spdlog.zip
patch_filename = spdlog-1.8.1-1-wrap.zip
patch_hash = 76844292a8e912aec78450618271a311841b33b17000988f215ddd6c64dd71b3
[provide]
spdlog = spdlog_dep