Compare commits

..

94 Commits
0.0.7 ... 0.2.0

Author SHA1 Message Date
43cd80fb31 chore: 0.2.0 2018-11-03 13:20:05 +01:00
5ece0d98ee Merge pull request #78 from mithodin/filesystem-experimental
add option for when filesystem still lives in the experimental namespace
2018-11-03 13:00:04 +01:00
0637888460 even simpler check 2018-11-03 12:44:15 +01:00
ebbdaa168c automatically detect where filesystem lives 2018-11-02 23:15:42 +01:00
6ab01b1ad4 fix(style): not charging 2018-11-02 23:00:38 +01:00
cf921a5e14 Merge pull request #76 from mithodin/charging-full
Add class for full battery and give option to interpret unknown as full
2018-11-02 22:51:57 +01:00
25f31b19f6 formatting is hard. 2018-11-02 22:50:01 +01:00
d8b6201632 ...and fix the function signature in the header 2018-11-02 22:15:54 +01:00
123ce083b4 fix typo and initialize old_status_ 2018-11-02 22:08:55 +01:00
0522577fe5 make status and state fully configurable formats 2018-11-02 22:04:43 +01:00
1ff9fd06af Merge pull request #77 from mithodin/old-gdbus-codegen
fix compilation on systems with old gdbus-codegen
2018-11-02 21:23:10 +01:00
b6cad05489 fix formatting 2018-11-02 21:13:57 +01:00
236be90c2f add option for when filesystem still lives in the experimental namespace 2018-11-02 20:59:41 +01:00
f137090d55 fix compilation on systems with old gdbus-codegen 2018-11-02 20:13:09 +01:00
9c57df505c Add class for full battery and give option to interpret unknown as full 2018-11-02 19:41:00 +01:00
00e7e87f55 fix: style 2018-11-02 17:39:00 +01:00
836c543c62 fix: style 2018-11-02 17:07:51 +01:00
7bca5fd6bd feat(Bar): add a warning about minimum height 2018-11-02 12:35:26 +01:00
61e9f0803d Merge pull request #75 from ForTheReallys/proper_height
Fix #54
2018-11-02 12:26:14 +01:00
9b201c77d7 feat: battery states && format-full/charging 2018-11-02 11:23:29 +01:00
4b68840212 Fix #54 2018-11-01 16:00:38 -05:00
9d4048983d refactor: remove useless tmp variable 2018-11-01 09:27:00 +01:00
0670225e69 Merge pull request #72 from Robinhuett/custom_module_states
Custom modules can control tooltip and CSS class
2018-11-01 09:11:15 +01:00
e23fbd0add Added return-type json to custom module 2018-11-01 00:40:44 +01:00
341d3300fa Custom modules can control tooltip and CSS class 2018-10-30 21:28:31 +01:00
c3e185546d Merge pull request #68 from harishkrupo/master
Add configuration options for widgets on mouse events
2018-10-30 16:32:37 +01:00
0e93de9c0a Merge pull request #71 from Robinhuett/configurable_battery_levels
Added second warning stage to battery module
2018-10-30 16:31:01 +01:00
3e34137ac7 pulseaudio: Change volume on scroll event
Subscribe for mouse scroll events on the pulseaudio widget
and change volume when event is received.
Scroll up increments the volume and scroll down decrements it.
These events are only subscibed when there are no user defined
commands present for them.

Signed-off-by: Harish Krupo <harishkrupo@gmail.com>
2018-10-30 20:53:43 +05:30
4c8621c7a5 Added second warning stage to battery module
Also naming is a bit more consistent
2018-10-30 16:23:36 +01:00
d7d1ebd736 ALabel: Add support for configurable mouse events
This patch adds 3 new configuration options applicable for
subclasses of ALabel. The options can be used to execute
user defined code in response to the 3 mouse events:
* on-click: The left mouse button click
* on-scroll-up
* on-scroll-down
This patch also modifies the behaviour of the format-alt toggle
such that when the on-click event is configured, format-alt is
toggled on any mouse click other than left click. When on-click
is not defined, any mouse button would toggle format-alt.

Signed-off-by: Harish Krupo <harishkrupo@gmail.com>
2018-10-30 20:52:23 +05:30
e93c5e7957 Merge pull request #70 from Robinhuett/mode_module
Add module to show sway binding mode
2018-10-30 14:26:43 +01:00
668b7b736c Added default config for sway binding mode 2018-10-30 13:44:44 +01:00
a042eea384 Add module to show sway binding mode 2018-10-30 13:39:30 +01:00
c9a8a07976 fix(window): title on new workspace 2018-10-29 21:52:53 +01:00
4307e4fd8e chore: upgrade fmt to 5.2.0 2018-10-28 14:40:25 +01:00
daf613f8ca feat: add debug about tray beta 2018-10-28 08:43:48 +01:00
3f2eb0b492 chore: 0.1.3 2018-10-28 08:39:33 +01:00
4f773ea268 Merge pull request #65 from maxice8/fix-musl
add missing <cstring> include for strncpy, fixes musl
2018-10-28 08:29:56 +01:00
047473e5a4 add missing <cstring> include for strncpy, fixes musl
I/usr/include/libdbusmenu-glib-0.4 -flto -fdiagnostics-color=always -DNDEBUG -pipe -D_FILE_OFFSET_BITS=64 -std=c++17 -DHAVE_SWAY -DHAVE_LIBPULSE -DHAVE_DBUSMENU -D_FORTIFY_SOURCE=2 -mtune=generic -O2 -D_REENTRANT -pthread  -MD -MQ 'waybar@exe/src_modules_sway_ipc_client.cpp.o' -MF 'waybar@exe/src_modules_sway_ipc_client.cpp.o.d' -o 'waybar@exe/src_modules_sway_ipc_client.cpp.o' -c ../src/modules/sway/ipc/client.cpp
../src/modules/sway/ipc/client.cpp: In member function 'int waybar::modules::sway::Ipc::open(const string&) const':
../src/modules/sway/ipc/client.cpp:47:3: error: 'strncpy' was not declared in this scope
   strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
   ^~~~~~~
../src/modules/sway/ipc/client.cpp:47:3: note: 'strncpy' is defined in header '<cstring>'; did you forget to '#include <cstring>'?
../src/modules/sway/ipc/client.cpp:2:1:
+#include <cstring>

../src/modules/sway/ipc/client.cpp:47:3:
   strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
   ^~~~~~~
[36/44] Compiling C++ object 'waybar@exe/src_modules_custom.cpp.o'.
[37/44] Compiling C++ object 'waybar@exe/src_client.cpp.o'.
[38/44] Compiling C++ object 'waybar@exe/src_modules_cpu.cpp.o'.
ninja: build stopped: subcommand failed.
2018-10-28 04:06:07 -03:00
ed3e4b1395 fix(pulseaudio): check active_port is set 2018-10-27 11:23:43 +02:00
16b01e1059 Merge pull request #62 from colemickens/giounix20
meson: fix 'gio-unix-2.0' dependency
2018-10-27 09:35:47 +02:00
1ae490c8f7 Merge pull request #61 from colemickens/outdir
meson: make extra output directory configurable
2018-10-27 09:16:11 +02:00
0d0a3be483 meson: fix 'gio-unix-2.0' dependency 2018-10-26 23:21:03 -07:00
a1c4b9bb0c meson: make extra output directory configurable 2018-10-26 23:20:38 -07:00
a55a1ae866 fix(tray): icons size 2018-10-26 14:53:39 +02:00
07d8dfb3d6 feat(tray): spacing config 2018-10-26 12:08:50 +02:00
5010227e6b fix(tray): icons 2018-10-26 11:59:03 +02:00
e8f3c1c6b3 chore: v0.1.2 2018-10-26 11:21:04 +02:00
7e6c701659 chore: update README 2018-10-26 11:16:17 +02:00
adc38c3dfe feat(sni): set protocol version 2018-10-26 10:56:45 +02:00
b10907ee44 refactor: remove useless code 2018-10-26 10:39:25 +02:00
0c9699b076 fix: check before set is host registered 2018-10-26 10:27:15 +02:00
63e86fbe9e fix: check type 2018-10-26 10:12:34 +02:00
f20441fa92 refactor: simpler sni naming 2018-10-26 10:05:54 +02:00
3f269ff463 fix: check json::value type 2018-10-26 09:27:16 +02:00
fd76e98552 fix: ifdef include 2018-10-25 19:12:28 +02:00
9fae5efc06 feat: use interval thread until got inotify event 2018-10-25 17:39:15 +02:00
7f1f217d84 feat: multiple config per modules 2018-10-25 17:30:26 +02:00
1ea0c1f9dd chore: find gdbus-codegen once 2018-10-25 16:42:01 +02:00
4626cbef63 fix(pulseaudio): round volume 2018-10-25 13:57:35 +02:00
85f845ca43 refactor: remove debug 2018-10-25 13:49:30 +02:00
68d9d2c347 fix: ifdef 2018-10-25 12:24:39 +02:00
f3fe57dd24 chore: v0.1.0 2018-10-25 12:15:52 +02:00
d4b97d5d09 feat: optional tray 2018-10-25 11:47:03 +02:00
f3975e6634 feat: gdbus dbus-menu 2018-10-25 11:44:04 +02:00
0dedcc0126 Merge pull request #39 from Alexays/tray-gdbus
Tray beta
2018-10-25 11:40:33 +02:00
0e6147b644 Merge branch 'master' into tray-gdbus 2018-10-25 11:36:35 +02:00
45847847b9 style: disallow resize 2018-10-25 10:43:37 +02:00
c912d8c86a fix(workspaces): index 2018-10-25 10:22:11 +02:00
da0674debc Merge pull request #53 from dangerousdan/fix-clock-interval
fix custom intervals causing cpu to go mental
2018-10-23 03:02:23 +02:00
137c5fb712 fix custom intervals causing cpu to go mental 2018-10-23 00:06:46 +01:00
5a6e05dcde feat: workspaces index 2018-10-22 10:41:52 +02:00
290f3a79e9 Merge pull request #46 from DanySpin97/build-options
Add options for pulseaudio and libnl
2018-10-21 12:31:14 +02:00
73553802f9 Add options for pulseaudio and libnl 2018-10-21 09:58:35 +00:00
a13ce6e227 Merge branch 'tray-gdbus' into tray-gdbus 2018-10-05 21:20:36 +02:00
091b460d03 feat(Tray): handle click 2018-10-04 18:53:50 +02:00
56e55fa4aa fix: remove TODO 2018-10-04 18:53:50 +02:00
dc799adf45 feat(Tray): icon pixmap 2018-10-04 18:53:50 +02:00
75c9477aa8 feat(Tray): handle item unregister 2018-10-04 18:53:50 +02:00
75cf1d70fd feat(WIP): tray
feat(wip): tray

feat(wip): tray

feat(WIP): gdbus

feat(WIP): tray
2018-10-04 18:53:50 +02:00
9a333088e5 Merge branch 'tray-gdbus' of github.com:topisani/Waybar into tray-gdbus 2018-10-04 18:47:06 +02:00
b231054b69 Merge remote-tracking branch 'origin/master' into tray-gdbus 2018-10-04 18:04:36 +02:00
108b1092e5 WIP sni dbus-menu support. 2018-10-04 18:03:01 +02:00
a63650aa67 chore: optimize preview 2018-10-01 18:56:58 +02:00
0e8b3f71b8 fix(Custom): pclose if continuous script end 2018-09-18 23:21:08 +02:00
00959c7d65 feat(Custom): handle continuous script 2018-09-18 23:15:37 +02:00
d5d620e72d feat(Window): handle closed window 2018-09-18 21:16:35 +02:00
d914429194 feat(Workspaces): format 2018-09-18 20:58:11 +02:00
fcdb8387af feat(Tray): handle click 2018-09-17 23:32:05 +02:00
3e2e1a7018 fix: remove TODO 2018-09-15 19:01:28 +02:00
86958f264e feat(Tray): icon pixmap 2018-09-15 19:01:28 +02:00
20ff2cab9e feat(Tray): handle item unregister 2018-09-15 19:01:28 +02:00
fc6e42d748 feat(WIP): tray
feat(wip): tray

feat(wip): tray

feat(WIP): gdbus

feat(WIP): tray
2018-09-15 19:00:45 +02:00
0eee8eade7 feat(WIP): tray
feat(wip): tray

feat(wip): tray

feat(WIP): gdbus

feat(WIP): tray
2018-09-02 17:29:16 +02:00
47 changed files with 1713 additions and 178 deletions

0
.SRCINFO Normal file
View File

2
.gitignore vendored
View File

@ -4,7 +4,7 @@ vgcore.*
/.vscode
*.swp
packagecache
/subprojects/fmt-4.1.0
/subprojects/**/
/build
/dist
/meson.egg-info

View File

@ -7,6 +7,7 @@
**Current features**
- Sway Workspaces
- Sway focused window name
- Tray (Beta) [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time
- Battery
- Network

View File

@ -6,21 +6,25 @@
namespace waybar {
class ALabel : public IModule {
public:
ALabel(const Json::Value&, const std::string format);
virtual ~ALabel() = default;
virtual auto update() -> void;
virtual std::string getIcon(uint16_t, const std::string& alt = "");
virtual operator Gtk::Widget &();
protected:
Gtk::EventBox event_box_;
Gtk::Label label_;
const Json::Value& config_;
std::string format_;
private:
bool handleToggle(GdkEventButton* const& ev);
bool alt = false;
const std::string default_format_;
public:
ALabel(const Json::Value&, const std::string format);
virtual ~ALabel() = default;
virtual auto update() -> void;
virtual std::string getIcon(uint16_t, const std::string& alt = "");
virtual operator Gtk::Widget&();
protected:
Gtk::EventBox event_box_;
Gtk::Label label_;
const Json::Value& config_;
std::string format_;
std::mutex mutex_;
private:
bool handleToggle(GdkEventButton* const& ev);
bool handleScroll(GdkEventScroll*);
bool alt = false;
const std::string default_format_;
};
}
} // namespace waybar

View File

@ -2,14 +2,10 @@
#include <unistd.h>
#include <wordexp.h>
#include <fmt/format.h>
#include <gdk/gdk.h>
#include <wayland-client.h>
#include <gdk/gdkwayland.h>
#include "bar.hpp"
namespace waybar {
@ -30,14 +26,14 @@ class Client {
struct wl_seat *seat = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
private:
void bindInterfaces();
auto setupCss();
private:
void bindInterfaces();
auto setupCss();
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 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);
};
}

View File

@ -3,12 +3,16 @@
#include <json/json.h>
#include "modules/clock.hpp"
#ifdef HAVE_SWAY
#include "modules/sway/mode.hpp"
#include "modules/sway/workspaces.hpp"
#include "modules/sway/window.hpp"
#endif
#include "modules/battery.hpp"
#include "modules/memory.hpp"
#include "modules/cpu.hpp"
#ifdef HAVE_DBUSMENU
#include "modules/sni/tray.hpp"
#endif
#ifdef HAVE_LIBNL
#include "modules/network.hpp"
#endif

View File

@ -1,6 +1,10 @@
#pragma once
#include <filesystem>
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
#include <fstream>
#include <iostream>
#include <fmt/format.h>
@ -11,7 +15,11 @@
namespace waybar::modules {
#ifdef FILESYSTEM_EXPERIMENTAL
namespace fs = std::experimental::filesystem;
#else
namespace fs = std::filesystem;
#endif
class Battery : public ALabel {
public:
@ -22,10 +30,14 @@ class Battery : public ALabel {
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
void worker();
std::tuple<uint16_t, std::string> getInfos();
std::string getState(uint16_t);
util::SleeperThread thread_;
util::SleeperThread threadTimer_;
std::vector<fs::path> batteries_;
int fd_;
std::string old_status_;
};
}

View File

@ -4,6 +4,7 @@
#include <iostream>
#include "util/chrono.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "ALabel.hpp"
namespace waybar::modules {
@ -13,11 +14,19 @@ class Custom : public ALabel {
Custom(const std::string, const Json::Value&);
auto update() -> void;
private:
void worker();
void delayWorker();
void continuousWorker();
void parseOutputRaw();
void parseOutputJson();
const std::string name_;
std::string text_;
std::string tooltip_;
std::string class_;
std::string prevclass_;
waybar::util::SleeperThread thread_;
waybar::util::command::res output_;
waybar::util::JsonParser parser_;
};
}

View File

@ -1,7 +1,8 @@
#pragma once
#include <pulse/pulseaudio.h>
#include <fmt/format.h>
#include <pulse/pulseaudio.h>
#include <pulse/volume.h>
#include <algorithm>
#include "ALabel.hpp"
@ -18,6 +19,8 @@ class Pulseaudio : public ALabel {
static void contextStateCb(pa_context*, void*);
static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
static void volumeModifyCb(pa_context*, int, void*);
bool handleScroll(GdkEventScroll* e);
const std::string getPortIcon() const;
@ -26,9 +29,11 @@ class Pulseaudio : public ALabel {
pa_context* context_;
uint32_t sink_idx_{0};
uint16_t volume_;
pa_cvolume pa_volume_;
bool muted_;
std::string port_name_;
std::string desc_;
bool scrolling_;
};
}
} // namespace waybar::modules

View File

@ -0,0 +1,38 @@
#pragma once
#include <gtkmm.h>
#include <json/json.h>
#include <tuple>
#include <dbus-status-notifier-watcher.h>
#include "modules/sni/sni.hpp"
namespace waybar::modules::SNI {
class Host {
public:
Host(Glib::Dispatcher*, const Json::Value&);
std::vector<Item> items;
private:
static void busAcquired(GDBusConnection*, const gchar*, gpointer);
static void nameAppeared(GDBusConnection*, const gchar*, const gchar*,
gpointer);
static void nameVanished(GDBusConnection*, const gchar*, gpointer);
static void proxyReady(GObject*, GAsyncResult*, gpointer);
static void registerHost(GObject*, GAsyncResult*, gpointer);
static void itemRegistered(SnWatcher*, const gchar*, gpointer);
static void itemUnregistered(SnWatcher*, const gchar*, gpointer);
std::tuple<std::string, std::string> getBusNameAndObjectPath(const gchar*);
void addRegisteredItem(const gchar* service);
uint32_t bus_name_id_;
uint32_t watcher_id_;
std::string bus_name_;
std::string object_path_;
Glib::Dispatcher* dp_;
GCancellable* cancellable_ = nullptr;
SnWatcher* watcher_ = nullptr;
const Json::Value &config_;
};
}

View File

@ -0,0 +1,59 @@
#pragma once
#include <dbus-status-notifier-item.h>
#include <gtkmm.h>
#include <json/json.h>
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
namespace waybar::modules::SNI {
class Item {
public:
Item(std::string, std::string, Glib::Dispatcher*, Json::Value);
std::string bus_name;
std::string object_path;
Gtk::EventBox event_box;
int icon_size;
int effective_icon_size;
Gtk::Image *image;
Gtk::Menu *gtk_menu = nullptr;
std::string category;
std::string id;
std::string status;
std::string title;
int32_t window_id;
std::string icon_name;
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
std::string overlay_icon_name;
std::string attention_icon_name;
std::string attention_movie_name;
std::string icon_theme_path;
std::string menu;
bool item_is_menu;
private:
static void proxyReady(GObject *obj, GAsyncResult *res, gpointer data);
static void getAll(GObject *obj, GAsyncResult *res, gpointer data);
static void handleActivate(GObject *, GAsyncResult *, gpointer);
static void handleSecondaryActivate(GObject *, GAsyncResult *, gpointer);
void updateImage();
void updateMenu();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant *variant);
Glib::RefPtr<Gdk::Pixbuf> getIconByName(std::string name, int size);
bool handleClick(GdkEventButton *const & /*ev*/);
Glib::Dispatcher *dp_;
GCancellable *cancellable_ = nullptr;
SnItem *proxy_ = nullptr;
Json::Value config_;
};
} // namespace waybar::modules::SNI

View File

@ -0,0 +1,47 @@
#pragma once
#include <gtkmm.h>
#include <dbus-status-notifier-watcher.h>
namespace waybar::modules::SNI {
class Watcher {
public:
Watcher();
~Watcher();
private:
typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType;
typedef struct {
GfWatchType type;
Watcher *watcher;
gchar *service;
gchar *bus_name;
gchar *object_path;
guint watch_id;
} GfWatch;
static void busAcquired(GDBusConnection *, const gchar *, gpointer);
static gboolean handleRegisterHost(Watcher *, GDBusMethodInvocation *,
const gchar *);
static gboolean handleRegisterItem(Watcher *, GDBusMethodInvocation *,
const gchar *);
static GfWatch *gfWatchFind(GSList *list, const gchar *bus_name,
const gchar *object_path);
static GfWatch *gfWatchNew(GfWatchType, const gchar *, const gchar *,
const gchar *, Watcher *);
static void nameVanished(GDBusConnection *connection, const char *name,
gpointer data);
void updateRegisteredItems(SnWatcher *obj);
uint32_t bus_name_id_;
uint32_t watcher_id_;
GSList *hosts_ = nullptr;
GSList *items_ = nullptr;
SnWatcher *watcher_ = nullptr;
};
} // namespace waybar::modules::SNI

View File

@ -0,0 +1,25 @@
#pragma once
#include <fmt/format.h>
#include <thread>
#include "util/json.hpp"
#include "IModule.hpp"
#include "modules/sni/snw.hpp"
#include "modules/sni/snh.hpp"
namespace waybar::modules::SNI {
class Tray : public IModule {
public:
Tray(const Json::Value&);
auto update() -> void;
operator Gtk::Widget &();
private:
std::thread thread_;
const Json::Value& config_;
Gtk::Box box_;
SNI::Watcher watcher_ ;
SNI::Host host_;
};
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>

View File

@ -0,0 +1,27 @@
#pragma once
#include <fmt/format.h>
#include "bar.hpp"
#include "client.hpp"
#include "util/chrono.hpp"
#include "util/json.hpp"
#include "ALabel.hpp"
#include "modules/sway/ipc/client.hpp"
namespace waybar::modules::sway {
class Mode : public ALabel {
public:
Mode(waybar::Bar&, const Json::Value&);
auto update() -> void;
private:
void worker();
Bar& bar_;
waybar::util::SleeperThread thread_;
util::JsonParser parser_;
Ipc ipc_;
std::string mode_;
};
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <fmt/format.h>
#include <tuple>
#include "bar.hpp"
#include "client.hpp"
#include "util/chrono.hpp"
@ -16,7 +17,7 @@ class Window : public ALabel {
auto update() -> void;
private:
void worker();
std::string getFocusedNode(Json::Value nodes);
std::tuple<int, std::string> getFocusedNode(Json::Value nodes);
void getFocusedWindow();
Bar& bar_;
@ -24,6 +25,7 @@ class Window : public ALabel {
util::JsonParser parser_;
Ipc ipc_;
std::string window_;
int windowId_;
};
}

View File

@ -70,7 +70,7 @@ struct SleeperThread {
condvar_.notify_all();
}
~SleeperThread()
auto stop()
{
do_run_ = false;
condvar_.notify_all();
@ -79,6 +79,11 @@ struct SleeperThread {
}
}
~SleeperThread()
{
stop();
}
private:
std::thread thread_;
std::condition_variable condvar_;

View File

@ -29,7 +29,23 @@ inline struct res exec(const std::string cmd)
output.erase(output.length()-1);
}
int exit_code = WEXITSTATUS(pclose(fp));
return { exit_code, output };
return {exit_code, output};
}
inline bool forkExec(std::string cmd) {
if (cmd == "") return true;
int32_t pid = fork();
if (pid < 0) {
printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno));
return false;
}
// Child executes the command
if (!pid) execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
return true;
}
} // namespace waybar::util::command

View File

@ -1,6 +1,6 @@
project(
'waybar', 'cpp', 'c',
version: '0.0.5',
version: '0.2.0',
license: 'MIT',
default_options : [
'cpp_std=c++17',
@ -32,11 +32,13 @@ wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
wlroots = dependency('wlroots', fallback: ['wlroots', 'wlroots'])
gtkmm = dependency('gtkmm-3.0')
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk'))
jsoncpp = dependency('jsoncpp')
sigcpp = dependency('sigc++-2.0')
libnl = dependency('libnl-3.0', required: false)
libnlgen = dependency('libnl-genl-3.0', required: false)
libpulse = dependency('libpulse', required: false)
libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
src_files = files(
'src/factory.cpp',
@ -55,6 +57,7 @@ if find_program('sway', required : false).found()
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [
'src/modules/sway/ipc/client.cpp',
'src/modules/sway/mode.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
]
@ -70,6 +73,22 @@ if libpulse.found()
src_files += 'src/modules/pulseaudio.cpp'
endif
if dbusmenu_gtk.found()
add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp')
src_files += files(
'src/modules/sni/tray.cpp',
'src/modules/sni/snw.cpp',
'src/modules/sni/snh.cpp',
'src/modules/sni/sni.cpp'
)
endif
compiler = meson.get_compiler('cpp')
if not compiler.has_header('filesystem')
add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp')
endif
subdir('protocol')
executable(
@ -86,9 +105,11 @@ executable(
libinput,
wayland_cursor,
gtkmm,
dbusmenu_gtk,
giounix,
libnl,
libnlgen,
libpulse,
libpulse
],
include_directories: [include_directories('include')],
install: true,
@ -97,7 +118,7 @@ executable(
install_data(
'./resources/config',
'./resources/style.css',
install_dir: '/etc/xdg/waybar',
install_dir: join_paths(get_option('out'), 'etc/xdg/waybar')
)
clangtidy = find_program('clang-tidy', required: false)

4
meson_options.txt Normal file
View File

@ -0,0 +1,4 @@
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
option('out', type: 'string', value : '/', description: 'output prefix directory')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 22 KiB

69
protocol/dbus-menu.xml Normal file
View File

@ -0,0 +1,69 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="com.canonical.dbusmenu">
<!-- Properties -->
<property name="Version" type="u" access="read" />
<property name="TextDirection" type="s" access="read" />
<property name="Status" type="s" access="read" />
<property name="IconThemePath" type="as" access="read" />
<!-- Functions -->
<method name="GetLayout">
<arg type="i" name="parentId" direction="in" />
<arg type="i" name="recursionDepth" direction="in" />
<arg type="as" name="propertyNames" direction="in" />
<arg type="u" name="revision" direction="out" />
<arg type="(ia{sv}av)" name="layout" direction="out" />
</method>
<method name="GetGroupProperties">
<arg type="ai" name="ids" direction="in" />
<arg type="as" name="propertyNames" direction="in" />
<arg type="a(ia{sv})" name="properties" direction="out" />
</method>
<method name="GetProperty">
<arg type="i" name="id" direction="in" />
<arg type="s" name="name" direction="in" />
<arg type="v" name="value" direction="out" />
</method>
<method name="Event">
<arg type="i" name="id" direction="in" />
<arg type="s" name="eventId" direction="in" />
<arg type="v" name="data" direction="in" />
<arg type="u" name="timestamp" direction="in" />
</method>
<method name="EventGroup">
<arg type="a(isvu)" name="events" direction="in" />
<arg type="ai" name="idErrors" direction="out" />
</method>
<method name="AboutToShow">
<arg type="i" name="id" direction="in" />
<arg type="b" name="needUpdate" direction="out" />
</method>
<method name="AboutToShowGroup">
<arg type="ai" name="ids" direction="in" />
<arg type="ai" name="updatesNeeded" direction="out" />
<arg type="ai" name="idErrors" direction="out" />
</method>
<!-- Signals -->
<signal name="ItemsPropertiesUpdated">
<arg type="a(ia{sv})" name="updatedProps" direction="out" />
<arg type="a(ias)" name="removedProps" direction="out" />
</signal>
<signal name="LayoutUpdated">
<arg type="u" name="revision" direction="out" />
<arg type="i" name="parent" direction="out" />
</signal>
<signal name="ItemActivationRequested">
<arg type="i" name="id" direction="out" />
<arg type="u" name="timestamp" direction="out" />
</signal>
</interface>
</node>

View File

@ -0,0 +1,78 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierItem">
<annotation name="org.gtk.GDBus.C.Name" value="Item" />
<property name="Category" type="s" access="read"/>
<property name="Id" type="s" access="read"/>
<property name="Title" type="s" access="read"/>
<property name="Status" type="s" access="read"/>
<property name="WindowId" type="i" access="read"/>
<property name="Menu" type="o" access="read" />
<!-- main icon -->
<!-- names are preferred over pixmaps -->
<property name="IconName" type="s" access="read" />
<property name="IconThemePath" type="s" access="read" />
<!-- struct containing width, height and image data-->
<!-- implementation has been dropped as of now -->
<property name="IconPixmap" type="a(iiay)" access="read" />
<!-- not used in ayatana code, no test case so far -->
<property name="OverlayIconName" type="s" access="read"/>
<property name="OverlayIconPixmap" type="a(iiay)" access="read" />
<!-- Requesting attention icon -->
<property name="AttentionIconName" type="s" access="read"/>
<!--same definition as image-->
<property name="AttentionIconPixmap" type="a(iiay)" access="read" />
<!-- tooltip data -->
<!-- unimplemented as of now -->
<!--(iiay) is an image-->
<property name="ToolTip" type="(sa(iiay)ss)" access="read" />
<!-- interaction: actually, we do not use them. -->
<method name="Activate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="SecondaryActivate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Scroll">
<arg name="delta" type="i" direction="in"/>
<arg name="dir" type="s" direction="in"/>
</method>
<!-- Signals: the client wants to change something in the status-->
<signal name="NewTitle"></signal>
<signal name="NewIcon"></signal>
<signal name="NewIconThemePath">
<arg type="s" name="icon_theme_path" direction="out" />
</signal>
<signal name="NewAttentionIcon"></signal>
<signal name="NewOverlayIcon"></signal>
<signal name="NewToolTip"></signal>
<signal name="NewStatus">
<arg name="status" type="s" />
</signal>
<!-- ayatana labels -->
<!-- These are commented out because GDBusProxy would otherwise require them,
but they are not available for KDE indicators
-->
<!--<signal name="XAyatanaNewLabel">
<arg type="s" name="label" direction="out" />
<arg type="s" name="guide" direction="out" />
</signal>
<property name="XAyatanaLabel" type="s" access="read" />
<property name="XAyatanaLabelGuide" type="s" access="read" />-->
</interface>
</node>

View File

@ -0,0 +1,52 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierWatcher">
<annotation name="org.gtk.GDBus.C.Name" value="Watcher" />
<!-- methods -->
<method name="RegisterStatusNotifierItem">
<annotation name="org.gtk.GDBus.C.Name" value="RegisterItem" />
<arg name="service" type="s" direction="in"/>
</method>
<method name="RegisterStatusNotifierHost">
<annotation name="org.gtk.GDBus.C.Name" value="RegisterHost" />
<arg name="service" type="s" direction="in"/>
</method>
<!-- properties -->
<property name="RegisteredStatusNotifierItems" type="as" access="read">
<annotation name="org.gtk.GDBus.C.Name" value="RegisteredItems" />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringList"/>
</property>
<property name="IsStatusNotifierHostRegistered" type="b" access="read">
<annotation name="org.gtk.GDBus.C.Name" value="IsHostRegistered" />
</property>
<property name="ProtocolVersion" type="i" access="read"/>
<!-- signals -->
<signal name="StatusNotifierItemRegistered">
<annotation name="org.gtk.GDBus.C.Name" value="ItemRegistered" />
<arg type="s" direction="out" name="service" />
</signal>
<signal name="StatusNotifierItemUnregistered">
<annotation name="org.gtk.GDBus.C.Name" value="ItemUnregistered" />
<arg type="s" direction="out" name="service" />
</signal>
<signal name="StatusNotifierHostRegistered">
<annotation name="org.gtk.GDBus.C.Name" value="HostRegistered" />
</signal>
<signal name="StatusNotifierHostUnregistered">
<annotation name="org.gtk.GDBus.C.Name" value="HostUnregistered" />
</signal>
</interface>
</node>

View File

@ -36,10 +36,66 @@ foreach p : client_protocols
client_protos_headers += wayland_scanner_client.process(xml)
endforeach
gdbus_codegen = find_program('gdbus-codegen')
r = run_command(gdbus_codegen, '--body', '--output', '/dev/null')
if r.returncode() != 0
gdbus_code_dsnw = custom_target(
'dbus-status-notifier-watcher.[ch]',
output: ['@BASENAME@.c','@BASENAME@.h'],
input: './dbus-status-notifier-watcher.xml',
command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
gdbus_code_dsni = custom_target(
'dbus-status-notifier-item.[ch]',
output: ['@BASENAME@.c','@BASENAME@.h'],
input: './dbus-status-notifier-item.xml',
command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
gdbus_code_dm = custom_target(
'dbus-menu.[ch]',
output: ['@BASENAME@.c','@BASENAME@.h'],
input: './dbus-menu.xml',
command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],
)
client_protos_src += gdbus_code_dsnw[0]
client_protos_headers += gdbus_code_dsnw[1]
client_protos_src += gdbus_code_dsni[0]
client_protos_headers += gdbus_code_dsni[1]
client_protos_src += gdbus_code_dm[0]
client_protos_headers += gdbus_code_dm[1]
else
gdbus_code = generator(
gdbus_codegen,
output: '@BASENAME@.c',
arguments: ['--c-namespace', 'Sn', '--body', '--output', '@OUTPUT@', '@INPUT@']
)
gdbus_header = generator(
gdbus_codegen,
output: '@BASENAME@.h',
arguments: ['--c-namespace', 'Sn', '--header', '--output', '@OUTPUT@', '@INPUT@']
)
client_protos_src += gdbus_code.process('./dbus-status-notifier-watcher.xml')
client_protos_headers += gdbus_header.process('./dbus-status-notifier-watcher.xml')
client_protos_src += gdbus_code.process('./dbus-status-notifier-item.xml')
client_protos_headers += gdbus_header.process('./dbus-status-notifier-item.xml')
client_protos_src += gdbus_code.process('./dbus-menu.xml')
client_protos_headers += gdbus_header.process('./dbus-menu.xml')
endif
lib_client_protos = static_library(
'client_protos',
client_protos_src + client_protos_headers,
dependencies: [wayland_client]
dependencies: [wayland_client, gtkmm, giounix],
include_directories: include_directories('..'),
) # for the include directory
client_protos = declare_dependency(

View File

@ -4,13 +4,14 @@
// "height": 30, // Waybar height
// "width": 1280, // Waybar width
// Choose the order of the modules
"modules-left": ["sway/workspaces", "custom/spotify"],
"modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"],
"modules-center": ["sway/window"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "clock"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration
// "sway/workspaces": {
// "disable-scroll": true,
// "all-outputs": true,
// "format": "{name}: {icon}",
// "format-icons": {
// "1": "",
// "2": "",
@ -22,9 +23,16 @@
// "default": ""
// }
// },
"sway/mode": {
"format": "{}"
},
"sway/window": {
"max-length": 50
},
"tray": {
// "icon-size": 21,
"spacing": 10
},
"clock": {
"format-alt": "{:%Y-%m-%d}"
},
@ -35,9 +43,19 @@
"format": "{}% "
},
"battery": {
"states": {
// "good": 95,
"warning": 30,
"critical": 15
},
"format": "{capacity}% {icon}",
// "format-good": "", // An empty format will hide the module
// "format-full": "",
"format-icons": ["", "", "", "", ""]
},
"battery#bat2": {
"bat": "BAT2"
},
"network": {
// "interface": "wlp2s0", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ",
@ -56,12 +74,14 @@
"portable": "",
"car": "",
"default": ["", ""]
}
},
"on-click": "pavucontrol"
},
"custom/spotify": {
"format": " {}",
"max-length": 40,
"exec": "$HOME/.config/waybar/mediaplayer.sh", // Script in resources folder
"interval": 30, // Remove this if your script is endless and write in loop
"exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder
"exec-if": "pgrep spotify"
}
}

View File

@ -3,9 +3,10 @@
border-radius: 0;
font-family: Roboto, Helvetica, Arial, sans-serif;
font-size: 13px;
min-height: 0;
}
window {
window#waybar {
background: rgba(43, 48, 59, 0.5);
border-bottom: 3px solid rgba(100, 114, 125, 0.5);
color: white;
@ -18,16 +19,17 @@ window {
border-bottom: 3px solid transparent;
}
#workspaces button.icon label {
font-size: 10px;
}
#workspaces button.focused {
background: #64727D;
border-bottom: 3px solid white;
}
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify {
#mode {
background: #64727D;
border-bottom: 3px solid white;
}
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify, #tray, #mode {
padding: 0 10px;
margin: 0 5px;
}
@ -53,7 +55,7 @@ window {
}
}
#battery.warning {
#battery.warning:not(.charging) {
background: #f53c3c;
color: white;
animation-name: blink;
@ -94,3 +96,7 @@ window {
background: #66cc99;
color: #2a5c45;
}
#tray {
background-color: #2980b9;
}

View File

@ -1,46 +1,98 @@
#include "ALabel.hpp"
#include <util/command.hpp>
#include <iostream>
waybar::ALabel::ALabel(const Json::Value& config, const std::string format)
: config_(config),
format_(config_["format"] ? config_["format"].asString() : format),
format_(config_["format"].isString() ? config_["format"].asString() : format),
default_format_(format_)
{
event_box_.add(label_);
if (config_["max-length"]) {
if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
}
if (config_["format-alt"]) {
if (config_["format-alt"].isString()) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event()
.connect(sigc::mem_fun(*this, &ALabel::handleToggle));
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &ALabel::handleToggle));
}
// configure events' user commands
if (config_["on-click"].isString()) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &ALabel::handleToggle));
}
if (config_["on-scroll-up"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &ALabel::handleScroll));
}
if (config_["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &ALabel::handleScroll));
}
}
auto waybar::ALabel::update() -> void
{
auto waybar::ALabel::update() -> void {
// Nothing here
}
bool waybar::ALabel::handleToggle(GdkEventButton* const& /*ev*/)
{
alt = !alt;
if (alt) {
format_ = config_["format-alt"].asString();
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
if (config_["on-click"].isString() && e->button == 1) {
waybar::util::command::forkExec(config_["on-click"].asString());
} else {
format_ = default_format_;
alt = !alt;
if (alt) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
}
}
dp.emit();
return true;
}
bool waybar::ALabel::handleScroll(GdkEventScroll* e) {
// Avoid concurrent scroll event
std::lock_guard<std::mutex> lock(mutex_);
bool direction_up = false;
if (e->direction == GDK_SCROLL_UP) {
direction_up = true;
}
if (e->direction == GDK_SCROLL_DOWN) {
direction_up = false;
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent*>(e),
&delta_x, &delta_y);
if (delta_y < 0) {
direction_up = true;
} else if (delta_y > 0) {
direction_up = false;
}
}
if (direction_up && config_["on-scroll-up"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-up"].asString());
} else if (config_["on-scroll-down"].isString()) {
waybar::util::command::forkExec(config_["on-scroll-down"].asString());
}
dp.emit();
return true;
}
std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt)
{
std::string waybar::ALabel::getIcon(uint16_t percentage,
const std::string& alt) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
if (!alt.empty() && format_icons[alt]) {
if (!alt.empty() && format_icons[alt].isString()) {
format_icons = format_icons[alt];
} else {
format_icons = format_icons["default"];
@ -57,6 +109,4 @@ std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt)
return "";
}
waybar::ALabel::operator Gtk::Widget &() {
return event_box_;
}
waybar::ALabel::operator Gtk::Widget&() { return event_box_; }

View File

@ -20,14 +20,15 @@ waybar::Bar::Bar(const Client& client,
zxdg_output_manager_v1_get_xdg_output(client.xdg_output_manager, *output);
zxdg_output_v1_add_listener(xdg_output_, &xdgOutputListener, this);
window.set_title("waybar");
window.set_name("waybar");
window.set_decorated(false);
window.set_resizable(false);
setupConfig();
setupCss();
setupWidgets();
Gtk::Widget& wrap(window);
gtk_widget_realize(wrap.gobj());
GdkWindow *gdk_window = gtk_widget_get_window(wrap.gobj());
auto wrap = reinterpret_cast<GtkWidget*>(window.gobj());
gtk_widget_realize(wrap);
GdkWindow *gdk_window = gtk_widget_get_window(wrap);
gdk_wayland_window_set_use_custom_surface(gdk_window);
surface = gdk_wayland_window_get_wl_surface(gdk_window);
@ -51,13 +52,15 @@ waybar::Bar::Bar(const Client& client,
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
}
auto height = config_["height"] ? config_["height"].asUInt() : height_;
auto width = config_["width"] ? config_["width"].asUInt() : width_;
auto height = config_["height"].isUInt() ? config_["height"].asUInt() : height_;
auto width = config_["width"].isUInt() ? config_["width"].asUInt() : width_;
zwlr_layer_surface_v1_set_anchor(layer_surface, anchor);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, height);
zwlr_layer_surface_v1_set_size(layer_surface, width, height);
wl_surface_commit(surface);
setupWidgets();
}
void waybar::Bar::handleLogicalPosition(void* /*data*/,
@ -102,11 +105,20 @@ void waybar::Bar::layerSurfaceHandleConfigure(void* 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_);
int dummy_width, min_height;
o->window.get_size(dummy_width, min_height);
if (o->height_ < static_cast<uint32_t>(min_height)) {
std::cout << fmt::format("Requested height: {} exceeds the minimum \
height: {} required by the modules", o->height_, min_height) << std::endl;
o->height_ = min_height;
}
std::cout << fmt::format(
"Bar configured (width: {}, height: {}) for output: {}",
o->width_, o->height_, o->output_name) << std::endl;
o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_);
zwlr_layer_surface_v1_set_exclusive_zone(surface, o->height_);
zwlr_layer_surface_v1_set_size(surface, o->width_, o->height_);
wl_surface_commit(o->surface);
@ -161,7 +173,7 @@ auto waybar::Bar::setupCss() -> void
void waybar::Bar::getModules(const Factory& factory, const std::string& pos)
{
if (config_[pos]) {
if (config_[pos].isArray()) {
for (const auto &name : config_[pos]) {
try {
auto module = factory.makeModule(name.asString());
@ -198,14 +210,14 @@ auto waybar::Bar::setupWidgets() -> void
getModules(factory, "modules-left");
getModules(factory, "modules-center");
getModules(factory, "modules-right");
for (auto& module : modules_left_) {
for (auto const& module : modules_left_) {
left.pack_start(*module, false, true, 0);
}
for (auto& module : modules_center_) {
for (auto const& module : modules_center_) {
center.pack_start(*module, true, true, 0);
}
std::reverse(modules_right_.begin(), modules_right_.end());
for (auto& module : modules_right_) {
for (auto const& module : modules_right_) {
right.pack_end(*module, false, false, 0);
}
}

View File

@ -7,38 +7,47 @@ waybar::Factory::Factory(Bar& bar, const Json::Value& config)
waybar::IModule* waybar::Factory::makeModule(const std::string &name) const
{
try {
if (name == "battery") {
auto ref = name.substr(0, name.find("#"));
if (ref == "battery") {
return new waybar::modules::Battery(config_[name]);
}
#ifdef HAVE_SWAY
if (name == "sway/workspaces") {
if (ref == "sway/mode") {
return new waybar::modules::sway::Mode(bar_, config_[name]);
}
if (ref == "sway/workspaces") {
return new waybar::modules::sway::Workspaces(bar_, config_[name]);
}
if (name == "sway/window") {
if (ref == "sway/window") {
return new waybar::modules::sway::Window(bar_, config_[name]);
}
#endif
if (name == "memory") {
if (ref == "memory") {
return new waybar::modules::Memory(config_[name]);
}
if (name == "cpu") {
if (ref == "cpu") {
return new waybar::modules::Cpu(config_[name]);
}
if (name == "clock") {
if (ref == "clock") {
return new waybar::modules::Clock(config_[name]);
}
#ifdef HAVE_DBUSMENU
if (ref == "tray") {
return new waybar::modules::SNI::Tray(config_[name]);
}
#endif
#ifdef HAVE_LIBNL
if (name == "network") {
if (ref == "network") {
return new waybar::modules::Network(config_[name]);
}
#endif
#ifdef HAVE_LIBPULSE
if (name == "pulseaudio") {
if (ref == "pulseaudio") {
return new waybar::modules::Pulseaudio(config_[name]);
}
#endif
if (name.compare(0, 7, "custom/") == 0 && name.size() > 7) {
return new waybar::modules::Custom(name.substr(7), config_[name]);
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), config_[name]);
}
} catch (const std::exception& e) {
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what());

View File

@ -4,26 +4,36 @@ waybar::modules::Battery::Battery(const Json::Value& config)
: ALabel(config, "{capacity}%")
{
try {
for (auto &node : fs::directory_iterator(data_dir_)) {
if (fs::is_directory(node) && fs::exists(node / "capacity")
&& fs::exists(node / "status") && fs::exists(node / "uevent")) {
batteries_.push_back(node);
if (config_["bat"].isString()) {
auto dir = data_dir_ / config_["bat"].asString();
if (fs::is_directory(dir) && fs::exists(dir / "capacity")
&& fs::exists(dir / "status") && fs::exists(dir / "uevent")) {
batteries_.push_back(dir);
}
} else {
for (auto const& node : fs::directory_iterator(data_dir_)) {
if (fs::is_directory(node) && fs::exists(node / "capacity")
&& fs::exists(node / "status") && fs::exists(node / "uevent")) {
batteries_.push_back(node);
}
}
}
} catch (fs::filesystem_error &e) {
throw std::runtime_error(e.what());
}
if (batteries_.empty()) {
if (config_["bat"].isString()) {
throw std::runtime_error("No battery named " + config_["bat"].asString());
}
throw std::runtime_error("No batteries.");
}
fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
for (auto &bat : batteries_) {
for (auto const& bat : batteries_) {
inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
}
label_.set_name("battery");
worker();
}
@ -36,22 +46,28 @@ void waybar::modules::Battery::worker()
{
// Trigger first values
update();
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 60;
threadTimer_ = [this, interval] {
thread_.sleep_for(chrono::seconds(interval));
dp.emit();
};
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
if (nbytes != sizeof(event)) {
return;
}
threadTimer_.stop();
dp.emit();
};
}
auto waybar::modules::Battery::update() -> void
std::tuple<uint16_t, std::string> waybar::modules::Battery::getInfos()
{
try {
uint16_t total = 0;
std::string status;
for (auto &bat : batteries_) {
std::string status = "Unknown";
for (auto const& bat : batteries_) {
uint16_t capacity;
std::string _status;
std::ifstream(bat / "capacity") >> capacity;
@ -62,22 +78,64 @@ auto waybar::modules::Battery::update() -> void
total += capacity;
}
uint16_t capacity = total / batteries_.size();
label_.set_text(fmt::format(format_, fmt::arg("capacity", capacity),
fmt::arg("icon", getIcon(capacity))));
label_.set_tooltip_text(status);
bool charging = status == "Charging";
if (charging) {
label_.get_style_context()->add_class("charging");
} else {
label_.get_style_context()->remove_class("charging");
}
auto critical = config_["critical"] ? config_["critical"].asUInt() : 15;
if (capacity <= critical && !charging) {
label_.get_style_context()->add_class("warning");
} else {
label_.get_style_context()->remove_class("warning");
}
return {capacity, status};
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return {0, "Unknown"};
}
}
std::string waybar::modules::Battery::getState(uint16_t capacity)
{
// Get current state
std::vector<std::pair<std::string, uint16_t>> states;
if (config_["states"].isObject()) {
for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) {
if (it->isUInt() && it.key().isString()) {
states.push_back({it.key().asString(), it->asUInt()});
}
}
}
// Sort states
std::sort(states.begin(), states.end(), [](auto &a, auto &b) {
return a.second < b.second;
});
std::string validState = "";
for (auto state : states) {
if (capacity <= state.second && validState.empty()) {
label_.get_style_context()->add_class(state.first);
validState = state.first;
} else {
label_.get_style_context()->remove_class(state.first);
}
}
return validState;
}
auto waybar::modules::Battery::update() -> void
{
auto [capacity, status] = getInfos();
label_.set_tooltip_text(status);
std::transform(status.begin(), status.end(), status.begin(), ::tolower);
auto format = format_;
auto state = getState(capacity);
label_.get_style_context()->remove_class(old_status_);
label_.get_style_context()->add_class(status);
old_status_ = status;
if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
format = config_["format-" + status + "-" + state].asString();
} else if (config_["format-" + status].isString()) {
format = config_["format-" + status].asString();
} else if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
label_.set_name("");
} else {
event_box_.show();
label_.set_name("battery");
label_.set_text(fmt::format(format, fmt::arg("capacity", capacity),
fmt::arg("icon", getIcon(capacity))));
}
}

View File

@ -4,7 +4,7 @@ waybar::modules::Clock::Clock(const Json::Value& config)
: ALabel(config, "{:%H:%M}")
{
label_.set_name("clock");
uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 60;
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 60;
thread_ = [this, interval] {
auto now = waybar::chrono::clock::now();
dp.emit();

View File

@ -4,7 +4,7 @@ waybar::modules::Cpu::Cpu(const Json::Value& config)
: ALabel(config, "{}%")
{
label_.set_name("cpu");
uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 10;
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 10;
thread_ = [this, interval] {
dp.emit();
thread_.sleep_for(chrono::seconds(interval));

View File

@ -4,18 +4,22 @@ waybar::modules::Custom::Custom(const std::string name,
const Json::Value& config)
: ALabel(config, "{}"), name_(name)
{
if (!config_["exec"]) {
if (!config_["exec"].isString()) {
throw std::runtime_error(name_ + " has no exec path.");
}
worker();
if (config_["interval"].isUInt()) {
delayWorker();
} else {
continuousWorker();
}
}
void waybar::modules::Custom::worker()
void waybar::modules::Custom::delayWorker()
{
uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 30;
auto interval = config_["interval"].asUInt();
thread_ = [this, interval] {
bool can_update = true;
if (config_["exec-if"]) {
if (config_["exec-if"].isString()) {
auto res = waybar::util::command::exec(config_["exec-if"].asString());
if (res.exit_code != 0) {
can_update = false;
@ -31,6 +35,35 @@ void waybar::modules::Custom::worker()
};
}
void waybar::modules::Custom::continuousWorker()
{
auto cmd = config_["exec"].asString();
FILE* fp(popen(cmd.c_str(), "r"));
if (!fp) {
throw std::runtime_error("Unable to open " + cmd);
}
thread_ = [this, fp] {
char* buff = nullptr;
size_t len = 0;
if (getline(&buff, &len, fp) == -1) {
pclose(fp);
thread_.stop();
output_ = { 1, "" };
dp.emit();
return;
}
std::string output = buff;
// Remove last newline
if (!output.empty() && output[output.length()-1] == '\n') {
output.erase(output.length()-1);
}
output_ = { 0, output };
dp.emit();
};
}
auto waybar::modules::Custom::update() -> void
{
// Hide label if output is empty
@ -39,9 +72,65 @@ auto waybar::modules::Custom::update() -> void
label_.set_name("");
} else {
label_.set_name("custom-" + name_);
auto str = fmt::format(format_, output_.out);
if (config_["return-type"].asString() == "json") {
parseOutputJson();
} else {
parseOutputRaw();
}
auto str = fmt::format(format_, text_);
label_.set_text(str);
label_.set_tooltip_text(str);
if (text_ == tooltip_) {
label_.set_tooltip_text(str);
} else {
label_.set_tooltip_text(tooltip_);
}
if (class_ != "") {
if (prevclass_ != "") {
label_.get_style_context()->remove_class(prevclass_);
}
label_.get_style_context()->add_class(class_);
prevclass_ = class_;
} else {
label_.get_style_context()->remove_class(prevclass_);
prevclass_ = "";
}
label_.show();
}
}
void waybar::modules::Custom::parseOutputRaw()
{
std::istringstream output(output_.out);
std::string line;
int i = 0;
while (getline(output, line)) {
if (i == 0) {
text_ = line;
tooltip_ = line;
class_ = "";
} else if (i == 1) {
tooltip_ = line;
} else if (i == 2) {
class_ = line;
} else {
break;
}
i++;
}
}
void waybar::modules::Custom::parseOutputJson()
{
std::istringstream output(output_.out);
std::string line;
while (getline(output, line)) {
auto parsed = parser_.parse(line);
text_ = parsed["text"].asString();
tooltip_ = parsed["tooltip"].asString();
class_ = parsed["class"].asString();
break;
}
}

View File

@ -4,7 +4,7 @@ waybar::modules::Memory::Memory(const Json::Value& config)
: ALabel(config, "{}%")
{
label_.set_name("memory");
uint32_t interval = config_["interval"] ? config_["inveral"].asUInt() : 30;
uint32_t interval = config_["interval"].isUInt() ? config_["interval"].asUInt() : 30;
thread_ = [this, interval] {
dp.emit();
thread_.sleep_for(chrono::seconds(interval));

View File

@ -14,7 +14,7 @@ waybar::modules::Network::Network(const Json::Value& config)
sizeof(nladdr_)) != 0) {
throw std::runtime_error("Can't bind network socket");
}
if (config_["interface"]) {
if (config_["interface"].isString()) {
ifid_ = if_nametoindex(config_["interface"].asCString());
ifname_ = config_["interface"].asString();
if (ifid_ <= 0) {
@ -56,7 +56,7 @@ waybar::modules::Network::Network(const Json::Value& config)
}
}
}
if (ifid_ <= 0 && !config_["interface"]) {
if (ifid_ <= 0 && !config_["interface"].isString()) {
// Need to wait before get external interface
thread_.sleep_for(std::chrono::seconds(1));
ifid_ = getExternalInterface();
@ -84,15 +84,15 @@ auto waybar::modules::Network::update() -> void
{
auto format = format_;
if (ifid_ <= 0) {
format = config_["format-disconnected"]
format = config_["format-disconnected"].isString()
? config_["format-disconnected"].asString() : format;
label_.get_style_context()->add_class("disconnected");
} else {
if (essid_.empty()) {
format = config_["format-ethernet"]
format = config_["format-ethernet"].isString()
? config_["format-ethernet"].asString() : format;
} else {
format = config_["format-wifi"]
format = config_["format-wifi"].isString()
? config_["format-wifi"].asString() : format;
}
label_.get_style_context()->remove_class("disconnected");
@ -142,7 +142,7 @@ int waybar::modules::Network::getExternalInterface()
int ifidx = -1;
/* Prepare request. */
uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0};
/* Build the RTM_GETROUTE request. */

View File

@ -1,9 +1,14 @@
#include "modules/pulseaudio.hpp"
waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config)
: ALabel(config, "{volume}%"), mainloop_(nullptr), mainloop_api_(nullptr),
context_(nullptr), sink_idx_(0), volume_(0), muted_(false)
{
waybar::modules::Pulseaudio::Pulseaudio(const Json::Value &config)
: ALabel(config, "{volume}%"),
mainloop_(nullptr),
mainloop_api_(nullptr),
context_(nullptr),
sink_idx_(0),
volume_(0),
muted_(false),
scrolling_(false) {
label_.set_name("pulseaudio");
mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) {
@ -26,10 +31,18 @@ waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config)
throw std::runtime_error("pa_mainloop_run() failed.");
}
pa_threaded_mainloop_unlock(mainloop_);
// define the pulse scroll events only when no user provided
// events are configured
if (!config["on-scroll-up"].isString() &&
!config["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Pulseaudio::handleScroll));
}
}
waybar::modules::Pulseaudio::~Pulseaudio()
{
waybar::modules::Pulseaudio::~Pulseaudio() {
mainloop_api_->quit(mainloop_api_, 0);
pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_free(mainloop_);
@ -58,6 +71,47 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data)
}
}
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
// Avoid concurrent scroll event
bool direction_up = false;
// XXX/TODO: Change of 100 corresponds to 1%, does that always hold true?
uint16_t change = 100;
pa_cvolume pa_volume = pa_volume_;
if (scrolling_) {
return false;
}
scrolling_ = true;
if (e->direction == GDK_SCROLL_UP) {
direction_up = true;
}
if (e->direction == GDK_SCROLL_DOWN) {
direction_up = false;
}
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), &delta_x,
&delta_y);
if (delta_y < 0) {
direction_up = true;
} else if (delta_y > 0) {
direction_up = false;
}
}
if (direction_up) {
if (volume_ + 1 < 100) pa_cvolume_inc(&pa_volume, change);
} else {
if (volume_ - 1 > 0) pa_cvolume_dec(&pa_volume, change);
}
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume,
volumeModifyCb, this);
return true;
}
/*
* Called when an event we subscribed to occurs.
*/
@ -75,21 +129,34 @@ void waybar::modules::Pulseaudio::subscribeCb(pa_context* context,
}
}
/*
* Called in response to a volume change request
*/
void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success,
void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (success) {
pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb,
data);
}
}
/*
* Called when the requested sink information is ready.
*/
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context* /*context*/,
const pa_sink_info* i, int /*eol*/, void* data)
{
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/,
const pa_sink_info *i, int /*eol*/,
void *data) {
if (i != nullptr) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
float volume = static_cast<float>(pa_cvolume_avg(&(i->volume)))
/ float{PA_VOLUME_NORM};
pa->pa_volume_ = i->volume;
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) /
float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index;
pa->volume_ = volume * 100.0f;
pa->volume_ = std::round(volume * 100.0f);
pa->muted_ = i->mute != 0;
pa->desc_ = i->description;
pa->port_name_ = i->active_port->name;
pa->port_name_ = i->active_port ? i->active_port->name : "Unknown";
pa->dp.emit();
}
}
@ -118,7 +185,7 @@ const std::string waybar::modules::Pulseaudio::getPortIcon() const
"hifi",
"phone",
};
for (auto port : ports) {
for (auto const& port : ports) {
if (port_name_.find(port) != std::string::npos) {
return port;
}
@ -131,18 +198,21 @@ auto waybar::modules::Pulseaudio::update() -> void
auto format = format_;
if (muted_) {
format =
config_["format-muted"] ? config_["format-muted"].asString() : format;
config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
label_.get_style_context()->add_class("muted");
} else if (port_name_.find("a2dp_sink") != std::string::npos) {
format = config_["format-bluetooth"]
format = config_["format-bluetooth"].isString()
? config_["format-bluetooth"].asString() : format;
label_.get_style_context()->add_class("bluetooth");
} else {
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->add_class("bluetooth");
}
label_.set_label(fmt::format(format,
fmt::arg("volume", volume_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
label_.set_label(
fmt::format(format, fmt::arg("volume", volume_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
label_.set_tooltip_text(desc_);
if (scrolling_) {
scrolling_ = false;
}
}

151
src/modules/sni/snh.cpp Normal file
View File

@ -0,0 +1,151 @@
#include "modules/sni/snh.hpp"
#include <iostream>
using namespace waybar::modules::SNI;
Host::Host(Glib::Dispatcher* dp, const Json::Value &config)
: dp_(dp), config_(config)
{
GBusNameOwnerFlags flags = static_cast<GBusNameOwnerFlags>(
G_BUS_NAME_OWNER_FLAGS_NONE);
bus_name_ = "org.kde.StatusNotifierHost-" + std::to_string(getpid());
object_path_ = "/StatusNotifierHost";
bus_name_id_ = g_bus_own_name(G_BUS_TYPE_SESSION,
bus_name_.c_str(), flags,
&Host::busAcquired, nullptr, nullptr, this, nullptr);
}
void Host::busAcquired(GDBusConnection* connection,
const gchar* name, gpointer data)
{
auto host = static_cast<SNI::Host *>(data);
host->watcher_id_ = g_bus_watch_name(
G_BUS_TYPE_SESSION,
"org.kde.StatusNotifierWatcher",
G_BUS_NAME_WATCHER_FLAGS_NONE,
&Host::nameAppeared, &Host::nameVanished, data, nullptr);
}
void Host::nameAppeared(GDBusConnection* connection,
const gchar* name, const gchar* name_owner, gpointer data)
{
auto host = static_cast<SNI::Host *>(data);
if (host->cancellable_ != nullptr) {
// TODO
return;
}
host->cancellable_ = g_cancellable_new();
sn_watcher_proxy_new(
connection,
G_DBUS_PROXY_FLAGS_NONE,
"org.kde.StatusNotifierWatcher",
"/StatusNotifierWatcher",
host->cancellable_, &Host::proxyReady, data);
}
void Host::nameVanished(GDBusConnection* connection,
const gchar* name, gpointer data)
{
auto host = static_cast<SNI::Host *>(data);
g_cancellable_cancel(host->cancellable_);
g_clear_object(&host->cancellable_);
g_clear_object(&host->watcher_);
host->items.clear();
}
void Host::proxyReady(GObject* src, GAsyncResult* res,
gpointer data)
{
GError* error = nullptr;
SnWatcher* watcher = sn_watcher_proxy_new_finish(res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
auto host = static_cast<SNI::Host *>(data);
host->watcher_ = watcher;
if (error != nullptr) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
sn_watcher_call_register_host(
host->watcher_, host->object_path_.c_str(), host->cancellable_,
&Host::registerHost, data);
}
void Host::registerHost(GObject* src, GAsyncResult* res,
gpointer data)
{
GError* error = nullptr;
sn_watcher_call_register_host_finish(SN_WATCHER(src), res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
auto host = static_cast<SNI::Host *>(data);
if (error != nullptr) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
g_signal_connect(host->watcher_, "item-registered",
G_CALLBACK(&Host::itemRegistered), data);
g_signal_connect(host->watcher_, "item-unregistered",
G_CALLBACK(&Host::itemUnregistered), data);
auto items = sn_watcher_dup_registered_items(host->watcher_);
if (items) {
for (uint32_t i = 0; items[i] != nullptr; i += 1) {
host->addRegisteredItem(items[i]);
}
}
g_strfreev(items);
}
void Host::itemRegistered(
SnWatcher* watcher, const gchar* service, gpointer data)
{
auto host = static_cast<SNI::Host *>(data);
host->addRegisteredItem(service);
}
void Host::itemUnregistered(
SnWatcher* watcher, const gchar* service, gpointer data)
{
auto host = static_cast<SNI::Host *>(data);
auto [bus_name, object_path] = host->getBusNameAndObjectPath(service);
for (auto it = host->items.begin(); it != host->items.end(); ++it) {
if (it->bus_name == bus_name && it->object_path == object_path) {
host->items.erase(it);
break;
}
}
host->dp_->emit();
}
std::tuple<std::string, std::string> Host::getBusNameAndObjectPath(
const gchar* service)
{
std::string bus_name;
std::string object_path;
gchar* tmp = g_strstr_len(service, -1, "/");
if (tmp != nullptr) {
gchar** str = g_strsplit(service, "/", 2);
bus_name = str[0];
object_path = tmp;
g_strfreev(str);
} else {
bus_name = service;
object_path = "/StatusNotifierItem";
}
return { bus_name, object_path };
}
void Host::addRegisteredItem(const gchar* service)
{
auto [bus_name, object_path] = getBusNameAndObjectPath(service);
items.emplace_back(bus_name, object_path, dp_, config_);
}

278
src/modules/sni/sni.cpp Normal file
View File

@ -0,0 +1,278 @@
#include "modules/sni/sni.hpp"
#include <iostream>
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
waybar::modules::SNI::Item::Item(std::string bn, std::string op,
Glib::Dispatcher *dp, Json::Value config)
: bus_name(bn), object_path(op), event_box(), icon_size(16),
effective_icon_size(0), image(Gtk::manage(new Gtk::Image())),
dp_(dp), config_(config)
{
if (config_["icon-size"].isUInt()) {
icon_size = config_["icon-size"].asUInt();
}
event_box.add(*image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK);
event_box.signal_button_press_event().connect(
sigc::mem_fun(*this, &Item::handleClick));
cancellable_ = g_cancellable_new();
sn_item_proxy_new_for_bus(
G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, bus_name.c_str(),
object_path.c_str(), cancellable_, &Item::proxyReady, this);
}
void waybar::modules::SNI::Item::proxyReady(GObject *obj, GAsyncResult *res,
gpointer data) {
GError *error = nullptr;
SnItem *proxy = sn_item_proxy_new_for_bus_finish(res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_error_free(error);
return;
}
auto item = static_cast<SNI::Item *>(data);
item->proxy_ = proxy;
if (error) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
auto conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(proxy));
g_dbus_connection_call(conn, item->bus_name.c_str(),
item->object_path.c_str(),
"org.freedesktop.DBus.Properties", "GetAll",
g_variant_new("(s)", "org.kde.StatusNotifierItem"),
G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1,
item->cancellable_, &Item::getAll, data);
}
void waybar::modules::SNI::Item::getAll(GObject *obj, GAsyncResult *res,
gpointer data) {
GError *error = nullptr;
auto conn = G_DBUS_CONNECTION(obj);
GVariant *properties = g_dbus_connection_call_finish(conn, res, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_error_free(error);
return;
}
auto item = static_cast<SNI::Item *>(data);
if (error) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
GVariantIter *it = nullptr;
g_variant_get(properties, "(a{sv})", &it);
gchar *key;
GVariant *value;
while (g_variant_iter_next(it, "{sv}", &key, &value)) {
if (g_strcmp0(key, "Category") == 0) {
item->category = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Id") == 0) {
item->id = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Title") == 0) {
item->title = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Status") == 0) {
item->status = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "WindowId") == 0) {
item->window_id = g_variant_get_int32(value);
} else if (g_strcmp0(key, "IconName") == 0) {
item->icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "IconPixmap") == 0) {
item->icon_pixmap = item->extractPixBuf(value);
} else if (g_strcmp0(key, "OverlayIconName") == 0) {
item->overlay_icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "OverlayIconPixmap") == 0) {
// TODO: overlay_icon_pixmap
} else if (g_strcmp0(key, "AttentionIconName") == 0) {
item->attention_icon_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "AttentionIconPixmap") == 0) {
// TODO: attention_icon_pixmap
} else if (g_strcmp0(key, "AttentionMovieName") == 0) {
item->attention_movie_name = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "ToolTip") == 0) {
// TODO: tooltip
} else if (g_strcmp0(key, "IconThemePath") == 0) {
item->icon_theme_path = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "Menu") == 0) {
item->menu = g_variant_dup_string(value, nullptr);
} else if (g_strcmp0(key, "ItemIsMenu") == 0) {
item->item_is_menu = g_variant_get_boolean(value);
}
g_variant_unref(value);
g_free(key);
}
g_variant_iter_free(it);
g_variant_unref(properties);
if (item->id.empty() || item->category.empty() || item->status.empty()) {
std::cerr << "Invalid Status Notifier Item: " + item->bus_name + "," +
item->object_path
<< std::endl;
return;
}
if (!item->icon_theme_path.empty()) {
GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
gtk_icon_theme_append_search_path(icon_theme,
item->icon_theme_path.c_str());
}
item->updateImage();
item->updateMenu();
item->dp_->emit();
// TODO: handle change
}
Glib::RefPtr<Gdk::Pixbuf>
waybar::modules::SNI::Item::extractPixBuf(GVariant *variant) {
GVariantIter *it;
g_variant_get(variant, "a(iiay)", &it);
if (it == nullptr) {
return Glib::RefPtr<Gdk::Pixbuf>{};
}
GVariant *val;
gint lwidth = 0;
gint lheight = 0;
gint width;
gint height;
guchar *array = nullptr;
while (g_variant_iter_loop(it, "(ii@ay)", &width, &height, &val)) {
if (width > 0 && height > 0 && val != nullptr &&
width * height > lwidth * lheight) {
auto size = g_variant_get_size(val);
/* Sanity check */
if (size == 4U * width * height) {
/* Find the largest image */
gconstpointer data = g_variant_get_data(val);
if (data != nullptr) {
if (array != nullptr) {
g_free(array);
}
array = static_cast<guchar *>(g_memdup(data, size));
lwidth = width;
lheight = height;
}
}
}
}
g_variant_iter_free(it);
if (array != nullptr) {
/* argb to rgba */
for (uint32_t i = 0; i < 4U * lwidth * lheight; i += 4) {
guchar alpha = array[i];
array[i] = array[i + 1];
array[i + 1] = array[i + 2];
array[i + 2] = array[i + 3];
array[i + 3] = alpha;
}
return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB,
true, 8, lwidth, lheight, 4 * lwidth);
}
return Glib::RefPtr<Gdk::Pixbuf>{};
}
void waybar::modules::SNI::Item::updateMenu()
{
event_box.set_tooltip_text(title);
if (gtk_menu == nullptr && !menu.empty()) {
auto dbmenu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data());
if (dbmenu != nullptr) {
g_object_ref_sink(dbmenu);
gtk_menu = Glib::wrap(GTK_MENU(dbmenu), false);
gtk_menu->attach_to_widget(event_box);
}
}
}
void waybar::modules::SNI::Item::updateImage()
{
image->set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image->set_pixel_size(icon_size);
if (!icon_name.empty()) {
try {
// Try to find icons specified by path and filename
#ifdef FILESYSTEM_EXPERIMENTAL
if (std::experimental::filesystem::exists(icon_name)) {
#else
if (std::filesystem::exists(icon_name)) {
#endif
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for
// the tray
pixbuf = pixbuf->scale_simple(icon_size, icon_size,
Gdk::InterpType::INTERP_BILINEAR);
image->set(pixbuf);
}
} else {
image->set(getIconByName(icon_name, icon_size));
}
} catch (Glib::Error &e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
} else if (icon_pixmap) {
// An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size,
Gdk::InterpType::INTERP_BILINEAR);
image->set(icon_pixmap);
}
}
Glib::RefPtr<Gdk::Pixbuf>
waybar::modules::SNI::Item::getIconByName(std::string name, int request_size) {
int tmp_size = 0;
Glib::RefPtr<Gtk::IconTheme> icon_theme = Gtk::IconTheme::get_default();
icon_theme->rescan_if_needed();
auto sizes = icon_theme->get_icon_sizes(name.c_str());
for (auto const &size : sizes) {
// -1 == scalable
if (size == request_size || size == -1) {
tmp_size = request_size;
break;
} else if (size < request_size || size > tmp_size) {
tmp_size = size;
}
}
if (tmp_size == 0) {
tmp_size = request_size;
}
return icon_theme->load_icon(name.c_str(), tmp_size,
Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
}
void waybar::modules::SNI::Item::handleActivate(GObject *src, GAsyncResult *res,
gpointer data) {
auto item = static_cast<SNI::Item *>(data);
sn_item_call_activate_finish(item->proxy_, res, nullptr);
}
void waybar::modules::SNI::Item::handleSecondaryActivate(GObject *src,
GAsyncResult *res,
gpointer data) {
auto item = static_cast<SNI::Item *>(data);
sn_item_call_secondary_activate_finish(item->proxy_, res, nullptr);
}
bool waybar::modules::SNI::Item::handleClick(GdkEventButton *const &ev) {
if (ev->type == GDK_BUTTON_PRESS) {
if (gtk_menu && gtk_menu->get_children().size() > 0) {
#if GTK_CHECK_VERSION(3, 22, 0)
gtk_menu->popup_at_widget(reinterpret_cast<Gtk::Widget*>(&event_box),
Gdk::GRAVITY_NORTH_WEST, Gdk::GRAVITY_NORTH_WEST,
reinterpret_cast<GdkEvent*>(ev));
#else
gtk_menu->popup(ev->button, ev->time);
#endif
gtk_menu->set_state_flags(Gtk::STATE_FLAG_ACTIVE, false);
} else {
sn_item_call_activate(
proxy_, ev->x, ev->y, nullptr, &Item::handleActivate, this);
}
} else if (ev->type == GDK_2BUTTON_PRESS) {
sn_item_call_secondary_activate(
proxy_, ev->x, ev->y, nullptr, &Item::handleSecondaryActivate, this);
} else {
return false;
}
return true;
}

167
src/modules/sni/snw.cpp Normal file
View File

@ -0,0 +1,167 @@
#include "modules/sni/snw.hpp"
#include <iostream>
using namespace waybar::modules::SNI;
Watcher::Watcher()
{
GBusNameOwnerFlags flags = static_cast<GBusNameOwnerFlags>(
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT
| G_BUS_NAME_OWNER_FLAGS_REPLACE);
bus_name_id_ = g_bus_own_name(G_BUS_TYPE_SESSION,
"org.kde.StatusNotifierWatcher", flags,
&Watcher::busAcquired, nullptr, nullptr, this, nullptr);
watcher_ = sn_watcher_skeleton_new();
sn_watcher_set_protocol_version(watcher_, 1);
}
Watcher::~Watcher()
{
}
void Watcher::busAcquired(GDBusConnection* connection, const gchar* name,
gpointer data)
{
GError* error = nullptr;
auto host = static_cast<SNI::Watcher*>(data);
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(host->watcher_),
connection, "/StatusNotifierWatcher", &error);
if (error != nullptr) {
std::cerr << error->message << std::endl;
g_error_free(error);
return;
}
g_signal_connect_swapped(host->watcher_, "handle-register-item",
G_CALLBACK(&Watcher::handleRegisterItem), data);
g_signal_connect_swapped(host->watcher_, "handle-register-host",
G_CALLBACK(&Watcher::handleRegisterHost), data);
}
gboolean Watcher::handleRegisterHost(Watcher* obj,
GDBusMethodInvocation* invocation, const gchar* service)
{
const gchar* bus_name = service;
const gchar* object_path = "/StatusNotifierHost";
if (*service == '/') {
bus_name = g_dbus_method_invocation_get_sender(invocation);
object_path = service;
}
if (g_dbus_is_name(bus_name) == FALSE) {
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS, "D-Bus bus name '%s' is not valid", bus_name);
return TRUE;
}
auto watch = gfWatchFind(obj->hosts_, bus_name, object_path);
if (watch != nullptr) {
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS, "Status Notifier Host with bus name '%s' and object path '%s' is already registered",
bus_name, object_path);
return TRUE;
}
watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj);
obj->hosts_ = g_slist_prepend(obj->hosts_, watch);
if (!sn_watcher_get_is_host_registered(obj->watcher_)) {
sn_watcher_set_is_host_registered(obj->watcher_, TRUE);
sn_watcher_emit_host_registered(obj->watcher_);
}
sn_watcher_complete_register_host(obj->watcher_, invocation);
return TRUE;
}
gboolean Watcher::handleRegisterItem(Watcher* obj,
GDBusMethodInvocation* invocation, const gchar* service)
{
const gchar* bus_name = service;
const gchar* object_path = "/StatusNotifierItem";
if (*service == '/') {
bus_name = g_dbus_method_invocation_get_sender(invocation);
object_path = service;
}
if (g_dbus_is_name(bus_name) == FALSE) {
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS, "D-Bus bus name '%s' is not valid", bus_name);
return TRUE;
}
auto watch = gfWatchFind(obj->items_, bus_name, object_path);
if (watch != nullptr) {
g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered",
bus_name, object_path);
sn_watcher_complete_register_item(obj->watcher_, invocation);
return TRUE;
}
watch = gfWatchNew(GF_WATCH_TYPE_ITEM, service, bus_name, object_path, obj);
obj->items_ = g_slist_prepend(obj->items_, watch);
obj->updateRegisteredItems(obj->watcher_);
gchar* tmp = g_strdup_printf("%s%s", bus_name, object_path);
sn_watcher_emit_item_registered(obj->watcher_, tmp);
g_free(tmp);
sn_watcher_complete_register_item(obj->watcher_, invocation);
return TRUE;
}
Watcher::GfWatch* Watcher::gfWatchFind(GSList* list, const gchar* bus_name,
const gchar* object_path)
{
for (GSList* l = list; l != nullptr; l = g_slist_next (l)) {
GfWatch* watch = static_cast<GfWatch*>(l->data);
if (g_strcmp0 (watch->bus_name, bus_name) == 0
&& g_strcmp0 (watch->object_path, object_path) == 0) {
return watch;
}
}
return nullptr;
}
Watcher::GfWatch* Watcher::gfWatchNew(GfWatchType type, const gchar* service,
const gchar* bus_name, const gchar* object_path, Watcher* watcher)
{
GfWatch* watch = g_new0(GfWatch, 1);
watch->type = type;
watch->watcher = watcher;
watch->service = g_strdup(service);
watch->bus_name = g_strdup(bus_name);
watch->object_path = g_strdup(object_path);
watch->watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION, bus_name,
G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, &Watcher::nameVanished, watch,
nullptr);
return watch;
}
void Watcher::nameVanished(GDBusConnection* connection, const char* name,
gpointer data)
{
auto watch = static_cast<GfWatch *>(data);
if (watch->type == GF_WATCH_TYPE_HOST) {
watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch);
if (watch->watcher->hosts_ == nullptr) {
sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE);
sn_watcher_emit_host_registered(watch->watcher->watcher_);
}
} else if (watch->type == GF_WATCH_TYPE_ITEM) {
watch->watcher->items_ = g_slist_remove(watch->watcher->items_, watch);
watch->watcher->updateRegisteredItems(watch->watcher->watcher_);
gchar* tmp = g_strdup_printf("%s%s", watch->bus_name, watch->object_path);
sn_watcher_emit_item_unregistered(watch->watcher->watcher_, tmp);
g_free(tmp);
}
}
void Watcher::updateRegisteredItems(SnWatcher* obj)
{
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE("as"));
for (GSList* l = items_; l != nullptr; l = g_slist_next(l)) {
GfWatch* watch = static_cast<GfWatch*>(l->data);
gchar* item = g_strdup_printf("%s%s", watch->bus_name, watch->object_path);
g_variant_builder_add(&builder, "s", item);
g_free(item);
}
GVariant* variant = g_variant_builder_end(&builder);
const gchar** items = g_variant_get_strv(variant, nullptr);
sn_watcher_set_registered_items(obj, items);
g_variant_unref(variant);
g_free(items);
}

30
src/modules/sni/tray.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "modules/sni/tray.hpp"
#include <iostream>
waybar::modules::SNI::Tray::Tray(const Json::Value &config)
: config_(config), watcher_(), host_(&dp, config)
{
std::cout << "Tray is in beta, so there may be bugs or even be unusable." << std::endl;
if (config_["spacing"].isUInt()) {
box_.set_spacing(config_["spacing"].asUInt());
}
}
auto waybar::modules::SNI::Tray::update() -> void {
auto childrens = box_.get_children();
childrens.erase(childrens.begin(), childrens.end());
for (auto &item : host_.items) {
box_.pack_start(item.event_box);
}
if (box_.get_children().size() > 0) {
box_.set_name("tray");
box_.show_all();
} else {
box_.set_name("");
}
}
waybar::modules::SNI::Tray::operator Gtk::Widget &() {
return box_;
}

43
src/modules/sway/mode.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "modules/sway/mode.hpp"
waybar::modules::sway::Mode::Mode(Bar& bar, const Json::Value& config)
: ALabel(config, "{}"), bar_(bar)
{
ipc_.connect();
ipc_.subscribe("[ \"mode\" ]");
// Launch worker
worker();
}
void waybar::modules::sway::Mode::worker()
{
thread_ = [this] {
try {
auto res = ipc_.handleEvent();
auto parsed = parser_.parse(res.payload);
if ((parsed["change"]) != "default" ) {
mode_ = parsed["change"].asString();
dp.emit();
}
else if ((parsed["change"]) == "default" ) {
mode_.clear();
dp.emit();
}
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
};
}
auto waybar::modules::sway::Mode::update() -> void
{
if (mode_.empty()) {
label_.set_name("");
label_.hide();
} else {
label_.set_name("mode");
label_.set_text(fmt::format(format_, mode_));
label_.set_tooltip_text(mode_);
label_.show();
}
}

View File

@ -1,11 +1,11 @@
#include "modules/sway/window.hpp"
waybar::modules::sway::Window::Window(Bar &bar, const Json::Value& config)
: ALabel(config, "{}"), bar_(bar)
: ALabel(config, "{}"), bar_(bar), windowId_(-1)
{
label_.set_name("window");
ipc_.connect();
ipc_.subscribe("[ \"window\" ]");
ipc_.subscribe("[\"window\",\"workspace\"]");
getFocusedWindow();
// Launch worker
worker();
@ -20,6 +20,15 @@ void waybar::modules::sway::Window::worker()
if ((parsed["change"] == "focus" || parsed["change"] == "title")
&& parsed["container"]["focused"].asBool()) {
window_ = parsed["container"]["name"].asString();
windowId_ = parsed["container"]["id"].asInt();
dp.emit();
} else if ((parsed["change"] == "close"
&& parsed["container"]["focused"].asBool()
&& windowId_ == parsed["container"]["id"].asInt())
|| (parsed["change"] == "focus" && parsed["current"]["focus"].isArray()
&& parsed["current"]["focus"].empty())) {
window_.clear();
windowId_ = -1;
dp.emit();
}
} catch (const std::exception& e) {
@ -34,18 +43,19 @@ auto waybar::modules::sway::Window::update() -> void
label_.set_tooltip_text(window_);
}
std::string waybar::modules::sway::Window::getFocusedNode(Json::Value nodes)
std::tuple<int, std::string> waybar::modules::sway::Window::getFocusedNode(
Json::Value nodes)
{
for (auto &node : nodes) {
for (auto const& node : nodes) {
if (node["focused"].asBool() && node["type"] == "con") {
return node["name"].asString();
return { node["id"].asInt(), node["name"].asString() };
}
auto res = getFocusedNode(node["nodes"]);
if (!res.empty()) {
return res;
auto [id, name] = getFocusedNode(node["nodes"]);
if (id > -1 && !name.empty()) {
return { id, name };
}
}
return std::string();
return { -1, std::string() };
}
void waybar::modules::sway::Window::getFocusedWindow()
@ -53,7 +63,9 @@ void waybar::modules::sway::Window::getFocusedWindow()
try {
auto res = ipc_.sendCmd(IPC_GET_TREE);
auto parsed = parser_.parse(res.payload);
window_ = getFocusedNode(parsed["nodes"]);
auto [id, name] = getFocusedNode(parsed["nodes"]);
windowId_ = id;
window_ = name;
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Window::update));
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;

View File

@ -49,7 +49,7 @@ auto waybar::modules::sway::Workspaces::update() -> void
++it;
}
}
for (auto node : workspaces_) {
for (auto const& node : workspaces_) {
if (!config_["all-outputs"].asBool()
&& bar_.output_name != node["output"].asString()) {
continue;
@ -79,7 +79,14 @@ auto waybar::modules::sway::Workspaces::update() -> void
box_.reorder_child(button, node["num"].asInt());
}
auto icon = getIcon(node["name"].asString(), node);
button.set_label(icon);
if (config_["format"].isString()) {
auto format = config_["format"].asString();
button.set_label(fmt::format(format, fmt::arg("icon", icon),
fmt::arg("name", node["name"].asString()),
fmt::arg("index", node["num"].asString())));
} else {
button.set_label(icon);
}
button.show();
}
}
@ -91,11 +98,13 @@ auto waybar::modules::sway::Workspaces::update() -> void
void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node)
{
auto icon = getIcon(node["name"].asString(), node);
auto pair = buttons_.emplace(node["num"].asInt(), icon);
auto format = config_["format"].isString()
? fmt::format(config_["format"].asString(), fmt::arg("icon", icon),
fmt::arg("name", node["name"].asString()),
fmt::arg("index", node["num"].asString()))
: icon;
auto pair = buttons_.emplace(node["num"].asInt(), format);
auto &button = pair.first->second;
if (icon != node["name"].asString()) {
button.get_style_context()->add_class("icon");
}
box_.pack_start(button, false, false, 0);
button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, pair] {
@ -132,10 +141,10 @@ std::string waybar::modules::sway::Workspaces::getIcon(std::string name,
name, "urgent", "focused", "visible", "default"};
for (auto const& key : keys) {
if (key == "focused" || key == "visible" || key == "urgent") {
if (config_["format-icons"][key] && node[key].asBool()) {
if (config_["format-icons"][key].isString() && node[key].asBool()) {
return config_["format-icons"][key].asString();
}
} else if (config_["format-icons"][key]) {
} else if (config_["format-icons"][key].isString()) {
return config_["format-icons"][key].asString();
}
}

View File

@ -1,10 +1,10 @@
[wrap-file]
directory = fmt-4.1.0
directory = fmt-5.2.0
source_url = https://github.com/fmtlib/fmt/archive/4.1.0.tar.gz
source_filename = fmt-4.1.0.tar.gz
source_hash = 46628a2f068d0e33c716be0ed9dcae4370242df135aed663a180b9fd8e36733d
source_url = https://github.com/fmtlib/fmt/archive/5.2.0.tar.gz
source_filename = fmt-5.2.0.tar.gz
source_hash = b0e8c71a8fb906123966686f788e83cd95ae499afe9c25ff6284f624488435ac
patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/4.1.0/1/get_zip
patch_filename = fmt-4.1.0-1-wrap.zip
patch_hash = 741931f01e558491724fc1c67bff996d1df79c0277626fc463de138052c9ecc0
patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/5.2.0/1/get_zip
patch_filename = fmt-5.2.0-1-wrap.zip
patch_hash = 04005310ad939bc640ca0eb918e6b5390dbd5b5cb9d58636eb7c442306aa14cd