mirror of
https://github.com/rad4day/Waybar.git
synced 2023-12-21 10:22:59 +01:00
Merge branch 'master' into battery-custom-tooltip
This commit is contained in:
commit
fdaba72974
22
.github/workflows/freebsd.yml
vendored
Normal file
22
.github/workflows/freebsd.yml
vendored
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -67,6 +67,7 @@ libnl [Network module]
|
||||
libappindicator-gtk3 [Tray module]
|
||||
libdbusmenu-gtk3 [Tray module]
|
||||
libmpdclient [MPD module]
|
||||
libsndio [sndio module]
|
||||
```
|
||||
|
||||
**Build dependencies**
|
||||
|
@ -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_;
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
@ -29,18 +35,20 @@ class Client {
|
||||
std::tuple<const std::string, const std::string> getConfigs(const std::string &config,
|
||||
const std::string &style) const;
|
||||
void bindInterfaces();
|
||||
const std::string getValidPath(const std::vector<std::string> &paths) const;
|
||||
void handleOutput(struct waybar_output &output);
|
||||
bool isValidOutput(const Json::Value &config, struct waybar_output &output);
|
||||
auto setupConfig(const std::string &config_file) -> void;
|
||||
auto setupCss(const std::string &css_file) -> void;
|
||||
struct waybar_output &getOutput(void *);
|
||||
const std::string getValidPath(const std::vector<std::string> &paths) const;
|
||||
void handleOutput(struct waybar_output &output);
|
||||
bool isValidOutput(const Json::Value &config, struct waybar_output &output);
|
||||
auto setupConfig(const std::string &config_file) -> void;
|
||||
auto setupCss(const std::string &css_file) -> void;
|
||||
struct waybar_output & getOutput(void *);
|
||||
std::vector<Json::Value> getOutputConfigs(struct waybar_output &output);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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
|
67
include/modules/mpd/mpd.hpp
Normal file
67
include/modules/mpd/mpd.hpp
Normal 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
|
217
include/modules/mpd/state.hpp
Normal file
217
include/modules/mpd/state.hpp
Normal 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
|
24
include/modules/mpd/state.inl.hpp
Normal file
24
include/modules/mpd/state.inl.hpp
Normal 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
|
@ -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_;
|
||||
|
||||
|
24
include/modules/simpleclock.hpp
Normal file
24
include/modules/simpleclock.hpp
Normal 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
30
include/modules/sndio.hpp
Normal 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
|
28
include/modules/sway/language.hpp
Normal file
28
include/modules/sway/language.hpp
Normal 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
|
@ -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;
|
||||
|
@ -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 ++
|
||||
|
@ -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*
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
83
man/waybar-sndio.5.scd
Normal 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*
|
@ -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
|
||||
|
72
man/waybar-sway-language.5.scd
Normal file
72
man/waybar-sway-language.5.scd
Normal 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*
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
68
meson.build
68
meson.build
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
ExecStart=@prefix@/bin/waybar
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
|
@ -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();
|
||||
|
@ -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()) {
|
||||
|
671
src/bar.cpp
671
src/bar.cpp
@ -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,32 +371,30 @@ 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);
|
||||
box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
|
||||
vertical = true;
|
||||
}
|
||||
|
||||
left_.get_style_context()->add_class("modules-left");
|
||||
center_.get_style_context()->add_class("modules-center");
|
||||
right_.get_style_context()->add_class("modules-right");
|
||||
|
||||
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()) {
|
||||
@ -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;
|
||||
}
|
||||
if (!vertical && 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_, width, height);
|
||||
auto gdk_window = window.get_window()->gobj();
|
||||
surface = gdk_wayland_window_get_wl_surface(gdk_window);
|
||||
}
|
||||
|
||||
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_);
|
||||
}
|
||||
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]) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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]);
|
||||
|
15
src/main.cpp
15
src/main.cpp
@ -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) {
|
||||
|
@ -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)) {
|
||||
@ -54,12 +90,22 @@ void waybar::modules::Battery::getBatteries() {
|
||||
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
|
||||
fs::exists(node.path() / "capacity") && fs::exists(node.path() / "uevent") &&
|
||||
fs::exists(node.path() / "status") && fs::exists(node.path() / "type")) {
|
||||
std::string type;
|
||||
std::ifstream(node.path() / "type") >> type;
|
||||
std::string type;
|
||||
std::ifstream(node.path() / "type") >> type;
|
||||
|
||||
if (!type.compare("Battery")){
|
||||
batteries_.push_back(node.path());
|
||||
if (!type.compare("Battery")){
|
||||
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();
|
||||
if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {capacity, time_remaining, status};
|
||||
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;
|
||||
}
|
||||
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"};
|
||||
@ -154,10 +224,14 @@ const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) c
|
||||
}
|
||||
|
||||
const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemaining) {
|
||||
hoursRemaining = std::fabs(hoursRemaining);
|
||||
hoursRemaining = std::fabs(hoursRemaining);
|
||||
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();
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,9 +131,13 @@ auto waybar::modules::Custom::update() -> void {
|
||||
label_.set_markup(str);
|
||||
if (tooltipEnabled()) {
|
||||
if (text_ == tooltip_) {
|
||||
label_.set_tooltip_markup(str);
|
||||
if (label_.get_tooltip_markup() != str) {
|
||||
label_.set_tooltip_markup(str);
|
||||
}
|
||||
} else {
|
||||
label_.set_tooltip_markup(tooltip_);
|
||||
if (label_.get_tooltip_markup() != tooltip_) {
|
||||
label_.set_tooltip_markup(tooltip_);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto classes = label_.get_style_context()->list_classes();
|
||||
|
@ -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;
|
||||
|
||||
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", percentage_used)
|
||||
, fmt::arg("total", total)
|
||||
, fmt::arg("path", path_)
|
||||
));
|
||||
}
|
||||
|
||||
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("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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_,
|
||||
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));
|
||||
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()) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
event_box_.show();
|
||||
} else {
|
||||
event_box_.hide();
|
||||
}
|
||||
|
@ -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) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
void waybar::modules::MPD::queryMPD() {
|
||||
if (connection_ != nullptr) {
|
||||
spdlog::debug("{}: fetching state information", module_name_);
|
||||
try {
|
||||
fetchState();
|
||||
spdlog::debug("{}: fetch complete", module_name_);
|
||||
} catch (std::exception const& e) {
|
||||
spdlog::error("{}: {}", module_name_, e.what());
|
||||
state_ = MPD_STATE_UNKNOWN;
|
||||
}
|
||||
});
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
}
|
||||
|
||||
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,42 +144,55 @@ void waybar::modules::MPD::setLabel() {
|
||||
bool singleActivated = mpd_status_get_single(status_.get());
|
||||
std::string singleIcon = getOptionIcon("single", singleActivated);
|
||||
|
||||
// TODO: format can fail
|
||||
label_.set_markup(
|
||||
fmt::format(format,
|
||||
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
|
||||
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
|
||||
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
|
||||
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
|
||||
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
|
||||
fmt::arg("elapsedTime", elapsedTime),
|
||||
fmt::arg("totalTime", totalTime),
|
||||
fmt::arg("stateIcon", stateIcon),
|
||||
fmt::arg("consumeIcon", consumeIcon),
|
||||
fmt::arg("randomIcon", randomIcon),
|
||||
fmt::arg("repeatIcon", repeatIcon),
|
||||
fmt::arg("singleIcon", singleIcon)));
|
||||
try {
|
||||
label_.set_markup(
|
||||
fmt::format(format,
|
||||
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
|
||||
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
|
||||
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
|
||||
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
|
||||
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)";
|
||||
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("stateIcon", stateIcon),
|
||||
fmt::arg("consumeIcon", consumeIcon),
|
||||
fmt::arg("randomIcon", randomIcon),
|
||||
fmt::arg("repeatIcon", repeatIcon),
|
||||
fmt::arg("singleIcon", singleIcon));
|
||||
label_.set_tooltip_text(tooltip_text);
|
||||
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
383
src/modules/mpd/state.cpp
Normal 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
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
33
src/modules/simpleclock.cpp
Normal file
33
src/modules/simpleclock.cpp
Normal 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
201
src/modules/sndio.cpp
Normal 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 */
|
70
src/modules/sway/language.cpp
Normal file
70
src/modules/sway/language.cpp
Normal 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
|
@ -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) {
|
||||
for (auto const& node : nodes) {
|
||||
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(),
|
||||
node["id"].asInt(),
|
||||
Glib::Markup::escape_text(node["name"].asString()),
|
||||
app_id};
|
||||
: node["window_properties"]["instance"].asString();
|
||||
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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,8 +637,20 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
|
||||
Taskbar::~Taskbar()
|
||||
{
|
||||
if (manager_) {
|
||||
zwlr_foreign_toplevel_manager_v1_destroy(manager_);
|
||||
manager_ = nullptr;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user