Compare commits

...

113 Commits
0.3.0 ... 0.5.1

Author SHA1 Message Date
24684ca71b chore: v0.5.1 2019-04-04 12:01:00 +02:00
8351dea292 refactor(network): process all messages 2019-04-04 11:58:27 +02:00
a68011bce6 style(workspaces): urgent style 2019-04-02 09:31:40 +02:00
1f6f443c48 Revert "refactor(network): fix skipped messages"
This reverts commit 1ccf372f8e.
2019-04-01 11:41:43 +02:00
7fac483530 fix: don't bind RTMGRP_IPV(4|6)_ROUTE 2019-03-31 16:33:01 +02:00
0ad9a03d3d Merge pull request #256 from SibrenVasse/spotify-json
Convert spotify module to generic json version
2019-03-31 16:30:16 +02:00
0f689b733d feat(custom): make spotify module generic
Set class via json attribute
Choose icon via alt attribute
2019-03-31 16:20:43 +02:00
618fe80670 chore(custom): fix typo and quoting style 2019-03-31 16:10:42 +02:00
3a8cd91cc0 Revert "refactor: partial revert of 1ccf372f8e9d74cb18e92220c18a0729832fe69e"
This reverts commit 949a4ecf2e.
2019-03-30 09:20:28 +01:00
30725824d0 Merge pull request #251 from DanielVoogsgerd/feature-select-player
Feature select player
2019-03-30 09:19:24 +01:00
6522a7acb4 chore: let compile sway modules even if we can't find the binary 2019-03-30 09:15:51 +01:00
fd3e4ab2d4 Merge branch 'master' into feature-select-player 2019-03-30 09:07:31 +01:00
949a4ecf2e refactor: partial revert of 1ccf372f8e 2019-03-30 09:03:31 +01:00
860a6a1b81 Merge pull request #252 from alebastr/tray-items-refresh
Process tray icon updates
2019-03-30 08:40:55 +01:00
5a2b5624dc feat(tray): process tray icon update signals 2019-03-29 18:40:28 -07:00
82fcee33b3 refactor(tray): use Gio::DBus bindings in SNI Item class 2019-03-29 18:28:29 -07:00
842e8501f9 fix(tray): free icon data on pixbuf update 2019-03-28 10:52:25 -07:00
7bd5454e43 Merge pull request #250 from DanielVoogsgerd/feature-arguments-logging
Feature: Command line arguments/Logging
2019-03-28 09:38:33 +01:00
8340e88795 Merge pull request #249 from DanielVoogsgerd/refactor-main_method
refactor: Add main method / Clean global scope
2019-03-28 09:38:07 +01:00
095294389b feat(Spotify): Add option to select player 2019-03-27 22:27:23 +01:00
3bcf3befec chore(Spotify): Add logging for events 2019-03-27 17:28:02 +01:00
db69b449ba feat(Spotify): Add logger 2019-03-27 16:37:59 +01:00
ed358ef024 feat(Spotify): Add argument parser 2019-03-27 16:33:15 +01:00
faa79ea216 refactor: Add main method / Clean global scope 2019-03-27 16:10:36 +01:00
b17984f75e Merge pull request #248 from ndrewtl/patch-1
Note availability in Arch community repository
2019-03-27 08:15:44 +01:00
4521db66ce Note availability in Arch community repository 2019-03-26 18:20:09 -07:00
7912b7f119 Merge pull request #245 from SibrenVasse/json-icon
[custom] Allow icon selection based on json alt attribute
2019-03-26 09:26:58 +01:00
55a6e4907b feat(custom): allow icon selection based on json alt attribute 2019-03-26 00:35:49 +01:00
dda0cc793e fix: check vertical after parsing multiple outputs 2019-03-25 21:02:00 +01:00
5144426f0e fix(workspace): scroll direction 2019-03-25 11:55:01 +01:00
98c028226d Merge pull request #241 from jlbribeiro/fix/essid-unescaped-input
Fix #240: Escape ESSID text before interpreting as pango
2019-03-24 10:28:00 +01:00
3eb901f800 Escape ESSID text before interpreting as pango
Fixes #240.
2019-03-24 03:11:54 +00:00
47142a61ae feat: allow waybar to be positioned on left/right 2019-03-22 12:25:05 +01:00
f700319d7f chore: v0.5.0 2019-03-20 10:51:40 +01:00
418767c405 Merge pull request #228 from hoellen/feat-rtsignal
Add pkill signals to custom modules
2019-03-18 19:53:02 +01:00
1f924c9c06 Merge branch 'master' into feat-rtsignal 2019-03-18 19:04:11 +01:00
38fa7ceab1 add signalhandler for module update 2019-03-18 18:46:44 +01:00
22cddc5e26 refactor(workspaces): scroll output aware 2019-03-18 14:44:07 +01:00
1ccf372f8e refactor(network): fix skipped messages 2019-03-18 11:07:36 +01:00
3257968a28 Merge pull request #222 from alebastr/pulseaudio-ci-icon-lookup
fix(pulseaudio) use case-insensitive comparison for icon lookup
2019-03-15 09:43:02 +01:00
6fc06fe9db Merge branch 'master' into pulseaudio-ci-icon-lookup 2019-03-15 09:41:40 +01:00
75afcd3ad1 Merge pull request #221 from alebastr/sway-ipc-string-assertion
fix(sway): ipc client crash when compiled with -D_GLIBCXX_ASSERTIONS
2019-03-15 09:40:41 +01:00
9ad80849b1 fix(pulseaudio): Avoid allocation of string vector on every call of getPortIcon() 2019-03-14 18:35:16 -07:00
492d151079 fix(pulseaudio) use case-insensitive comparison for icon lookup 2019-03-14 18:08:12 -07:00
00176c9514 fix(sway): ipc client crash when compiled with -D_GLIBCXX_ASSERTIONS
reserve() does not change string size and any access beyond data() + size() is UB
2019-03-14 17:53:45 -07:00
6d2dcd8ac7 fix(temperature): default thermal zone 2019-03-14 14:01:10 +01:00
7d6079bb06 style: update default temperature style/config 2019-03-13 22:39:39 +01:00
c820ed3495 Merge pull request #216 from Groggy/temperature
Add temperature module
2019-03-13 22:21:11 +01:00
0314bf70b3 feat(temperature): update README 2019-03-13 16:56:13 +01:00
7ae549dc9e Add temperature module 2019-03-13 13:35:43 +01:00
cd13180199 Merge pull request #213 from jubalh/rdmos
Add available on openSUSE
2019-03-12 11:41:05 +01:00
8d6d8e8210 Add available on openSUSE 2019-03-12 11:01:18 +01:00
2995da845d fix: config per output 2019-03-10 10:34:56 +01:00
973cec1713 feat(idle): add status class 2019-03-10 10:29:06 +01:00
94d7b083c5 fix(Pulseaudio): switch case 2019-03-08 15:30:41 +01:00
ef88f0a223 fix: clock rounding 2019-03-07 12:34:21 +01:00
ab0dcbfb2e Merge pull request #204 from Organic-Code/master
Improving mouse buttons support
2019-03-05 10:16:19 +01:00
1974766125 Merge branch 'master' into master 2019-03-05 10:15:05 +01:00
9c0c0d262e Using "inclusive or" for format-alt-click and other click events
c.f. https://github.com/Alexays/Waybar/pull/204#discussion_r262009635

Co-Authored-By: Organic-Code <Lazarelucas@yahoo.fr>
2019-03-04 15:00:44 -05:00
768e57dd37 Merge pull request #208 from jonfin/idle_inhibitor_bugfix
[bugfix] idle_inhibitor: handle click events correctly
2019-03-04 11:20:23 +01:00
67c756b28e Merge branch 'master' into idle_inhibitor_bugfix 2019-03-04 11:19:14 +01:00
584750d0fe Merge pull request #207 from jonfin/tooltip-network
Add custom tooltip format for network module
2019-03-04 11:17:52 +01:00
80ef63791d [bugfix] idle_inhibitor handles click events correctly
- Declare event handler in ALabel virtual so the idle_inhibitor can
  overriding them
- Handle the right click event in idle_inhibitor and call ALabel handler if needed
2019-03-03 22:02:34 +01:00
a9f680b06e Add custom tooltip format for network module 2019-03-03 21:35:35 +01:00
7e4fed8218 Merge pull request #206 from dikeert/issue/205-add-option-to-show-current-workspace-only
resolves #205
2019-03-03 18:04:03 +01:00
737da3615f resolves #205 2019-03-03 21:35:32 +11:00
d0f56b7bdd Improving mouse buttons support
Adding support for middle, backward, and forward mouse buttons click events, adds config keys : "on-click-middle", "on-click-forward" and "on-click-backward"
Adding the key "format-alt-click" to choose what mouse clicks toggles the alternative format, when present. Possible values (in config): "click-right", "click-left", "click-middle", "click-forward", "click-backward". Other values have the same effect than "click-left". Previous behaviour was to toggle it whenever any click was registered and any click that was not handled by "on-click-right" or "on-click-left" occurred
2019-03-02 14:07:12 -05:00
f47492c371 chore: v0.4.0 2019-03-01 17:12:02 +01:00
79b0367e6c feat(custom): escape option 2019-03-01 17:03:01 +01:00
2c411b9848 Merge pull request #199 from ianhattendorf/fix/backlight-disable-not-found
Disable backlight module if no backlights found
2019-03-01 09:31:49 +01:00
ac6d833d4d Ignore all potential build directories
This is useful when using multiple build directories, eg:
- build-debug
- build-release
- build-sanitize
2019-02-28 19:57:49 -07:00
39de8e544c Disable backlight module if no backlight found 2019-02-28 19:50:57 -07:00
0271e9bc1a Revert "fix: Escape user controlled input"
This reverts commit 7247360e29.
2019-02-28 19:30:27 +01:00
470f539346 Merge pull request #193 from Robinhuett/custom_alt
feat(custom): Add field for additional data to json
2019-02-26 12:18:47 +01:00
6633e34bbd Merge branch 'master' into custom_alt 2019-02-26 12:18:34 +01:00
4b3725ec31 Merge pull request #194 from ianhattendorf/fix/backlight-crash-unplug-monitor
Fix crash when monitor unplugged
2019-02-26 12:18:19 +01:00
c1295c8fd6 Fix crash when monitor unplugged
`Backlight#devices_` was being destructed before
`Backlight#udev_thread_`. Also check if thread is still running after
`epoll_wait`
2019-02-25 20:05:44 -07:00
39bf403505 feat(custom): Add field for additional data to json 2019-02-25 22:04:09 +01:00
4499a23e51 Merge pull request #192 from DutchMofo/clock-tooltip
Fix tooltip format
2019-02-25 20:39:31 +01:00
821a009c32 Fix tooltip format 2019-02-25 19:25:19 +01:00
961a05bedf Merge pull request #191 from Robinhuett/escape_userinput
fix: Escape user controlled input
2019-02-25 18:46:17 +01:00
7247360e29 fix: Escape user controlled input 2019-02-25 18:28:56 +01:00
abfa428dab Merge pull request #190 from Robinhuett/fix_backlight_fmtalt
fix(backlight) Allow format-alt
2019-02-25 10:52:42 +01:00
63e97df9ff fix(backlight) Allow format-alt 2019-02-24 22:15:41 +01:00
5a7f801922 Merge pull request #189 from DutchMofo/clock-tooltip
Added clock tooltip with optional alternate format
2019-02-24 12:10:01 +01:00
8fa30f8ce3 Forgot comma in json 2019-02-24 11:54:59 +01:00
da4661f97c Removed clock tooltip from example config 2019-02-24 11:49:58 +01:00
7245fb5d0a Added clock tooltip with optional alternate format 2019-02-24 09:25:34 +01:00
b4f36436c3 style: update idle inhibitor color 2019-02-22 17:34:13 +01:00
eb7e265eb1 Merge pull request #187 from jonfin/idle_inhibitor
Add module Idle inhibitor
2019-02-22 17:31:51 +01:00
c2ed0cb832 Add the possibility to disable the tooltip 2019-02-22 16:58:36 +01:00
d708ce2be9 Add idle inhibitor module 2019-02-22 16:55:46 +01:00
83a6475510 feat: can disable tooltip 2019-02-22 11:35:47 +01:00
331b28393a Merge pull request #181 from ianhattendorf/feature/module-brightness
Add backlight module
2019-02-21 16:04:32 +01:00
afbf2de1b9 Merge branch 'master' into feature/module-brightness 2019-02-21 15:29:17 +01:00
e67347f6ad Gate backlight module behind libudev availability 2019-02-18 21:11:18 -07:00
bef8520937 Remove opensuse ci for now 2019-02-18 12:33:40 +01:00
8bfcb106c0 style: comment about workspace hover effect 2019-02-18 12:30:41 +01:00
875306804c Add backlight module
Monitor the backlight level via udev. Poll every `interval` as well,
in case backlight udev events aren't supported.
2019-02-17 15:29:49 -07:00
6bf4f65228 fix: two finger scroll 2019-02-16 09:56:53 +01:00
0c9edb0c4b fix(custom): check exist_status in endless script before clean the output 2019-02-16 09:48:27 +01:00
3e18673451 fix: remove bluetooth class on else 2019-02-12 17:55:50 +01:00
d0370acb21 refactor(network): better network disconnection 2019-02-11 19:06:39 +01:00
aeec80f375 fix(Tray): big icon 2019-02-07 13:34:30 +01:00
dc9fe04d11 refactor: add retry to get external interface 2019-02-06 10:33:12 +01:00
01cec9fcb7 fix(bar): multi screens 2019-02-04 22:09:01 +01:00
6fb25ade7e chore: update opensuse Dockerfile 2019-02-04 11:20:18 +01:00
e10e9554ab Update README.md 2019-02-04 10:32:16 +01:00
93173851d3 fix(Workspaces): index 2019-02-02 12:07:59 +01:00
add9e925f4 fix(Workspaces): button order 2019-02-02 00:36:52 +01:00
60af51fc17 chore: update opensuse dockerfile 2019-02-01 21:56:34 +01:00
3021ef2d51 Update README.md 2019-02-01 21:52:38 +01:00
0ddcf26a45 feat: output configuration 2019-02-01 21:45:59 +01:00
44 changed files with 1337 additions and 331 deletions

2
.gitignore vendored
View File

@ -5,7 +5,7 @@ vgcore.*
*.swp
packagecache
/subprojects/**/
/build
/build*
/dist
/meson.egg-info

View File

@ -6,7 +6,7 @@ services:
env:
- distro: debian
- distro: archlinux
- distro: opensuse
# - distro: opensuse
before_install:
- docker pull alexays/waybar:${distro}

View File

@ -2,4 +2,4 @@ FROM opensuse/tumbleweed:latest
RUN zypper -n up && \
zypper -n install -t pattern devel_C_C++ && \
zypper -n install git meson clang libinput-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel
zypper -n install git meson clang libinput10 libinput-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel

View File

@ -1,8 +1,9 @@
# Waybar [![Travis](https://travis-ci.org/Alexays/Waybar.svg?branch=master)](https://travis-ci.org/Alexays/Waybar) [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
# Waybar [![Travis](https://travis-ci.org/Alexays/Waybar.svg?branch=master)](https://travis-ci.org/Alexays/Waybar) [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
**Proof of concept**
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
> Available on [AUR](https://aur.archlinux.org/packages/waybar-git/)
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
[AUR](https://aur.archlinux.org/packages/waybar-git/) and [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar)
**Current features**
- Sway (Workspaces, Binding mode, Focused window name)
@ -13,7 +14,9 @@
- Pulseaudio
- Memory
- Cpu load average
- Temperature
- Custom scripts
- Multiple output configuration
- And much more customizations
**Configuration and Styling**

View File

@ -17,6 +17,8 @@ public:
virtual operator Gtk::Widget &();
protected:
bool tooltipEnabled();
Gtk::EventBox event_box_;
Gtk::Label label_;
const Json::Value &config_;
@ -26,9 +28,8 @@ protected:
bool alt_ = false;
std::string default_format_;
private:
bool handleToggle(GdkEventButton *const &ev);
bool handleScroll(GdkEventScroll *);
virtual bool handleToggle(GdkEventButton *const &ev);
virtual bool handleScroll(GdkEventScroll *);
};
} // namespace waybar

View File

@ -7,6 +7,7 @@
#include <gtkmm/window.h>
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "IModule.hpp"
namespace waybar {
@ -21,6 +22,7 @@ class Bar {
~Bar() = default;
auto toggle() -> void;
void handleSignal(int);
const Client& client;
Gtk::Window window;
@ -30,6 +32,7 @@ class Bar {
std::string output_name;
uint32_t wl_name;
bool visible = true;
bool vertical = false;
private:
static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t,
int32_t);
@ -44,6 +47,9 @@ class Bar {
static void layerSurfaceHandleClosed(void *,
struct zwlr_layer_surface_v1 *);
void initBar();
bool isValidOutput(const Json::Value &config);
void destroyOutput();
auto setupConfig() -> void;
auto setupWidgets() -> void;
auto setupCss() -> void;

View File

@ -24,6 +24,7 @@ class Client {
struct zwlr_layer_shell_v1 *layer_shell = nullptr;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
struct wl_seat *seat = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
private:

View File

@ -7,6 +7,7 @@
#include "modules/sway/workspaces.hpp"
#include "modules/sway/window.hpp"
#endif
#include "modules/idle_inhibitor.hpp"
#include "modules/battery.hpp"
#include "modules/memory.hpp"
#include "modules/cpu.hpp"
@ -16,9 +17,13 @@
#ifdef HAVE_LIBNL
#include "modules/network.hpp"
#endif
#ifdef HAVE_LIBUDEV
#include "modules/backlight.hpp"
#endif
#ifdef HAVE_LIBPULSE
#include "modules/pulseaudio.hpp"
#endif
#include "modules/temperature.hpp"
#include "modules/custom.hpp"
namespace waybar {

View File

@ -0,0 +1,67 @@
#pragma once
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "ALabel.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
struct udev;
struct udev_device;
namespace waybar::modules {
class Backlight : public ALabel {
class BacklightDev {
public:
BacklightDev() = default;
BacklightDev(std::string name, int actual, int max);
std::string_view name() const;
int get_actual() const;
void set_actual(int actual);
int get_max() const;
void set_max(int max);
friend inline bool operator==(const BacklightDev &lhs,
const BacklightDev &rhs) {
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ &&
lhs.max_ == rhs.max_;
}
private:
std::string name_;
int actual_ = 1;
int max_ = 1;
};
public:
Backlight(const std::string &, const Json::Value &);
~Backlight();
auto update() -> void;
private:
template <class ForwardIt>
static const BacklightDev *best_device(ForwardIt first, ForwardIt last,
std::string_view);
template <class ForwardIt, class Inserter>
static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter,
udev_device *dev);
template <class ForwardIt, class Inserter>
static void enumerate_devices(ForwardIt first, ForwardIt last,
Inserter inserter, udev *udev);
const std::string name_;
const std::string preferred_device_;
static constexpr int EPOLL_MAX_EVENTS = 16;
std::optional<BacklightDev> previous_best_;
std::string previous_format_;
std::mutex udev_thread_mutex_;
std::vector<BacklightDev> devices_;
// thread must destruct before shared data
waybar::util::SleeperThread udev_thread_;
};
} // namespace waybar::modules

View File

@ -2,6 +2,7 @@
#include <fmt/format.h>
#include <iostream>
#include <csignal>
#include "util/sleeper_thread.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
@ -14,6 +15,7 @@ class Custom : public ALabel {
Custom(const std::string&, const Json::Value&);
~Custom();
auto update() -> void;
void refresh(int /*signal*/);
private:
void delayWorker();
void continuousWorker();
@ -22,6 +24,7 @@ class Custom : public ALabel {
const std::string name_;
std::string text_;
std::string alt_;
std::string tooltip_;
std::string class_;
std::string prevclass_;

View File

@ -0,0 +1,23 @@
#pragma once
#include <fmt/format.h>
#include "bar.hpp"
#include "client.hpp"
#include "ALabel.hpp"
namespace waybar::modules {
class IdleInhibitor: public ALabel {
public:
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~IdleInhibitor();
auto update() -> void;
private:
bool handleToggle(GdkEventButton* const& e);
const Bar& bar_;
std::string status_;
struct zwp_idle_inhibitor_v1 *idle_inhibitor_;
};
}

View File

@ -20,6 +20,9 @@ class Network : public ALabel {
~Network();
auto update() -> void;
private:
static const uint8_t MAX_RETRY = 5;
static const uint8_t EPOLL_MAX = 255;
static int handleEvents(struct nl_msg*, void*);
static int handleScan(struct nl_msg*, void*);

View File

@ -2,12 +2,14 @@
#include <dbus-status-notifier-item.h>
#include <glibmm/refptr.h>
#include <giomm/dbusproxy.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/image.h>
#include <gtkmm/icontheme.h>
#include <gtkmm/menu.h>
#include <json/json.h>
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h>
#ifdef FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
#else
@ -16,7 +18,7 @@
namespace waybar::modules::SNI {
class Item {
class Item : public sigc::trackable {
public:
Item(std::string, std::string, const Json::Value&);
~Item() = default;
@ -46,8 +48,12 @@ public:
bool item_is_menu;
private:
static void proxyReady(GObject *obj, GAsyncResult *res, gpointer data);
static void getAll(GObject *obj, GAsyncResult *res, gpointer data);
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments);
void updateImage();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant *variant);
@ -56,8 +62,9 @@ private:
bool makeMenu(GdkEventButton *const &ev);
bool handleClick(GdkEventButton *const & /*ev*/);
GCancellable *cancellable_ = nullptr;
SnItem *proxy_ = nullptr;
Glib::RefPtr<Gio::Cancellable> cancellable_;
Glib::RefPtr<Gio::DBus::Proxy> proxy_;
bool update_pending_;
};
} // namespace waybar::modules::SNI

View File

@ -11,7 +11,7 @@ namespace waybar::modules::SNI {
class Tray : public IModule {
public:
Tray(const std::string&, const Json::Value&);
Tray(const std::string&, const Bar&, const Json::Value&);
~Tray() = default;
auto update() -> void;
operator Gtk::Widget &();

View File

@ -19,11 +19,12 @@ class Workspaces : public IModule {
operator Gtk::Widget &();
private:
void worker();
void addWorkspace(Json::Value);
std::string getIcon(std::string, Json::Value);
void addWorkspace(const Json::Value&);
void onButtonReady(const Json::Value&, Gtk::Button&);
std::string getIcon(const std::string&, const Json::Value&);
bool handleScroll(GdkEventScroll*);
std::string getPrevWorkspace();
std::string getNextWorkspace();
const std::string getCycleWorkspace(uint8_t current, bool prev) const;
uint16_t getWorkspaceIndex(const std::string &name) const;
std::string trimWorkspaceName(std::string);
const Bar& bar_;

View File

@ -0,0 +1,23 @@
#pragma once
#include <fmt/format.h>
#include <fstream>
#include "util/sleeper_thread.hpp"
#include "ALabel.hpp"
namespace waybar::modules {
class Temperature : public ALabel {
public:
Temperature(const std::string&, const Json::Value&);
~Temperature() = default;
auto update() -> void;
private:
std::tuple<uint16_t, uint16_t> getTemperature();
bool isCritical(uint16_t);
std::string file_path_;
waybar::util::SleeperThread thread_;
};
}

View File

@ -21,7 +21,10 @@ public:
SleeperThread& operator=(std::function<void()> func)
{
thread_ = std::thread([this, func] {
while (do_run_) func();
while (do_run_) {
signal_ = false;
func();
}
});
return *this;
}
@ -34,18 +37,19 @@ public:
auto sleep_for(std::chrono::system_clock::duration dur)
{
std::unique_lock lk(mutex_);
return condvar_.wait_for(lk, dur, [this] { return !do_run_; });
return condvar_.wait_for(lk, dur, [this] { return signal_ || !do_run_; });
}
auto sleep_until(std::chrono::time_point<std::chrono::system_clock,
std::chrono::system_clock::duration> time_point)
{
std::unique_lock lk(mutex_);
return condvar_.wait_until(lk, time_point, [this] { return !do_run_; });
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; });
}
auto wake_up()
{
signal_ = true;
condvar_.notify_all();
}
@ -53,6 +57,7 @@ public:
{
{
std::lock_guard<std::mutex> lck(mutex_);
signal_ = true;
do_run_ = false;
}
condvar_.notify_all();
@ -71,6 +76,7 @@ private:
std::condition_variable condvar_;
std::mutex mutex_;
bool do_run_ = true;
bool signal_ = false;
};
}

View File

@ -1,6 +1,6 @@
project(
'waybar', 'cpp', 'c',
version: '0.3.0',
version: '0.5.1',
license: 'MIT',
default_options : [
'cpp_std=c++17',
@ -55,6 +55,7 @@ sigcpp = dependency('sigc++-2.0')
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'))
libudev = dependency('libudev', required: get_option('libudev'))
src_files = files(
'src/factory.cpp',
@ -64,12 +65,14 @@ src_files = files(
'src/modules/clock.cpp',
'src/modules/custom.cpp',
'src/modules/cpu.cpp',
'src/modules/idle_inhibitor.cpp',
'src/modules/temperature.cpp',
'src/main.cpp',
'src/bar.cpp',
'src/client.cpp'
)
if find_program('sway', required : false).found()
if true # find_program('sway', required : false).found()
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [
'src/modules/sway/ipc/client.cpp',
@ -99,6 +102,11 @@ if dbusmenu_gtk.found()
)
endif
if libudev.found()
add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp')
src_files += 'src/modules/backlight.cpp'
endif
subdir('protocol')
executable(
@ -119,7 +127,8 @@ executable(
giounix,
libnl,
libnlgen,
libpulse
libpulse,
libudev
],
include_directories: [include_directories('include')],
install: true,

View File

@ -1,4 +1,5 @@
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev 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')

View File

@ -24,6 +24,7 @@ wayland_scanner_client = generator(
client_protocols = [
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
['wlr-layer-shell-unstable-v1.xml'],
]

View File

@ -1,12 +1,12 @@
{
"layer": "top", // Waybar at top layer
// "position": "bottom", // Waybar at the bottom of your screen
// "position": "bottom", // Waybar position (top|bottom|left|right)
// "height": 30, // Waybar height
// "width": 1280, // Waybar width
// Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"],
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "battery#bat2", "clock", "tray"],
"modules-right": ["idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration
// "sway/workspaces": {
// "disable-scroll": true,
@ -26,11 +26,19 @@
"sway/mode": {
"format": "<span style=\"italic\">{}</span>"
},
"idle_inhibitor": {
"format": "{icon}",
"format-icons": {
"activated": "",
"deactivated": ""
}
},
"tray": {
// "icon-size": 21,
"spacing": 10
},
"clock": {
"tooltip-format": "{:%Y-%m-%d | %H:%M}",
"format-alt": "{:%Y-%m-%d}"
},
"cpu": {
@ -39,6 +47,18 @@
"memory": {
"format": "{}% "
},
"temperature": {
// "thermal-zone": 2,
// "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
"critical-threshold": 80,
// "format-critical": "{temperatureC}°C ",
"format": "{temperatureC}°C "
},
"backlight": {
// "device": "acpi_video1",
"format": "{percent}% {icon}",
"format-icons": ["", ""]
},
"battery": {
"states": {
// "good": 95,
@ -75,9 +95,15 @@
},
"on-click": "pavucontrol"
},
"custom/spotify": {
"format": " {}",
"custom/media": {
"format": "{icon} {}",
"return-type": "json",
"max-length": 40,
"format-icons": {
"spotify": "",
"default": "🎜"
},
"escape": true,
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
}
}

View File

@ -1,19 +1,34 @@
#!/usr/bin/env python3
import argparse
import logging
import sys
import signal
import gi
import json
gi.require_version('Playerctl', '2.0')
from gi.repository import Playerctl, GLib
manager = Playerctl.PlayerManager()
loop = GLib.MainLoop()
logger = logging.getLogger(__name__)
def write_output(text, player):
logger.info('Writing output')
output = {'text': text,
'class': 'custom-' + player.props.player_name,
'alt': player.props.player_name}
sys.stdout.write(json.dumps(output) + '\n')
sys.stdout.flush()
def on_play(player, status, manager):
logger.info('Received new playback status')
on_metadata(player, player.props.metadata, manager)
def on_metadata(player, metadata, manager):
logger.info('Received new metadata')
track_info = ''
if player.props.player_name == 'spotify' and \
@ -23,28 +38,27 @@ def on_metadata(player, metadata, manager):
elif player.get_artist() != '' and player.get_title() != '':
track_info = '{artist} - {title}'.format(artist=player.get_artist(),
title=player.get_title())
if player.props.status != 'Playing':
track_info = '' + track_info
write_output(track_info, player)
def on_player_appeared(manager, player, selected_player=None):
if player is not None and player.name == selected_player:
init_player(manager, player)
else:
sys.stdout.write('\n')
sys.stdout.flush()
return
if player.props.status == 'Playing':
sys.stdout.write(track_info + '\n')
else:
sys.stdout.write('' + track_info + '\n')
sys.stdout.flush()
def on_name_appeared(manager, name):
init_player(name)
logger.debug("New player appeared, but it's not the selected player, skipping")
def on_player_vanished(manager, player):
sys.stdout.write("\n")
logger.info('Player has vanished')
sys.stdout.write('\n')
sys.stdout.flush()
def init_player(name):
def init_player(manager, name):
logger.debug('Initialize player: {player}'.format(player=name.name))
player = Playerctl.Player.new_from_name(name)
player.connect('playback-status', on_play, manager)
player.connect('metadata', on_metadata, manager)
@ -53,19 +67,60 @@ def init_player(name):
def signal_handler(sig, frame):
sys.stdout.write("\n")
logger.debug('Received signal to stop, exiting')
sys.stdout.write('\n')
sys.stdout.flush()
loop.quit()
# loop.quit()
sys.exit(0)
manager.connect('name-appeared', on_name_appeared)
manager.connect('player-vanished', on_player_vanished)
def parse_arguments():
parser = argparse.ArgumentParser()
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Increase verbosity with every occurance of -v
parser.add_argument('-v', '--verbose', action='count', default=0)
for player in manager.props.player_names:
init_player(player)
# Define for which player we're listening
parser.add_argument('--player')
return parser.parse_args()
def main():
arguments = parse_arguments()
# Initialize logging
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG,
format='%(name)s %(levelname)s %(message)s')
# Logging is set by default to WARN and higher.
# With every occurrence of -v it's lowered by one
logger.setLevel(max((3 - arguments.verbose) * 10, 0))
# Log the sent command line arguments
logger.debug('Arguments received {}'.format(vars(arguments)))
manager = Playerctl.PlayerManager()
loop = GLib.MainLoop()
manager.connect('name-appeared', lambda *args: on_player_appeared(*args, arguments.player))
manager.connect('player-vanished', on_player_vanished)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
for player in manager.props.player_names:
if arguments.player is not None and arguments.player != player.name:
logger.debug('{player} is not the filtered player, skipping it'
.format(player=player.name)
)
continue
init_player(manager, player)
loop.run()
if __name__ == '__main__':
main()
loop.run()

View File

@ -9,27 +9,32 @@
window#waybar {
background: rgba(43, 48, 59, 0.5);
border-bottom: 3px solid rgba(100, 114, 125, 0.5);
color: white;
color: #ffffff;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button {
padding: 0 5px;
background: transparent;
color: white;
color: #ffffff;
border-bottom: 3px solid transparent;
}
#workspaces button.focused {
background: #64727D;
border-bottom: 3px solid white;
border-bottom: 3px solid #ffffff;
}
#workspaces button.urgent {
background-color: #eb4d4b;
}
#mode {
background: #64727D;
border-bottom: 3px solid white;
border-bottom: 3px solid #ffffff;
}
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify, #tray, #mode {
#clock, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #custom-media, #tray, #mode, #idle_inhibitor {
padding: 0 10px;
margin: 0 5px;
}
@ -40,24 +45,24 @@ window#waybar {
#battery {
background-color: #ffffff;
color: black;
color: #000000;
}
#battery.charging {
color: white;
color: #ffffff;
background-color: #26A65B;
}
@keyframes blink {
to {
background-color: #ffffff;
color: black;
color: #000000;
}
}
#battery.warning:not(.charging) {
#battery.critical:not(.charging) {
background: #f53c3c;
color: white;
color: #ffffff;
animation-name: blink;
animation-duration: 0.5s;
animation-timing-function: linear;
@ -74,6 +79,10 @@ window#waybar {
background: #9b59b6;
}
#backlight {
background: #90b1b1;
}
#network {
background: #2980b9;
}
@ -84,7 +93,7 @@ window#waybar {
#pulseaudio {
background: #f1c40f;
color: black;
color: #000000;
}
#pulseaudio.muted {
@ -92,11 +101,36 @@ window#waybar {
color: #2a5c45;
}
#custom-spotify {
#custom-media {
background: #66cc99;
color: #2a5c45;
}
.custom-spotify {
background: #66cc99;
}
.custom-vlc {
background: #ffa000;
}
#temperature {
background: #f0932b;
}
#temperature.critical {
background: #eb4d4b;
}
#tray {
background-color: #2980b9;
}
#idle_inhibitor {
background-color: #2d3436;
}
#idle_inhibitor.activated {
background-color: #ecf0f1;
color: #2d3436;
}

View File

@ -6,8 +6,9 @@
waybar::ALabel::ALabel(const Json::Value& config, const std::string format, uint16_t interval)
: config_(config),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(std::chrono::seconds(config_["interval"].isUInt()
? config_["interval"].asUInt() : interval)), default_format_(format_)
interval_(config_["interval"] == "once" ? std::chrono::seconds(100000000) :
std::chrono::seconds(config_["interval"].isUInt() ?
config_["interval"].asUInt() : interval)), default_format_(format_)
{
event_box_.add(label_);
if (config_["max-length"].isUInt()) {
@ -27,7 +28,7 @@ waybar::ALabel::ALabel(const Json::Value& config, const std::string format, uint
sigc::mem_fun(*this, &ALabel::handleToggle));
}
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK);
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &ALabel::handleScroll));
}
@ -40,11 +41,20 @@ auto waybar::ALabel::update() -> void {
bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
if (config_["on-click"].isString() && e->button == 1) {
waybar::util::command::forkExec(config_["on-click"].asString());
} else if (config_["on-click-middle"].isString() && e->button == 2) {
waybar::util::command::forkExec(config_["on-click-middle"].asString());
} else if (config_["on-click-right"].isString() && e->button == 3) {
waybar::util::command::forkExec(config_["on-click-right"].asString());
} else {
} else if (config_["on-click-forward"].isString() && e->button == 8) {
waybar::util::command::forkExec(config_["on-click-backward"].asString());
} else if (config_["on-click-backward"].isString() && e->button == 9) {
waybar::util::command::forkExec(config_["on-click-forward"].asString());
}
if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) {
alt_ = !alt_;
if (alt_) {
if (alt_ && config_["format-alt"].isString()) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
@ -86,8 +96,8 @@ bool waybar::ALabel::handleScroll(GdkEventScroll* e) {
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].isString() || format_icons[alt].isArray())) {
@ -107,4 +117,9 @@ std::string waybar::ALabel::getIcon(uint16_t percentage,
return "";
}
bool waybar::ALabel::tooltipEnabled()
{
return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true;
}
waybar::ALabel::operator Gtk::Widget&() { return event_box_; }

View File

@ -33,31 +33,97 @@ waybar::Bar::Bar(const Client& client,
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);
}
std::size_t layer_top = config_["layer"] == "top"
void waybar::Bar::initBar()
{
// Converting string to button code rn as to avoid doing it later
auto setupAltFormatKeyForModule = [this](const std::string& module_name){
if (config_.isMember(module_name)) {
Json::Value& module = config_[module_name];
if (module.isMember("format-alt")) {
if (module.isMember("format-alt-click")) {
Json::Value& click = module["format-alt-click"];
if (click.isString()) {
std::string str_click = click.asString();
if (str_click == "click-right") {
module["format-alt-click"] = 3u;
} else if (str_click == "click-middle") {
module["format-alt-click"] = 2u;
} else if (str_click == "click-backward") {
module["format-alt-click"] = 8u;
} else if (str_click == "click-forward") {
module["format-alt-click"] = 9u;
} else {
module["format-alt-click"] = 1u; // default click-left
}
} else {
module["format-alt-click"] = 1u;
}
} else {
module["format-alt-click"] = 1u;
}
}
}
};
auto setupAltFormatKeyForModuleList = [this, &setupAltFormatKeyForModule](const char* module_list_name) {
if (config_.isMember(module_list_name)) {
Json::Value& modules = config_[module_list_name];
for (const Json::Value& module_name : modules) {
if (module_name.isString()) {
setupAltFormatKeyForModule(module_name.asString());
}
}
}
};
// Convert to button code for every module that is used.
setupAltFormatKeyForModuleList("modules-left");
setupAltFormatKeyForModuleList("modules-right");
setupAltFormatKeyForModuleList("modules-center");
std::size_t 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, *output, layer_top, "waybar");
client.layer_shell, surface, *output, layer, "waybar");
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);
zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this);
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
if (config_["position"] == "bottom") {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (config_["position"] == "right" || config_["position"] == "left") {
height_ = 0;
width_ = 30;
}
auto height = config_["height"].isUInt() ? config_["height"].asUInt() : height_;
auto width = config_["width"].isUInt() ? config_["width"].asUInt() : width_;
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
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;
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;
}
zwlr_layer_surface_v1_set_anchor(layer_surface, anchor);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, height);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, vertical ? width : height);
zwlr_layer_surface_v1_set_size(layer_surface, width, height);
wl_surface_commit(surface);
@ -84,11 +150,50 @@ void waybar::Bar::handleDone(void* /*data*/,
// Nothing here
}
bool waybar::Bar::isValidOutput(const Json::Value &config)
{
bool found = true;
if (config["output"].isArray()) {
bool in_array = false;
for (auto const &output : config["output"]) {
if (output.isString() && output.asString() == output_name) {
in_array = true;
break;
}
}
found = in_array;
}
if (config["output"].isString() && config["output"].asString() != output_name) {
found = false;
}
return found;
}
void waybar::Bar::handleName(void* data, struct zxdg_output_v1* /*xdg_output*/,
const char* name)
{
auto o = static_cast<waybar::Bar *>(data);
o->output_name = name;
bool found = true;
if (o->config_.isArray()) {
bool in_array = false;
for (auto const &config : o->config_) {
if (config.isObject() && o->isValidOutput(config)) {
in_array = true;
o->config_ = config;
break;
}
}
found = in_array;
} else {
found = o->isValidOutput(o->config_);
}
if (!found) {
wl_output_destroy(*o->output);
zxdg_output_v1_destroy(o->xdg_output_);
} else {
o->initBar();
}
}
void waybar::Bar::handleDescription(void* /*data*/,
@ -97,12 +202,27 @@ void waybar::Bar::handleDescription(void* /*data*/,
// Nothing here
}
void waybar::Bar::handleSignal(int signal)
{
for (auto& module : modules_left_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if(custom) custom->refresh(signal);
}
for (auto& module : modules_center_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if(custom) custom->refresh(signal);
}
for (auto& module : modules_right_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if(custom) custom->refresh(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);
o->window.show_all();
zwlr_layer_surface_v1_ack_configure(surface, serial);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
@ -110,13 +230,18 @@ void waybar::Bar::layerSurfaceHandleConfigure(void* data,
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);
int min_width, min_height;
o->window.get_size(min_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;
}
if (o->width_ < static_cast<uint32_t>(min_width)) {
std::cout << fmt::format("Requested width: {} exceeds the minimum \
width: {} required by the modules", o->height_, min_width) << std::endl;
o->width_ = min_width;
}
std::cout << fmt::format(
"Bar configured (width: {}, height: {}) for output: {}",
o->width_, o->height_, o->output_name) << std::endl;
@ -129,7 +254,6 @@ void waybar::Bar::layerSurfaceHandleClosed(void* data,
struct zwlr_layer_surface_v1* /*surface*/)
{
auto o = static_cast<waybar::Bar *>(data);
std::cout << "Bar removed from output: " + o->output_name << std::endl;
zwlr_layer_surface_v1_destroy(o->layer_surface);
wl_output_destroy(*o->output);
zxdg_output_v1_destroy(o->xdg_output_);
@ -221,4 +345,5 @@ auto waybar::Bar::setupWidgets() -> void
for (auto const& module : modules_right_) {
right_.pack_end(*module, false, false, 0);
}
window.show_all();
}

View File

@ -55,6 +55,10 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry,
o->xdg_output_manager = static_cast<struct zxdg_output_manager_v1 *>(
wl_registry_bind(registry, name,
&zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));
} else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
o->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1 *>(
wl_registry_bind(registry, name,
&zwp_idle_inhibit_manager_v1_interface, 1));
}
}
@ -63,8 +67,10 @@ void waybar::Client::handleGlobalRemove(void* data,
{
auto o = static_cast<waybar::Client *>(data);
for (auto it = o->bars.begin(); it != o->bars.end(); ++it) {
if ((**it).wl_name == name) {
if ((*it)->wl_name == name) {
auto output_name = (*it)->output_name;
o->bars.erase(it);
std::cout << "Bar removed from output: " + output_name << std::endl;
break;
}
}
@ -138,6 +144,7 @@ int waybar::Client::main(int argc, char* argv[])
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_seat_destroy(seat);
wl_display_disconnect(wl_display);

View File

@ -24,6 +24,9 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const
return new waybar::modules::sway::Window(id, bar_, config_[name]);
}
#endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
}
if (ref == "memory") {
return new waybar::modules::Memory(id, config_[name]);
}
@ -35,7 +38,7 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const
}
#ifdef HAVE_DBUSMENU
if (ref == "tray") {
return new waybar::modules::SNI::Tray(id, config_[name]);
return new waybar::modules::SNI::Tray(id, bar_, config_[name]);
}
#endif
#ifdef HAVE_LIBNL
@ -43,11 +46,19 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const
return new waybar::modules::Network(id, config_[name]);
}
#endif
#ifdef HAVE_LIBUDEV
if (ref == "backlight") {
return new waybar::modules::Backlight(id, config_[name]);
}
#endif
#ifdef HAVE_LIBPULSE
if (ref == "pulseaudio") {
return new waybar::modules::Pulseaudio(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), config_[name]);
}

View File

@ -19,6 +19,14 @@ int main(int argc, char* argv[])
}
});
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [] (int sig/*signal*/) {
for (auto& bar : waybar::client->bars) {
bar->handleSignal(sig);
}
});
}
return c.main(argc, argv);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;

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

@ -0,0 +1,278 @@
#include "modules/backlight.hpp"
#include <algorithm>
#include <chrono>
#include <memory>
#include <libudev.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fmt/format.h>
namespace {
class FileDescriptor {
public:
explicit FileDescriptor(int fd) : fd_(fd) {}
FileDescriptor(const FileDescriptor &other) = delete;
FileDescriptor(FileDescriptor &&other) noexcept = delete;
FileDescriptor &operator=(const FileDescriptor &other) = delete;
FileDescriptor &operator=(FileDescriptor &&other) noexcept = delete;
~FileDescriptor() {
if (fd_ != -1) {
if (close(fd_) != 0) {
fmt::print(stderr, "Failed to close fd: {}\n", errno);
}
}
}
int get() const { return fd_; }
private:
int fd_;
};
struct UdevDeleter {
void operator()(udev *ptr) { udev_unref(ptr); }
};
struct UdevDeviceDeleter {
void operator()(udev_device *ptr) { udev_device_unref(ptr); }
};
struct UdevEnumerateDeleter {
void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); }
};
struct UdevMonitorDeleter {
void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); }
};
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
if (rc != expected) {
throw std::runtime_error(fmt::format(message, rc));
}
}
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
if (rc == bad_rc) {
throw std::runtime_error(fmt::format(message, rc));
}
}
void check0(int rc, const char *message = "rc wasn't 0") {
check_eq(rc, 0, message);
}
void check_gte(int rc, int gte, const char *message = "rc was: ") {
if (rc < gte) {
throw std::runtime_error(fmt::format(message, rc));
}
}
void check_nn(const void *ptr, const char *message = "ptr was null") {
if (ptr == nullptr) {
throw std::runtime_error(message);
}
}
} // namespace
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name,
int actual, int max)
: name_(std::move(name)), actual_(actual), max_(max) {}
std::string_view waybar::modules::Backlight::BacklightDev::name() const {
return name_;
}
int waybar::modules::Backlight::BacklightDev::get_actual() const {
return actual_;
}
void waybar::modules::Backlight::BacklightDev::set_actual(int actual) {
actual_ = actual;
}
int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
waybar::modules::Backlight::Backlight(const std::string &name,
const Json::Value &config)
: ALabel(config, "{percent}%", 2), name_(name),
preferred_device_(
config["device"].isString() ? config["device"].asString() : "") {
label_.set_name("backlight");
// Get initial state
{
std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};
check_nn(udev_check.get(), "Udev check new failed");
enumerate_devices(devices_.begin(), devices_.end(),
std::back_inserter(devices_), udev_check.get());
if (devices_.empty()) {
throw std::runtime_error("No backlight found");
}
dp.emit();
}
udev_thread_ = [this] {
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
check_nn(udev.get(), "Udev new failed");
std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{
udev_monitor_new_from_netlink(udev.get(), "udev")};
check_nn(mon.get(), "udev monitor new failed");
check_gte(udev_monitor_filter_add_match_subsystem_devtype(
mon.get(), "backlight", nullptr),
0, "udev failed to add monitor filter: ");
udev_monitor_enable_receiving(mon.get());
auto udev_fd = udev_monitor_get_fd(mon.get());
auto epoll_fd = FileDescriptor{epoll_create1(0)};
check_neq(epoll_fd.get(), -1, "epoll init failed: ");
epoll_event ctl_event;
ctl_event.events = EPOLLIN;
ctl_event.data.fd = udev_fd;
check0(
epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event),
"epoll_ctl failed: {}");
epoll_event events[EPOLL_MAX_EVENTS];
while (udev_thread_.isRunning()) {
const int event_count =
epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS,
std::chrono::milliseconds{interval_}.count());
if (!udev_thread_.isRunning()) {
break;
}
decltype(devices_) devices;
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices = devices_;
}
for (int i = 0; i < event_count; ++i) {
const auto &event = events[i];
check_eq(event.data.fd, udev_fd, "unexpected udev fd");
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{
udev_monitor_receive_device(mon.get())};
check_nn(dev.get(), "epoll dev was null");
upsert_device(devices.begin(), devices.end(),
std::back_inserter(devices), dev.get());
}
// Refresh state if timed out
if (event_count == 0) {
enumerate_devices(devices.begin(), devices.end(),
std::back_inserter(devices), udev.get());
}
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices_ = devices;
}
dp.emit();
}
};
}
waybar::modules::Backlight::~Backlight() = default;
auto waybar::modules::Backlight::update() -> void {
decltype(devices_) devices;
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices = devices_;
}
const auto best =
best_device(devices.cbegin(), devices.cend(), preferred_device_);
if (best != nullptr) {
if (previous_best_.has_value() && previous_best_.value() == *best &&
!previous_format_.empty() && previous_format_ == format_) {
return;
}
const auto percent =
best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max();
label_.set_markup(fmt::format(format_,
fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent))));
} else {
if (!previous_best_.has_value()) {
return;
}
label_.set_markup("");
}
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
previous_format_ = format_;
}
template <class ForwardIt>
const waybar::modules::Backlight::BacklightDev *
waybar::modules::Backlight::best_device(ForwardIt first, ForwardIt last,
std::string_view preferred_device) {
const auto found =
std::find_if(first, last, [preferred_device](const auto &dev) {
return dev.name() == preferred_device;
});
if (found != last) {
return &(*found);
}
const auto max =
std::max_element(first, last, [](const auto &l, const auto &r) {
return l.get_max() < r.get_max();
});
return max == last ? nullptr : &(*max);
}
template <class ForwardIt, class Inserter>
void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
Inserter inserter,
udev_device *dev) {
const char *name = udev_device_get_sysname(dev);
check_nn(name);
const char *actual = udev_device_get_sysattr_value(dev, "actual_brightness");
check_nn(actual);
const int actual_int = std::stoi(actual);
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
check_nn(max);
const int max_int = std::stoi(max);
auto found = std::find_if(first, last, [name](const auto &device) {
return device.name() == name;
});
if (found != last) {
found->set_actual(actual_int);
found->set_max(max_int);
} else {
*inserter = BacklightDev{name, actual_int, max_int};
++inserter;
}
}
template <class ForwardIt, class Inserter>
void waybar::modules::Backlight::enumerate_devices(ForwardIt first,
ForwardIt last,
Inserter inserter,
udev *udev) {
std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{
udev_enumerate_new(udev)};
udev_enumerate_add_match_subsystem(enumerate.get(), "backlight");
udev_enumerate_scan_devices(enumerate.get());
udev_list_entry *enum_devices =
udev_enumerate_get_list_entry(enumerate.get());
udev_list_entry *dev_list_entry;
udev_list_entry_foreach(dev_list_entry, enum_devices) {
const char *path = udev_list_entry_get_name(dev_list_entry);
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{
udev_device_new_from_syspath(udev, path)};
check_nn(dev.get(), "dev new failed");
upsert_device(first, last, inserter, dev.get());
}
}

View File

@ -148,7 +148,9 @@ auto waybar::modules::Battery::update() -> void
if (status == "Unknown") {
status = getAdapterStatus(capacity);
}
label_.set_tooltip_text(status);
if (tooltipEnabled()) {
label_.set_tooltip_text(status);
}
std::transform(status.begin(), status.end(), status.begin(), ::tolower);
auto format = format_;
auto state = getState(capacity);

View File

@ -8,18 +8,30 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
label_.get_style_context()->add_class(id);
}
thread_ = [this] {
auto now = std::chrono::system_clock::now();
dp.emit();
auto now = std::chrono::system_clock::now();
auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
auto time_s = std::chrono::time_point_cast<std::chrono::seconds>(timeout);
auto sub_m =
std::chrono::duration_cast<std::chrono::seconds>(time_s.time_since_epoch()).count() % interval_.count();
thread_.sleep_until(timeout - std::chrono::seconds(sub_m - 1));
if (sub_m > 0) thread_.sleep_until(timeout - std::chrono::seconds(sub_m - 1));
else thread_.sleep_until(timeout - std::chrono::seconds(sub_m));
};
}
auto waybar::modules::Clock::update() -> void
{
auto localtime = fmt::localtime(std::time(nullptr));
label_.set_markup(fmt::format(format_, localtime));
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);
}
}
}

View File

@ -18,7 +18,9 @@ auto waybar::modules::Cpu::update() -> void
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto cpu_load = getCpuLoad();
auto [cpu_usage, tooltip] = getCpuUsage();
label_.set_tooltip_text(tooltip);
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
label_.set_markup(fmt::format(format_,
fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
}

View File

@ -53,17 +53,19 @@ void waybar::modules::Custom::continuousWorker()
char* buff = nullptr;
size_t len = 0;
if (getline(&buff, &len, fp_) == -1) {
int exit_code = 1;
if (fp_) {
pclose(fp_);
exit_code = WEXITSTATUS(pclose(fp_));
fp_ = nullptr;
}
thread_.stop();
output_ = { 1, "" };
std::cerr << name_ + " just stopped, is it endless?" << std::endl;
dp.emit();
if (exit_code != 0) {
output_ = { exit_code, "" };
dp.emit();
std::cerr << name_ + " just stopped unexpectedly, is it endless?" << std::endl;
}
return;
}
std::string output = buff;
// Remove last newline
@ -75,6 +77,13 @@ void waybar::modules::Custom::continuousWorker()
};
}
void waybar::modules::Custom::refresh(int sig /*signal*/)
{
if(sig == SIGRTMIN + config_["signal"].asInt()) {
thread_.wake_up();
}
}
auto waybar::modules::Custom::update() -> void
{
// Hide label if output is empty
@ -88,13 +97,16 @@ auto waybar::modules::Custom::update() -> void
}
auto str = fmt::format(format_, text_,
fmt::arg("icon", getIcon(percentage_)),
fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_));
label_.set_markup(str);
if (text_ == tooltip_) {
label_.set_tooltip_text(str);
} else {
label_.set_tooltip_text(tooltip_);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
label_.set_tooltip_text(str);
} else {
label_.set_tooltip_text(tooltip_);
}
}
if (class_ != "") {
if (prevclass_ != "") {
@ -118,7 +130,11 @@ void waybar::modules::Custom::parseOutputRaw()
int i = 0;
while (getline(output, line)) {
if (i == 0) {
text_ = line;
if (config_["escape"].isBool() && config_["escape"].asBool()) {
text_ = Glib::Markup::escape_text(line);
} else {
text_ = line;
}
tooltip_ = line;
class_ = "";
} else if (i == 1) {
@ -138,7 +154,16 @@ void waybar::modules::Custom::parseOutputJson()
std::string line;
while (getline(output, line)) {
auto parsed = parser_.parse(line);
text_ = parsed["text"].asString();
if (config_["escape"].isBool() && config_["escape"].asBool()) {
text_ = Glib::Markup::escape_text(parsed["text"].asString());
} else {
text_ = parsed["text"].asString();
}
if (config_["escape"].isBool() && config_["escape"].asBool()) {
alt_ = Glib::Markup::escape_text(parsed["alt"].asString());
} else {
alt_ = parsed["alt"].asString();
}
tooltip_ = parsed["tooltip"].asString();
class_ = parsed["class"].asString();
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) {

View File

@ -0,0 +1,57 @@
#include "modules/idle_inhibitor.hpp"
#include "util/command.hpp"
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "{status}"), bar_(bar), status_("deactivated"), idle_inhibitor_(nullptr)
{
label_.set_name("idle_inhibitor");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
dp.emit();
}
waybar::modules::IdleInhibitor::~IdleInhibitor()
{
if(idle_inhibitor_) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
}
auto waybar::modules::IdleInhibitor::update() -> void
{
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_),
fmt::arg("icon", getIcon(0, status_))));
label_.get_style_context()->add_class(status_);
if(tooltipEnabled()) {
label_.set_tooltip_text(status_);
}
}
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
if (e->button == 1) {
label_.get_style_context()->remove_class(status_);
if (idle_inhibitor_) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
status_ = "deactivated";
} else {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
bar_.client.idle_inhibit_manager, bar_.surface);
status_ = "activated";
}
if (config_["on-click"].isString() && e->button == 1) {
waybar::util::command::forkExec(config_["on-click"].asString());
}
} else {
ALabel::handleToggle(e);
}
dp.emit();
return true;
}

View File

@ -20,7 +20,9 @@ auto waybar::modules::Memory::update() -> void
int used_ram_percentage = 100 * (memtotal_ - memfree_) / memtotal_;
label_.set_markup(fmt::format(format_, used_ram_percentage));
auto used_ram_gigabytes = (memtotal_ - memfree_) / std::pow(1024, 2);
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
}
event_box_.show();
} else {
event_box_.hide();

View File

@ -55,8 +55,8 @@ void waybar::modules::Network::createInfoSocket()
{
info_sock_ = nl_socket_alloc();
if (nl_connect(info_sock_, NETLINK_ROUTE) != 0) {
throw std::runtime_error("Can't connect network socket");
}
throw std::runtime_error("Can't connect network socket");
}
if (nl_socket_add_membership(info_sock_, RTMGRP_LINK) != 0) {
throw std::runtime_error("Can't add membership");
}
@ -65,7 +65,7 @@ void waybar::modules::Network::createInfoSocket()
}
nl_socket_disable_seq_check(info_sock_);
nl_socket_set_nonblocking(info_sock_);
nl_socket_modify_cb(info_sock_, NL_CB_MSG_IN, NL_CB_CUSTOM, handleEvents, this);
nl_socket_modify_cb(info_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
efd_ = epoll_create1(0);
if (efd_ < 0) {
throw std::runtime_error("Can't create epoll");
@ -82,7 +82,7 @@ void waybar::modules::Network::createInfoSocket()
{
auto fd = nl_socket_get_fd(info_sock_);
struct epoll_event event;
event.events = EPOLLIN;
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");
@ -93,7 +93,7 @@ void waybar::modules::Network::createInfoSocket()
void waybar::modules::Network::createEventSocket()
{
sk_ = nl_socket_alloc();
if (genl_connect(sk_) != 0) {
if (genl_connect(sk_) != 0) {
throw std::runtime_error("Can't connect to netlink socket");
}
if (nl_socket_modify_cb(sk_, NL_CB_VALID, NL_CB_CUSTOM, handleScan, this) < 0) {
@ -114,13 +114,12 @@ void waybar::modules::Network::worker()
}
thread_timer_.sleep_for(interval_);
};
thread_ = [this] {
struct epoll_event events[16];
int ec = epoll_wait(efd_, events, 16, -1);
struct epoll_event events[EPOLL_MAX];
thread_ = [this, &events] {
int ec = epoll_wait(efd_, events, EPOLL_MAX, -1);
if (ec > 0) {
for (auto i = 0; i < ec; i++) {
if (events[i].events & EPOLLIN
&& events[i].data.fd == nl_socket_get_fd(info_sock_)) {
if (events[i].data.fd == nl_socket_get_fd(info_sock_)) {
nl_recvmsgs_default(info_sock_);
} else {
thread_.stop();
@ -136,10 +135,17 @@ void waybar::modules::Network::worker()
auto waybar::modules::Network::update() -> void
{
std::string connectiontype;
std::string tooltip_format = "";
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (ifid_ <= 0 || ipaddr_.empty()) {
if (config_["format-disconnected"].isString()) {
default_format_ = config_["format-disconnected"].asString();
}
if (config_["tooltip-format-disconnected"].isString()) {
tooltip_format = config_["tooltip-format-disconnected"].asString();
}
label_.get_style_context()->add_class("disconnected");
connectiontype = "disconnected";
} else {
@ -147,11 +153,17 @@ auto waybar::modules::Network::update() -> void
if (config_["format-ethernet"].isString()) {
default_format_ = config_["format-ethernet"].asString();
}
if (config_["tooltip-format-ethernet"].isString()) {
tooltip_format = config_["tooltip-format-ethernet"].asString();
}
connectiontype = "ethernet";
} else {
if (config_["format-wifi"].isString()) {
default_format_ = config_["format-wifi"].asString();
}
if (config_["tooltip-format-wifi"].isString()) {
tooltip_format = config_["tooltip-format-wifi"].asString();
}
connectiontype = "wifi";
}
label_.get_style_context()->remove_class("disconnected");
@ -159,7 +171,7 @@ auto waybar::modules::Network::update() -> void
if (!alt_) {
format_ = default_format_;
}
label_.set_markup(fmt::format(format_,
auto text = fmt::format(format_,
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
@ -168,7 +180,25 @@ auto waybar::modules::Network::update() -> void
fmt::arg("ipaddr", ipaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("icon", getIcon(signal_strength_, connectiontype))
));
);
label_.set_markup(text);
if (tooltipEnabled()) {
if (!tooltip_format.empty()) {
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("icon", getIcon(signal_strength_, connectiontype))
);
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(text);
}
}
}
void waybar::modules::Network::disconnected()
@ -313,6 +343,9 @@ out:
void waybar::modules::Network::getInterfaceAddress() {
unsigned int cidrRaw;
struct ifaddrs *ifaddr, *ifa;
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
int success = getifaddrs(&ifaddr);
if (success == 0) {
ifa = ifaddr;
@ -333,10 +366,6 @@ void waybar::modules::Network::getInterfaceAddress() {
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
} else {
ipaddr_.clear();
netmask_.clear();
cidr_ = 0;
}
}
@ -367,25 +396,36 @@ int waybar::modules::Network::netlinkResponse(void *resp,
}
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
int ret = 0;
auto net = static_cast<waybar::modules::Network *>(data);
bool need_update = false;
nlmsghdr *nh = nlmsg_hdr(msg);
if (nh->nlmsg_type == RTM_NEWADDR) {
need_update = true;
}
if (nh->nlmsg_type < RTM_NEWADDR) {
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (rtif->ifi_index == static_cast<int>(net->ifid_)) {
for (nlmsghdr *nh = nlmsg_hdr(msg); NLMSG_OK(nh, ret);
nh = NLMSG_NEXT(nh, ret)) {
if (nh->nlmsg_type == RTM_NEWADDR) {
need_update = true;
if (!(rtif->ifi_flags & IFF_RUNNING)) {
net->disconnected();
}
if (nh->nlmsg_type < RTM_NEWADDR) {
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (rtif->ifi_index == static_cast<int>(net->ifid_)) {
need_update = true;
if (!(rtif->ifi_flags & IFF_RUNNING)) {
net->disconnected();
net->dp.emit();
return NL_SKIP;
}
}
}
if (need_update) break;
}
if (net->ifid_ <= 0 && !net->config_["interface"].isString()) {
// Need to wait before get external interface
net->thread_.sleep_for(std::chrono::seconds(1));
net->ifid_ = net->getExternalInterface();
for (uint8_t i = 0; i < MAX_RETRY; i += 1) {
net->ifid_ = net->getExternalInterface();
if (net->ifid_ > 0) {
break;
}
// Need to wait before get external interface
net->thread_.sleep_for(std::chrono::seconds(1));
}
if (net->ifid_ > 0) {
char ifname[IF_NAMESIZE];
if_indextoname(net->ifid_, ifname);
@ -451,7 +491,9 @@ void waybar::modules::Network::parseEssid(struct nlattr **bss)
if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
auto essid_begin = ies + hdr_len;
auto essid_end = essid_begin + ies[1];
std::copy(essid_begin, essid_end, std::back_inserter(essid_));
std::string essid_raw;
std::copy(essid_begin, essid_end, std::back_inserter(essid_raw));
essid_ = Glib::Markup::escape_text(essid_raw);
}
}
}

View File

@ -1,4 +1,5 @@
#include "modules/pulseaudio.hpp"
#include <array>
waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value &config)
: ALabel(config, "{volume}%"),
@ -39,7 +40,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value
// events are configured
if (!config["on-scroll-up"].isString() &&
!config["on-scroll-down"].isString()) {
event_box_.add_events(Gdk::SCROLL_MASK);
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(
sigc::mem_fun(*this, &Pulseaudio::handleScroll));
}
@ -63,13 +64,13 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data)
pa_context_set_subscribe_callback(c, subscribeCb, data);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr);
break;
case PA_CONTEXT_FAILED:
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break;
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
case PA_CONTEXT_FAILED:
default:
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
break;
}
}
@ -174,21 +175,24 @@ void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context,
sinkInfoCb, data);
}
static const std::array<std::string, 9> ports = {
"headphones",
"speaker",
"hdmi",
"headset",
"handsfree",
"portable",
"car",
"hifi",
"phone",
};
const std::string waybar::modules::Pulseaudio::getPortIcon() const
{
std::vector<std::string> ports = {
"headphones",
"speaker",
"hdmi",
"headset",
"handsfree",
"portable",
"car",
"hifi",
"phone",
};
std::string nameLC = port_name_;
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const& port : ports) {
if (port_name_.find(port) != std::string::npos) {
if (nameLC.find(port) != std::string::npos) {
return port;
}
}
@ -202,18 +206,22 @@ auto waybar::modules::Pulseaudio::update() -> void
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"].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");
if (port_name_.find("a2dp_sink") != std::string::npos) {
format = config_["format-bluetooth"].isString()
? config_["format-bluetooth"].asString() : format;
label_.get_style_context()->add_class("bluetooth");
} else {
label_.get_style_context()->remove_class("bluetooth");
}
}
label_.set_markup(
fmt::format(format, fmt::arg("volume", volume_),
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
label_.set_tooltip_text(desc_);
if (tooltipEnabled()) {
label_.set_tooltip_text(desc_);
}
if (scrolling_) {
scrolling_ = false;
}

View File

@ -1,10 +1,16 @@
#include "modules/sni/item.hpp"
#include <iostream>
#include <glibmm/main.h>
using namespace Glib;
static const ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name;
static const unsigned UPDATE_DEBOUNCE_TIME = 10;
waybar::modules::SNI::Item::Item(std::string bn, std::string op, const Json::Value& config)
: bus_name(bn), object_path(op), icon_size(16), effective_icon_size(0)
{
: bus_name(bn), object_path(op), icon_size(16), effective_icon_size(0),
update_pending_(false) {
if (config["icon-size"].isUInt()) {
icon_size = config["icon-size"].asUInt();
}
@ -12,107 +18,148 @@ waybar::modules::SNI::Item::Item(std::string bn, std::string op, const Json::Val
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);
cancellable_ = Gio::Cancellable::create();
auto interface = Glib::wrap(sn_item_interface_info(), true);
Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name,
object_path, SNI_INTERFACE_NAME, sigc::mem_fun(*this, &Item::proxyReady),
cancellable_, interface);
}
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);
void waybar::modules::SNI::Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
try {
this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result);
/* Properties are already cached during object creation */
auto cached_properties = this->proxy_->get_cached_property_names();
for (const auto& name: cached_properties) {
Glib::VariantBase value;
this->proxy_->get_cached_property(value, name);
setProperty(name, value);
}
g_variant_unref(value);
g_free(key);
this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
if (this->id.empty() || this->category.empty() || this->status.empty()) {
std::cerr << "Invalid Status Notifier Item: " + this->bus_name + "," +
this->object_path << std::endl;
return;
}
if (!this->icon_theme_path.empty()) {
Glib::RefPtr<Gtk::IconTheme> icon_theme = Gtk::IconTheme::get_default();
icon_theme->append_search_path(this->icon_theme_path);
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
g_error("Failed to create DBus Proxy for %s %s: %s", bus_name.c_str(),
object_path.c_str(), err.what().c_str());
} catch (const std::exception& err) {
g_error("Failed to create DBus Proxy for %s %s: %s", bus_name.c_str(),
object_path.c_str(), err.what());
}
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;
}
template<typename T>
T get_variant(VariantBase& value) {
return VariantBase::cast_dynamic<Variant<T>>(value).get();
}
void
waybar::modules::SNI::Item::setProperty(const ustring& name,
VariantBase& value) {
if (name == "Category") {
category = get_variant<std::string>(value);
} else if (name == "Id") {
id = get_variant<std::string>(value);
} else if (name == "Title") {
title = get_variant<std::string>(value);
} else if (name == "Status") {
status = get_variant<std::string>(value);
} else if (name == "WindowId") {
window_id = get_variant<int32_t>(value);
} else if (name == "IconName") {
icon_name = get_variant<std::string>(value);
} else if (name == "IconPixmap") {
icon_pixmap = this->extractPixBuf(value.gobj());
} else if (name == "OverlayIconName") {
overlay_icon_name = get_variant<std::string>(value);
} else if (name == "OverlayIconPixmap") {
// TODO: overlay_icon_pixmap
} else if (name == "AttentionIconName") {
attention_icon_name = get_variant<std::string>(value);
} else if (name == "AttentionIconPixmap") {
// TODO: attention_icon_pixmap
} else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value);
} else if (name == "ToolTip") {
// TODO: tooltip
} else if (name == "IconThemePath") {
icon_theme_path = get_variant<std::string>(value);
} else if (name == "Menu") {
menu = get_variant<std::string>(value);
} else if (name == "ItemIsMenu") {
item_is_menu = get_variant<bool>(value);
}
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());
}
void
waybar::modules::SNI::Item::getUpdatedProperties() {
update_pending_ = false;
auto params = VariantContainerBase::create_tuple({
Variant<ustring>::create(SNI_INTERFACE_NAME)
});
proxy_->call("org.freedesktop.DBus.Properties.GetAll",
sigc::mem_fun(*this, &Item::processUpdatedProperties), params);
};
void
waybar::modules::SNI::Item::processUpdatedProperties(
Glib::RefPtr<Gio::AsyncResult>& _result) {
try {
auto result = proxy_->call_finish(_result);
// extract "a{sv}" from VariantContainerBase
Variant<std::map<ustring, VariantBase>> properties_variant;
result.get_child(properties_variant);
auto properties = properties_variant.get();
for (const auto& [name, value]: properties) {
VariantBase old_value;
proxy_->get_cached_property(old_value, name);
if (!value.equal(old_value)) {
proxy_->set_cached_property(name, value);
setProperty(name, const_cast<VariantBase&>(value));
}
}
this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) {
g_warning("Failed to update properties: %s", err.what().c_str());
} catch (const std::exception& err) {
g_warning("Failed to update properties: %s", err.what());
}
item->updateImage();
// item->event_box.set_tooltip_text(item->title);
// TODO: handle change
}
void
waybar::modules::SNI::Item::onSignal(const ustring& sender_name,
const ustring& signal_name, const VariantContainerBase& arguments) {
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) {
/* Debounce signals and schedule update of all properties.
* Based on behavior of Plasma dataengine for StatusNotifierItem.
*/
update_pending_ = true;
Glib::signal_timeout().connect_once(
sigc::mem_fun(*this, &Item::getUpdatedProperties), UPDATE_DEBOUNCE_TIME);
}
}
static void
pixbuf_data_deleter(const guint8* data) {
g_free((void*) data);
}
Glib::RefPtr<Gdk::Pixbuf>
@ -158,7 +205,8 @@ waybar::modules::SNI::Item::extractPixBuf(GVariant *variant) {
array[i + 3] = alpha;
}
return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB,
true, 8, lwidth, lheight, 4 * lwidth);
true, 8, lwidth, lheight, 4 * lwidth,
&pixbuf_data_deleter);
}
return Glib::RefPtr<Gdk::Pixbuf>{};
}
@ -209,7 +257,7 @@ waybar::modules::SNI::Item::getIconByName(std::string name, int request_size) {
if (size == request_size || size == -1) {
tmp_size = request_size;
break;
} else if (size < request_size || size > tmp_size) {
} else if (size < request_size || (size > tmp_size && tmp_size > 0)) {
tmp_size = size;
}
}
@ -251,15 +299,21 @@ bool waybar::modules::SNI::Item::makeMenu(GdkEventButton *const &ev)
}
bool waybar::modules::SNI::Item::handleClick(GdkEventButton *const &ev) {
auto parameters = VariantContainerBase::create_tuple({
Variant<int>::create(ev->x),
Variant<int>::create(ev->y)
});
if ((ev->button == 1 && item_is_menu) || ev->button == 3) {
if (!makeMenu(ev)) {
return sn_item_call_context_menu_sync(proxy_, ev->x, ev->y, nullptr, nullptr);
proxy_->call("ContextMenu", parameters);
return true;
}
} else if (ev->button == 1) {
return sn_item_call_activate_sync(proxy_, ev->x, ev->y, nullptr, nullptr);
proxy_->call("Activate", parameters);
return true;
} else if (ev->button == 2) {
return sn_item_call_secondary_activate_sync(proxy_, ev->x, ev->y,
nullptr, nullptr);
proxy_->call("SecondaryActivate", parameters);
return true;
}
return false;
}

View File

@ -2,8 +2,11 @@
#include <iostream>
waybar::modules::SNI::Tray::Tray(const std::string& id, const Json::Value &config)
: config_(config), watcher_(), host_(nb_hosts_, config,
waybar::modules::SNI::Tray::Tray(const std::string& id, const Bar& bar,
const Json::Value &config)
: config_(config),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
watcher_(), host_(nb_hosts_, config,
std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::bind(&Tray::onRemove, this, std::placeholders::_1))
{

View File

@ -64,7 +64,7 @@ struct waybar::modules::sway::Ipc::ipc_response
waybar::modules::sway::Ipc::recv(int fd) const
{
std::string header;
header.reserve(ipc_header_size_);
header.resize(ipc_header_size_);
auto data32 = reinterpret_cast<uint32_t *>(header.data() + ipc_magic_.size());
size_t total = 0;
@ -83,7 +83,7 @@ struct waybar::modules::sway::Ipc::ipc_response
total = 0;
std::string payload;
payload.reserve(data32[0] + 1);
payload.resize(data32[0]);
while (total < data32[0]) {
auto res = ::recv(fd, payload.data() + total, data32[0] - total, 0);
if (res < 0) {
@ -91,7 +91,6 @@ struct waybar::modules::sway::Ipc::ipc_response
}
total += res;
}
payload[data32[0]] = 0;
return { data32[0], data32[1], &payload.front() };
}
@ -100,7 +99,7 @@ struct waybar::modules::sway::Ipc::ipc_response
const std::string& payload) const
{
std::string header;
header.reserve(ipc_header_size_);
header.resize(ipc_header_size_);
auto data32 = reinterpret_cast<uint32_t *>(header.data() + ipc_magic_.size());
memcpy(header.data(), ipc_magic_.c_str(), ipc_magic_.size());
data32[0] = payload.size();

View File

@ -37,7 +37,9 @@ auto waybar::modules::sway::Mode::update() -> void
event_box_.hide();
} else {
label_.set_markup(fmt::format(format_, mode_));
label_.set_tooltip_text(mode_);
if (tooltipEnabled()) {
label_.set_tooltip_text(mode_);
}
event_box_.show();
}
}

View File

@ -48,7 +48,9 @@ void waybar::modules::sway::Window::worker()
auto waybar::modules::sway::Window::update() -> void
{
label_.set_markup(fmt::format(format_, window_));
label_.set_tooltip_text(window_);
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
}
}
std::tuple<int, std::string> waybar::modules::sway::Window::getFocusedNode(

View File

@ -2,7 +2,9 @@
waybar::modules::sway::Workspaces::Workspaces(const std::string& id, const Bar& bar,
const Json::Value& config)
: bar_(bar), config_(config), scrolling_(false)
: bar_(bar), config_(config),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
scrolling_(false)
{
box_.set_name("workspaces");
if (!id.empty()) {
@ -17,12 +19,7 @@ void waybar::modules::sway::Workspaces::worker()
{
thread_ = [this] {
try {
// Wait for the name of the output
if (!config_["all-outputs"].asBool() && bar_.output_name.empty()) {
while (bar_.output_name.empty()) {
thread_.sleep_for(std::chrono::milliseconds(150));
}
} else if (!workspaces_.empty()) {
if (!workspaces_.empty()) {
ipc_.handleEvent();
}
{
@ -47,8 +44,7 @@ auto waybar::modules::sway::Workspaces::update() -> void
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),
[it](auto node) -> bool { return node["name"].asString() == it->first; });
if (ws == workspaces_.end() ||
(!config_["all-outputs"].asBool() &&
(*ws)["output"].asString() != bar_.output_name)) {
(!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output_name)) {
it = buttons_.erase(it);
needReorder = true;
} else {
@ -82,7 +78,7 @@ auto waybar::modules::sway::Workspaces::update() -> void
button.get_style_context()->remove_class("urgent");
}
if (needReorder) {
box_.reorder_child(button, node["num"].asInt());
box_.reorder_child(button, getWorkspaceIndex(node["name"].asString()));
}
auto icon = getIcon(node["name"].asString(), node);
if (config_["format"].isString()) {
@ -93,7 +89,7 @@ auto waybar::modules::sway::Workspaces::update() -> void
} else {
button.set_label(icon);
}
button.show();
onButtonReady(node, button);
}
}
if (scrolling_) {
@ -101,7 +97,7 @@ auto waybar::modules::sway::Workspaces::update() -> void
}
}
void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node)
void waybar::modules::sway::Workspaces::addWorkspace(const Json::Value &node)
{
auto icon = getIcon(node["name"].asString(), node);
auto format = config_["format"].isString()
@ -122,12 +118,12 @@ void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node)
std::cerr << e.what() << std::endl;
}
});
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
if (!config_["disable-scroll"].asBool()) {
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
button.signal_scroll_event()
.connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
box_.reorder_child(button, node["num"].asInt());
box_.reorder_child(button, getWorkspaceIndex(node["name"].asString()));
if (node["focused"].asBool()) {
button.get_style_context()->add_class("focused");
}
@ -137,14 +133,14 @@ void waybar::modules::sway::Workspaces::addWorkspace(Json::Value node)
if (node["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
}
button.show();
onButtonReady(node, button);
}
std::string waybar::modules::sway::Workspaces::getIcon(std::string name,
Json::Value node)
std::string waybar::modules::sway::Workspaces::getIcon(const std::string &name,
const Json::Value &node)
{
std::vector<std::string> keys = {
name, "urgent", "focused", "visible", "default"};
std::vector<std::string> keys = { name, "urgent", "focused", "visible", "default" };
for (auto const& key : keys) {
if (key == "focused" || key == "visible" || key == "urgent") {
if (config_["format-icons"][key].isString() && node[key].asBool()) {
@ -163,78 +159,82 @@ bool waybar::modules::sway::Workspaces::handleScroll(GdkEventScroll *e)
if (scrolling_) {
return false;
}
std::lock_guard<std::mutex> lock(mutex_);
uint8_t idx;
scrolling_ = true;
std::string name;
uint16_t idx = 0;
{
std::lock_guard<std::mutex> lock(mutex_);
for (; idx < workspaces_.size(); idx += 1) {
if (workspaces_[idx]["focused"].asBool()) {
name = workspaces_[idx]["name"].asString();
break;
}
for (idx = 0; idx < workspaces_.size(); idx += 1) {
if (workspaces_[idx]["focused"].asBool()) {
break;
}
}
if (name.empty()) {
if (idx == workspaces_.size()) {
scrolling_ = false;
return false;
}
std::string name;
if (e->direction == GDK_SCROLL_UP) {
name = getNextWorkspace();
name = getCycleWorkspace(idx, true);
}
if (e->direction == GDK_SCROLL_DOWN) {
name = getPrevWorkspace();
name = getCycleWorkspace(idx, 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) {
name = getNextWorkspace();
name = getCycleWorkspace(idx, true);
} else if (delta_y > 0) {
name = getPrevWorkspace();
name = getCycleWorkspace(idx, false);
}
}
if (!name.empty()) {
std::lock_guard<std::mutex> lock(mutex_);
if (name == workspaces_[idx]["name"].asString()) {
scrolling_ = false;
return false;
}
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
std::this_thread::sleep_for(std::chrono::milliseconds(150));
if (name.empty() || name == workspaces_[idx]["name"].asString()) {
scrolling_ = false;
return false;
}
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
std::this_thread::sleep_for(std::chrono::milliseconds(150));
return true;
}
std::string waybar::modules::sway::Workspaces::getPrevWorkspace()
const std::string waybar::modules::sway::Workspaces::getCycleWorkspace(
uint8_t focused_workspace, bool prev) const
{
for (uint16_t i = 0; i != workspaces_.size(); i += 1) {
if (workspaces_[i]["focused"].asBool()) {
if (i > 0) {
return workspaces_[i - 1]["name"].asString();
}
return workspaces_[workspaces_.size() - 1]["name"].asString();
auto inc = prev ? -1 : 1;
int size = workspaces_.size();
uint8_t idx = 0;
for (int i = focused_workspace; i < size && i >= 0; i += inc) {
bool same_output = (workspaces_[i]["output"].asString() == bar_.output_name
&& !config_["all-outputs"].asBool()) || config_["all-outputs"].asBool();
bool same_name =
workspaces_[i]["name"].asString() == workspaces_[focused_workspace]["name"].asString();
if (same_output && !same_name) {
return workspaces_[i]["name"].asString();
}
if (prev && i - 1 < 0) {
i = size;
} else if (!prev && i + 1 >= size) {
i = -1;
} else if (idx >= workspaces_.size()) {
return "";
}
idx += 1;
}
return "";
}
std::string waybar::modules::sway::Workspaces::getNextWorkspace()
uint16_t waybar::modules::sway::Workspaces::getWorkspaceIndex(const std::string &name) const
{
for (uint16_t i = 0; i != workspaces_.size(); i += 1) {
if (workspaces_[i]["focused"].asBool()) {
if (i + 1U < workspaces_.size()) {
return workspaces_[i + 1]["name"].asString();
}
return workspaces_[0]["String"].asString();
uint16_t idx = 0;
for (const auto &workspace : workspaces_) {
if (workspace["name"].asString() == name) {
return idx;
}
if (!(!config_["all-outputs"].asBool() && workspace["output"].asString() != bar_.output_name)) {
idx += 1;
}
}
return "";
}
waybar::modules::sway::Workspaces::operator Gtk::Widget &() {
return box_;
return workspaces_.size();
}
std::string waybar::modules::sway::Workspaces::trimWorkspaceName(std::string name)
@ -245,3 +245,20 @@ std::string waybar::modules::sway::Workspaces::trimWorkspaceName(std::string nam
}
return name;
}
void waybar::modules::sway::Workspaces::onButtonReady(const Json::Value& node, Gtk::Button& button)
{
if (config_["current-only"].asBool()) {
if (node["focused"].asBool()) {
button.show();
} else {
button.hide();
}
} else {
button.show();
}
}
waybar::modules::sway::Workspaces::operator Gtk::Widget &() {
return box_;
}

View File

@ -0,0 +1,60 @@
#include "modules/temperature.hpp"
waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config)
: ALabel(config, "{temperatureC}°C", 10)
{
if (config_["hwmon-path"].isString()) {
file_path_ = config_["hwmon-path"].asString();
} else {
auto zone =
config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0;
file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone);
}
label_.set_name("temperature");
if (!id.empty()) {
label_.get_style_context()->add_class(id);
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
auto waybar::modules::Temperature::update() -> void
{
auto [temperature_c, temperature_f] = getTemperature();
auto critical = isCritical(temperature_c);
auto format = format_;
if (critical) {
format =
config_["format-critical"].isString() ? config_["format-critical"].asString() : format;
label_.get_style_context()->add_class("critical");
} else {
label_.get_style_context()->remove_class("critical");
}
label_.set_markup(fmt::format(format,
fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f)));
}
std::tuple<uint16_t, uint16_t> waybar::modules::Temperature::getTemperature()
{
std::ifstream temp(file_path_);
if (!temp.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
}
std::string line;
if (temp.good()) {
getline(temp, line);
}
temp.close();
auto temperature_c = std::strtol(line.c_str(), nullptr, 10) / 1000.0;
auto temperature_f = temperature_c * 1.8 + 32;
std::tuple<uint16_t, uint16_t> temperatures(std::round(temperature_c), std::round(temperature_f));
return temperatures;
}
bool waybar::modules::Temperature::isCritical(uint16_t temperature_c)
{
return config_["critical-threshold"].isInt() && temperature_c >= config_["critical-threshold"].asInt();
}