Merge branch 'master' into taskbar/remove-trim

pull/825/head
Alex 2020-10-11 23:06:32 +02:00 committed by GitHub
commit d6381eeaff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 902 additions and 169 deletions

View File

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

View File

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

View File

@ -3,4 +3,4 @@
FROM archlinux/base:latest FROM archlinux/base:latest
RUN pacman -Syu --noconfirm && \ RUN pacman -Syu --noconfirm && \
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient --noconfirm pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm

View File

@ -3,5 +3,5 @@
FROM debian:sid FROM debian:sid
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection && \ apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev && \
apt-get clean apt-get clean

View File

@ -5,7 +5,7 @@
[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br> [AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* > *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
**Current features** #### Current features
- Sway (Workspaces, Binding mode, Focused window name) - Sway (Workspaces, Binding mode, Focused window name)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time
@ -22,11 +22,21 @@
- Multiple output configuration - Multiple output configuration
- And much more customizations - And much more customizations
**Configuration and Styling** #### Configuration and Styling
[See the wiki for more details](https://github.com/Alexays/Waybar/wiki). [See the wiki for more details](https://github.com/Alexays/Waybar/wiki).
**How to build** ### Installation
Waybar is available from a number of Linux distributions:
[![Packaging status](https://repology.org/badge/vertical-allrepos/waybar.svg)](https://repology.org/project/waybar/versions)
An Ubuntu PPA with more recent versions is available
[here](https://launchpad.net/~nschloe/+archive/ubuntu/waybar).
#### Building from source
```bash ```bash
$ git clone https://github.com/Alexays/Waybar $ git clone https://github.com/Alexays/Waybar
@ -57,6 +67,7 @@ libnl [Network module]
libappindicator-gtk3 [Tray module] libappindicator-gtk3 [Tray module]
libdbusmenu-gtk3 [Tray module] libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module] libmpdclient [MPD module]
libsndio [sndio module]
``` ```
**Build dependencies** **Build dependencies**

View File

@ -10,7 +10,7 @@ namespace waybar {
class ALabel : public AModule { class ALabel : public AModule {
public: public:
ALabel(const Json::Value &, const std::string &, const std::string &, const std::string &format, ALabel(const Json::Value &, const std::string &, const std::string &, const std::string &format,
uint16_t interval = 0, bool ellipsize = false); uint16_t interval = 0, bool ellipsize = false, bool enable_click = false, bool enable_scroll = false);
virtual ~ALabel() = default; virtual ~ALabel() = default;
virtual auto update() -> void; virtual auto update() -> void;
virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0); virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0);

View File

@ -53,13 +53,18 @@ class Bar {
static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *); static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *);
#ifdef HAVE_GTK_LAYER_SHELL #ifdef HAVE_GTK_LAYER_SHELL
/* gtk-layer-shell code */
void initGtkLayerShell(); void initGtkLayerShell();
void onConfigureGLS(GdkEventConfigure *ev);
void onMapGLS(GdkEventAny *ev);
#endif #endif
/* fallback layer-surface code */
void onConfigure(GdkEventConfigure *ev); void onConfigure(GdkEventConfigure *ev);
void onRealize(); void onRealize();
void onMap(GdkEventAny *ev); void onMap(GdkEventAny *ev);
void setExclusiveZone(uint32_t width, uint32_t height);
void setSurfaceSize(uint32_t width, uint32_t height); void setSurfaceSize(uint32_t width, uint32_t height);
/* common code */
void setExclusiveZone(uint32_t width, uint32_t height);
auto setupWidgets() -> void; auto setupWidgets() -> void;
void getModules(const Factory &, const std::string &); void getModules(const Factory &, const std::string &);
void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModule(const std::string &module_name);

View File

@ -39,11 +39,16 @@
#ifdef HAVE_LIBMPDCLIENT #ifdef HAVE_LIBMPDCLIENT
#include "modules/mpd.hpp" #include "modules/mpd.hpp"
#endif #endif
#ifdef HAVE_LIBSNDIO
#include "modules/sndio.hpp"
#endif
#include "bar.hpp" #include "bar.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/temperature.hpp" #include "modules/temperature.hpp"
#if defined(__linux__) #if defined(__linux__)
#include "modules/bluetooth.hpp" # ifdef WANT_RFKILL
# include "modules/bluetooth.hpp"
# endif
#endif #endif
namespace waybar { namespace waybar {

View File

@ -28,9 +28,12 @@ class Clock : public ALabel {
std::locale locale_; std::locale locale_;
const date::time_zone* time_zone_; const date::time_zone* time_zone_;
bool fixed_time_zone_; bool fixed_time_zone_;
int time_zone_idx_;
date::year_month_day cached_calendar_ymd_; date::year_month_day cached_calendar_ymd_;
std::string cached_calendar_text_; std::string cached_calendar_text_;
bool handleScroll(GdkEventScroll* e);
auto calendar_text(const waybar_time& wtime) -> std::string; auto calendar_text(const waybar_time& wtime) -> std::string;
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
auto first_day_of_week() -> date::weekday; auto first_day_of_week() -> date::weekday;

View File

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

View File

@ -11,7 +11,9 @@
#include <sys/epoll.h> #include <sys/epoll.h>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp" #include "util/rfkill.hpp"
#endif
namespace waybar::modules { namespace waybar::modules {
@ -52,6 +54,8 @@ class Network : public ALabel {
struct sockaddr_nl nladdr_ = {0}; struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sock_ = nullptr; struct nl_sock* sock_ = nullptr;
struct nl_sock* ev_sock_ = nullptr; struct nl_sock* ev_sock_ = nullptr;
int efd_;
int ev_fd_;
int nl80211_id_; int nl80211_id_;
std::mutex mutex_; std::mutex mutex_;
@ -70,9 +74,11 @@ class Network : public ALabel {
util::SleeperThread thread_; util::SleeperThread thread_;
util::SleeperThread thread_timer_; util::SleeperThread thread_timer_;
#ifdef WANT_RFKILL
util::SleeperThread thread_rfkill_; util::SleeperThread thread_rfkill_;
util::Rfkill rfkill_; util::Rfkill rfkill_;
#endif
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

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

View File

@ -7,6 +7,9 @@
#include <array> #include <array>
extern std::mutex reap_mtx;
extern std::list<pid_t> reap;
namespace waybar::util::command { namespace waybar::util::command {
struct res { struct res {
@ -32,10 +35,11 @@ inline std::string read(FILE* fp) {
inline int close(FILE* fp, pid_t pid) { inline int close(FILE* fp, pid_t pid) {
int stat = -1; int stat = -1;
pid_t ret;
fclose(fp); fclose(fp);
do { do {
waitpid(pid, &stat, WCONTINUED | WUNTRACED); ret = waitpid(pid, &stat, WCONTINUED | WUNTRACED);
if (WIFEXITED(stat)) { if (WIFEXITED(stat)) {
spdlog::debug("Cmd exited with code {}", WEXITSTATUS(stat)); spdlog::debug("Cmd exited with code {}", WEXITSTATUS(stat));
@ -45,6 +49,8 @@ inline int close(FILE* fp, pid_t pid) {
spdlog::debug("Cmd stopped by {}", WSTOPSIG(stat)); spdlog::debug("Cmd stopped by {}", WSTOPSIG(stat));
} else if (WIFCONTINUED(stat)) { } else if (WIFCONTINUED(stat)) {
spdlog::debug("Cmd continued"); spdlog::debug("Cmd continued");
} else if (ret == -1) {
spdlog::debug("waitpid failed: {}", strerror(errno));
} else { } else {
break; break;
} }
@ -65,6 +71,12 @@ inline FILE* open(const std::string& cmd, int& pid) {
} }
if (!child_pid) { if (!child_pid) {
int err;
sigset_t mask;
sigfillset(&mask);
// Reset sigmask
err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
if (err != 0) spdlog::error("pthread_sigmask in open failed: {}", strerror(err));
::close(fd[0]); ::close(fd[0]);
dup2(fd[1], 1); dup2(fd[1], 1);
setpgid(child_pid, child_pid); setpgid(child_pid, child_pid);
@ -97,7 +109,7 @@ inline struct res execNoRead(const std::string& cmd) {
inline int32_t forkExec(const std::string& cmd) { inline int32_t forkExec(const std::string& cmd) {
if (cmd == "") return -1; if (cmd == "") return -1;
int32_t pid = fork(); pid_t pid = fork();
if (pid < 0) { if (pid < 0) {
spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno)); spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno));
@ -106,12 +118,20 @@ inline int32_t forkExec(const std::string& cmd) {
// Child executes the command // Child executes the command
if (!pid) { if (!pid) {
int err;
sigset_t mask;
sigfillset(&mask);
// Reset sigmask
err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
if (err != 0) spdlog::error("pthread_sigmask in forkExec failed: {}", strerror(err));
setpgid(pid, pid); setpgid(pid, pid);
signal(SIGCHLD, SIG_DFL);
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0); exit(0);
} else { } else {
signal(SIGCHLD, SIG_IGN); reap_mtx.lock();
reap.push_back(pid);
reap_mtx.unlock();
spdlog::debug("Added child to reap list: {}", pid);
} }
return pid; return pid;

View File

@ -23,7 +23,7 @@ namespace fmt {
constexpr auto parse(ParseContext& ctx) -> decltype (ctx.begin()) { constexpr auto parse(ParseContext& ctx) -> decltype (ctx.begin()) {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == ':') ++it; if (it != end && *it == ':') ++it;
if (*it == '>' || *it == '<' || *it == '=') { if (it && (*it == '>' || *it == '<' || *it == '=')) {
spec = *it; spec = *it;
++it; ++it;
} }

View File

@ -26,11 +26,21 @@ The *clock* module displays the current date and time.
default: inferred local timezone ++ default: inferred local timezone ++
The timezone to display the time in, e.g. America/New_York. The timezone to display the time in, e.g. America/New_York.
*timezones*: ++
typeof: list of strings ++
A list of timezones to use for time display, changed using the scroll wheel. ++
Use "" to represent the system's local timezone. Using %Z in the format or tooltip format is useful to track which time zone is currently displayed.
*locale*: ++ *locale*: ++
typeof: string ++ typeof: string ++
default: inferred from current locale ++ default: inferred from current locale ++
A locale to be used to display the time. Intended to render times in custom timezones with the proper language and format. A locale to be used to display the time. Intended to render times in custom timezones with the proper language and format.
*today-format*: ++
typeof: string ++
default: <b><u>{}</u></b> ++
The format of today's date in the calendar.
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++
The maximum length in character the module should display. The maximum length in character the module should display.

View File

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

View File

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

View File

@ -148,6 +148,10 @@ Addressed by *mpd*
*{totalTime}*: The length of the current song. To format as a date/time (see example configuration) *{totalTime}*: The length of the current song. To format as a date/time (see example configuration)
*{songPosition}*: The position of the current song.
*{queueLength}*: The length of the current queue.
*{stateIcon}*: The icon corresponding the playing or paused status of the player (see *state-icons* option) *{stateIcon}*: The icon corresponding the playing or paused status of the player (see *state-icons* option)
*{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option) *{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option)

View File

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

View File

@ -31,6 +31,11 @@ Addressed by *sway/workspaces*
default: false ++ default: false ++
If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled. If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled.
*disable-click*: ++
typeof: bool ++
default: false ++
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
*smooth-scrolling-threshold*: ++ *smooth-scrolling-threshold*: ++
typeof: double ++ typeof: double ++
Threshold to be used when scrolling. Threshold to be used when scrolling.
@ -134,3 +139,4 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
- *#workspaces button.urgent* - *#workspaces button.urgent*
- *#workspaces button.persistent* - *#workspaces button.persistent*
- *#workspaces button.current_output* - *#workspaces button.current_output*
- *#workspaces button#sway-workspace-${name}*

View File

@ -32,6 +32,11 @@ Addressed by *wlr/taskbar*
default: 16 ++ default: 16 ++
The size of the icon. The size of the icon.
*markup*: ++
typeof: bool ++
default: false ++
If set to true, pango markup will be accepted in format and tooltip-format.
*tooltip*: ++ *tooltip*: ++
typeof: bool ++ typeof: bool ++
default: true ++ default: true ++

View File

@ -14,6 +14,7 @@ Valid locations for this file are:
- *~/.config/waybar/config* - *~/.config/waybar/config*
- *~/waybar/config* - *~/waybar/config*
- */etc/xdg/waybar/config* - */etc/xdg/waybar/config*
- *@sysconfdir@/xdg/waybar/config*
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config
Also a minimal example configuration can be found on the at the bottom of this man page. Also a minimal example configuration can be found on the at the bottom of this man page.
@ -185,14 +186,18 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
- *waybar-backlight(5)* - *waybar-backlight(5)*
- *waybar-battery(5)* - *waybar-battery(5)*
- *waybar-bluetooth(5)*
- *waybar-clock(5)* - *waybar-clock(5)*
- *waybar-cpu(5)* - *waybar-cpu(5)*
- *waybar-custom(5)* - *waybar-custom(5)*
- *waybar-disk(5)*
- *waybar-idle-inhibitor(5)* - *waybar-idle-inhibitor(5)*
- *waybar-memory(5)* - *waybar-memory(5)*
- *waybar-mpd(5)* - *waybar-mpd(5)*
- *waybar-network(5)* - *waybar-network(5)*
- *waybar-pulseaudio(5)* - *waybar-pulseaudio(5)*
- *waybar-river-tags(5)*
- *waybar-states(5)*
- *waybar-sway-mode(5)* - *waybar-sway-mode(5)*
- *waybar-sway-window(5)* - *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)* - *waybar-sway-workspaces(5)*

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.9.2', version: '0.9.4',
license: 'MIT', license: 'MIT',
default_options : [ default_options : [
'cpp_std=c++17', 'cpp_std=c++17',
@ -9,6 +9,8 @@ project(
], ],
) )
fs = import('fs')
compiler = meson.get_compiler('cpp') compiler = meson.get_compiler('cpp')
cpp_args = [] cpp_args = []
@ -94,6 +96,19 @@ libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
libpulse = dependency('libpulse', required: get_option('pulseaudio')) libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev')) libudev = dependency('libudev', required: get_option('libudev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd')) libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found()
if not compiler.has_function('sioctl_open', prefix: '#include <sndio.h>', dependencies: libsndio)
if get_option('sndio').enabled()
error('libsndio is too old, required >=1.7.0')
else
warning('libsndio is too old, required >=1.7.0')
libsndio = dependency('', required: false)
endif
endif
endif
gtk_layer_shell = dependency('gtk-layer-shell-0', gtk_layer_shell = dependency('gtk-layer-shell-0',
required: get_option('gtk-layer-shell'), required: get_option('gtk-layer-shell'),
fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep'])
@ -137,12 +152,10 @@ if is_linux
add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp') add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp')
src_files += files( src_files += files(
'src/modules/battery.cpp', 'src/modules/battery.cpp',
'src/modules/bluetooth.cpp',
'src/modules/cpu/common.cpp', 'src/modules/cpu/common.cpp',
'src/modules/cpu/linux.cpp', 'src/modules/cpu/linux.cpp',
'src/modules/memory/common.cpp', 'src/modules/memory/common.cpp',
'src/modules/memory/linux.cpp', 'src/modules/memory/linux.cpp',
'src/util/rfkill.cpp'
) )
elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd
add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp') add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp')
@ -207,6 +220,21 @@ if gtk_layer_shell.found()
add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp') add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp')
endif endif
if libsndio.found()
add_project_arguments('-DHAVE_LIBSNDIO', language: 'cpp')
src_files += 'src/modules/sndio.cpp'
endif
if get_option('rfkill').enabled()
if is_linux
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
src_files += files(
'src/modules/bluetooth.cpp',
'src/util/rfkill.cpp'
)
endif
endif
subdir('protocol') subdir('protocol')
executable( executable(
@ -231,6 +259,7 @@ executable(
libepoll, libepoll,
libmpdclient, libmpdclient,
gtk_layer_shell, gtk_layer_shell,
libsndio,
tz_dep tz_dep
], ],
include_directories: [include_directories('include')], include_directories: [include_directories('include')],
@ -248,9 +277,20 @@ scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_opti
if scdoc.found() if scdoc.found()
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
sh = find_program('sh', native: true) sh = find_program('sh', native: true)
main_manpage = configure_file(
input: 'man/waybar.5.scd.in',
output: 'waybar.5.scd',
configuration: {
'sysconfdir': join_paths(prefix, sysconfdir)
}
)
main_manpage_path = join_paths(meson.build_root(), '@0@'.format(main_manpage))
mandir = get_option('mandir') mandir = get_option('mandir')
man_files = [ man_files = [
'waybar.5.scd', main_manpage_path,
'waybar-backlight.5.scd', 'waybar-backlight.5.scd',
'waybar-battery.5.scd', 'waybar-battery.5.scd',
'waybar-clock.5.scd', 'waybar-clock.5.scd',
@ -271,16 +311,21 @@ if scdoc.found()
'waybar-states.5.scd', 'waybar-states.5.scd',
'waybar-wlr-taskbar.5.scd', 'waybar-wlr-taskbar.5.scd',
'waybar-bluetooth.5.scd', 'waybar-bluetooth.5.scd',
'waybar-sndio.5.scd',
] ]
foreach filename : man_files foreach file : man_files
topic = filename.split('.')[-3].split('/')[-1] path = '@0@'.format(file)
section = filename.split('.')[-2] basename = fs.name(path)
topic = basename.split('.')[-3].split('/')[-1]
section = basename.split('.')[-2]
output = '@0@.@1@'.format(topic, section) output = '@0@.@1@'.format(topic, section)
custom_target( custom_target(
output, output,
input: 'man/@0@'.format(filename), # drops the 'man' if `path` is an absolute path
input: join_paths('man', path),
output: output, output: output,
command: [ command: [
sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output) sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output)

View File

@ -7,3 +7,5 @@ option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable supp
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon') option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')

View File

@ -25,7 +25,7 @@
THIS SOFTWARE. THIS SOFTWARE.
</copyright> </copyright>
<interface name="zwlr_layer_shell_v1" version="1"> <interface name="zwlr_layer_shell_v1" version="3">
<description summary="create surfaces that are layers of the desktop"> <description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and wl_surfaces. Such surfaces are assigned to a "layer" of the output and
@ -82,17 +82,27 @@
<entry name="top" value="2"/> <entry name="top" value="2"/>
<entry name="overlay" value="3"/> <entry name="overlay" value="3"/>
</enum> </enum>
<!-- Version 3 additions -->
<request name="destroy" type="destructor" since="3">
<description summary="destroy the layer_shell object">
This request indicates that the client will not use the layer_shell
object any more. Objects that have been created through this instance
are not affected.
</description>
</request>
</interface> </interface>
<interface name="zwlr_layer_surface_v1" version="1"> <interface name="zwlr_layer_surface_v1" version="3">
<description summary="layer metadata interface"> <description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like are designed to be rendered as a layer of a stacked desktop-like
environment. environment.
Layer surface state (size, anchor, exclusive zone, margin, interactivity) Layer surface state (layer, size, anchor, exclusive zone,
is double-buffered, and will be applied at the time wl_surface.commit of margin, interactivity) is double-buffered, and will be applied at the
the corresponding wl_surface is called. time wl_surface.commit of the corresponding wl_surface is called.
</description> </description>
<request name="set_size"> <request name="set_size">
@ -115,7 +125,7 @@
<request name="set_anchor"> <request name="set_anchor">
<description summary="configures the anchor point of the surface"> <description summary="configures the anchor point of the surface">
Requests that the compositor anchor the surface to the specified edges Requests that the compositor anchor the surface to the specified edges
and corners. If two orthoginal edges are specified (e.g. 'top' and and corners. If two orthogonal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges 'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point (e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified. will be centered on that edge, or in the center if none is specified.
@ -127,20 +137,25 @@
<request name="set_exclusive_zone"> <request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface"> <description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area of the surface Requests that the compositor avoids occluding an area with other
with other surfaces. The compositor's use of this information is surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not implementation-dependent - do not assume that this region will not
actually be occluded. actually be occluded.
A positive value is only meaningful if the surface is anchored to an A positive value is only meaningful if the surface is anchored to one
edge, rather than a corner. The zone is the number of surface-local edge or an edge and both perpendicular edges. If the surface is not
coordinates from the edge that are considered exclusive. anchored, anchored to only two perpendicular edges (a corner), anchored
to only two parallel edges or anchored to all edges, a positive value
will be treated the same as zero.
A positive zone is the distance from the edge in surface-local
coordinates to consider exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding surface indicates that it would like to be moved to avoid occluding
surfaces with a positive excluzive zone. If set to -1, the surface surfaces with a positive exclusive zone. If set to -1, the surface
indicates that it would not like to be moved to accomodate for other indicates that it would not like to be moved to accommodate for other
surfaces, and the compositor should extend it all the way to the edges surfaces, and the compositor should extend it all the way to the edges
it is anchored to. it is anchored to.
@ -281,5 +296,16 @@
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/> <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/> <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
</enum> </enum>
<!-- Version 2 additions -->
<request name="set_layer" since="2">
<description summary="change the layer of the surface">
Change the layer that the surface is rendered on.
Layer is double-buffered, see wl_surface.commit.
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
</interface> </interface>
</protocol> </protocol>

View File

@ -27,7 +27,7 @@
"format": "<span style=\"italic\">{}</span>" "format": "<span style=\"italic\">{}</span>"
}, },
"mpd": { "mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ", "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ ",
"format-disconnected": "Disconnected ", "format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ", "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A", "unknown-tag": "N/A",

View File

@ -41,19 +41,19 @@ window#waybar.chromium {
padding: 0 5px; padding: 0 5px;
background-color: transparent; background-color: transparent;
color: #ffffff; color: #ffffff;
border-bottom: 3px solid transparent; /* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent;
} }
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button:hover { #workspaces button:hover {
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
box-shadow: inherit; box-shadow: inset 0 -3px #ffffff;
border-bottom: 3px solid #ffffff;
} }
#workspaces button.focused { #workspaces button.focused {
background-color: #64727D; background-color: #64727D;
border-bottom: 3px solid #ffffff; box-shadow: inset 0 -3px #ffffff;
} }
#workspaces button.urgent { #workspaces button.urgent {

View File

@ -1,13 +1,11 @@
[Unit] [Unit]
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors. Description=Highly customizable Wayland bar for Sway and Wlroots based compositors.
Documentation=https://github.com/Alexays/Waybar/wiki/ Documentation=https://github.com/Alexays/Waybar/wiki/
PartOf=wayland-session.target PartOf=graphical-session.target
After=wayland-session.target After=graphical-session.target
[Service] [Service]
Type=dbus
BusName=fr.arouillard.waybar
ExecStart=@prefix@/bin/waybar ExecStart=@prefix@/bin/waybar
[Install] [Install]
WantedBy=wayland-session.target WantedBy=graphical-session.target

View File

@ -5,8 +5,9 @@
namespace waybar { namespace waybar {
ALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id, ALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id,
const std::string& format, uint16_t interval, bool ellipsize) const std::string& format, uint16_t interval, bool ellipsize, bool enable_click,
: AModule(config, name, id, config["format-alt"].isString()), bool enable_scroll)
: AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format), format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(config_["interval"] == "once" interval_(config_["interval"] == "once"
? std::chrono::seconds(100000000) ? std::chrono::seconds(100000000)
@ -21,8 +22,10 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
if (config_["max-length"].isUInt()) { if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt()); label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_.set_single_line_mode(true);
} else if (ellipsize && label_.get_max_width_chars() == -1) { } else if (ellipsize && label_.get_max_width_chars() == -1) {
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_.set_single_line_mode(true);
} }
if (config_["rotate"].isUInt()) { if (config_["rotate"].isUInt()) {

View File

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

View File

@ -24,6 +24,9 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
window.get_style_context()->add_class(output->name); window.get_style_context()->add_class(output->name);
window.get_style_context()->add_class(config["name"].asString()); window.get_style_context()->add_class(config["name"].asString());
window.get_style_context()->add_class(config["position"].asString()); window.get_style_context()->add_class(config["position"].asString());
left_.get_style_context()->add_class("modules-left");
center_.get_style_context()->add_class("modules-center");
right_.get_style_context()->add_class("modules-right");
if (config["position"] == "right" || config["position"] == "left") { if (config["position"] == "right" || config["position"] == "left") {
height_ = 0; height_ = 0;
@ -101,56 +104,25 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true; use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls_) { if (use_gls_) {
initGtkLayerShell(); initGtkLayerShell();
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMapGLS));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigureGLS));
} }
#endif #endif
window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize)); if (!use_gls_) {
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure)); window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure));
}
window.set_size_request(width_, height_); window.set_size_request(width_, height_);
setupWidgets(); setupWidgets();
if (window.get_realized()) { if (!use_gls_ && window.get_realized()) {
onRealize(); onRealize();
} }
window.show_all(); window.show_all();
} }
void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ > 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (config["height"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ > 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (config["width"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (use_gls_) {
width_ = tmp_width;
height_ = tmp_height;
spdlog::debug("Set surface size {}x{} for output {}", width_, height_, output->name);
setExclusiveZone(tmp_width, tmp_height);
} else if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
}
}
#ifdef HAVE_GTK_LAYER_SHELL #ifdef HAVE_GTK_LAYER_SHELL
void waybar::Bar::initGtkLayerShell() { void waybar::Bar::initGtkLayerShell() {
auto gtk_window = window.gobj(); auto gtk_window = window.gobj();
@ -181,8 +153,80 @@ void waybar::Bar::initGtkLayerShell() {
setExclusiveZone(width_, height_); setExclusiveZone(width_, height_);
} }
} }
void waybar::Bar::onConfigureGLS(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
* Actual resizing is done within the gtk-layer-shell code; the only remaining action is to apply
* exclusive zone.
* gtk_layer_auto_exclusive_zone_enable() could handle even that, but at the cost of ignoring
* margins on unanchored edge.
*
* Note: forced resizing to a window smaller than required by GTK would not work with
* gtk-layer-shell.
*/
if (vertical) {
if (width_ > 1 && ev->width > static_cast<int>(width_)) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
} else {
if (!vertical && height_ > 1 && ev->height > static_cast<int>(height_)) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
}
width_ = ev->width;
height_ = ev->height;
spdlog::info(BAR_SIZE_MSG, width_, height_, output->name);
setExclusiveZone(width_, height_);
}
void waybar::Bar::onMapGLS(GdkEventAny* ev) {
/*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
*/
auto gdk_window = window.get_window();
surface = gdk_wayland_window_get_wl_surface(gdk_window->gobj());
}
#endif #endif
void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
*
* Prefer configured size if it's non-default.
* If the size is not set and the window is smaller than requested by GTK, request resize from
* layer surface.
*/
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ > 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (config["height"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ > 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (config["width"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
}
}
void waybar::Bar::onRealize() { void waybar::Bar::onRealize() {
auto gdk_window = window.get_window()->gobj(); auto gdk_window = window.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window); gdk_wayland_window_set_use_custom_surface(gdk_window);
@ -192,10 +236,6 @@ void waybar::Bar::onMap(GdkEventAny* ev) {
auto gdk_window = window.get_window()->gobj(); auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window); surface = gdk_wayland_window_get_wl_surface(gdk_window);
if (use_gls_) {
return;
}
auto client = waybar::Client::inst(); auto client = waybar::Client::inst();
// owned by output->monitor; no need to destroy // owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj()); auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj());
@ -362,7 +402,9 @@ auto waybar::Bar::toggle() -> void {
window.set_opacity(1); window.set_opacity(1);
} }
setExclusiveZone(width_, height_); setExclusiveZone(width_, height_);
wl_surface_commit(surface); if (!use_gls_) {
wl_surface_commit(surface);
}
} }
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {

View File

@ -162,6 +162,7 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
"$XDG_CONFIG_HOME/waybar/config", "$XDG_CONFIG_HOME/waybar/config",
"$HOME/.config/waybar/config", "$HOME/.config/waybar/config",
"$HOME/waybar/config", "$HOME/waybar/config",
"/etc/xdg/waybar/config",
SYSCONFDIR "/xdg/waybar/config", SYSCONFDIR "/xdg/waybar/config",
"./resources/config", "./resources/config",
}) })
@ -170,6 +171,7 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
"$XDG_CONFIG_HOME/waybar/style.css", "$XDG_CONFIG_HOME/waybar/style.css",
"$HOME/.config/waybar/style.css", "$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css", "$HOME/waybar/style.css",
"/etc/xdg/waybar/style.css",
SYSCONFDIR "/xdg/waybar/style.css", SYSCONFDIR "/xdg/waybar/style.css",
"./resources/style.css", "./resources/style.css",
}) })

View File

@ -76,14 +76,21 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "mpd") { if (ref == "mpd") {
return new waybar::modules::MPD(id, config_[name]); return new waybar::modules::MPD(id, config_[name]);
} }
#endif
#ifdef HAVE_LIBSNDIO
if (ref == "sndio") {
return new waybar::modules::Sndio(id, config_[name]);
}
#endif #endif
if (ref == "temperature") { if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]); return new waybar::modules::Temperature(id, config_[name]);
} }
#if defined(__linux__) #if defined(__linux__)
# ifdef WANT_RFKILL
if (ref == "bluetooth") { if (ref == "bluetooth") {
return new waybar::modules::Bluetooth(id, config_[name]); return new waybar::modules::Bluetooth(id, config_[name]);
} }
# endif
#endif #endif
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]); return new waybar::modules::Custom(ref.substr(7), id, config_[name]);

View File

@ -1,7 +1,72 @@
#include <csignal> #include <csignal>
#include <list>
#include <mutex>
#include <sys/types.h>
#include <sys/wait.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include "client.hpp" #include "client.hpp"
std::mutex reap_mtx;
std::list<pid_t> reap;
void* signalThread(void* args) {
int err, signum;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
while (true) {
err = sigwait(&mask, &signum);
if (err != 0) {
spdlog::error("sigwait failed: {}", strerror(errno));
continue;
}
switch (signum) {
case SIGCHLD:
spdlog::debug("Received SIGCHLD in signalThread");
if (!reap.empty()) {
reap_mtx.lock();
for (auto it = reap.begin(); it != reap.end(); ++it) {
if (waitpid(*it, nullptr, WNOHANG) == *it) {
spdlog::debug("Reaped child with PID: {}", *it);
it = reap.erase(it);
}
}
reap_mtx.unlock();
}
break;
default:
spdlog::debug("Received signal with number {}, but not handling",
signum);
break;
}
}
}
void startSignalThread(void) {
int err;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
// Block SIGCHLD so it can be handled by the signal thread
// Any threads created by this one (the main thread) should not
// modify their signal mask to unblock SIGCHLD
err = pthread_sigmask(SIG_BLOCK, &mask, nullptr);
if (err != 0) {
spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err));
exit(1);
}
pthread_t thread_id;
err = pthread_create(&thread_id, nullptr, signalThread, nullptr);
if (err != 0) {
spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err));
exit(1);
}
}
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
try { try {
auto client = waybar::Client::inst(); auto client = waybar::Client::inst();
@ -18,6 +83,7 @@ int main(int argc, char* argv[]) {
} }
}); });
} }
startSignalThread();
auto ret = client->main(argc, argv); auto ret = client->main(argc, argv);
delete client; delete client;

View File

@ -13,7 +13,7 @@
using waybar::modules::waybar_time; using waybar::modules::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60), fixed_time_zone_(false) { : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
if (config_["timezone"].isString()) { if (config_["timezone"].isString()) {
spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018."); spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018.");
time_zone_ = date::locate_zone(config_["timezone"].asString()); time_zone_ = date::locate_zone(config_["timezone"].asString());
@ -71,6 +71,40 @@ auto waybar::modules::Clock::update() -> void {
ALabel::update(); ALabel::update();
} }
bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
// defer to user commands if set
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
auto dir = AModule::getScrollDir(e);
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
return true;
}
auto nr_zones = config_["timezones"].size();
int new_idx = time_zone_idx_ + ((dir == SCROLL_DIR::UP) ? 1 : -1);
if (new_idx < 0) {
time_zone_idx_ = nr_zones - 1;
} else if (new_idx >= nr_zones) {
time_zone_idx_ = 0;
} else {
time_zone_idx_ = new_idx;
}
auto zone_name = config_["timezones"][time_zone_idx_];
if (!zone_name.isString() || zone_name.empty()) {
fixed_time_zone_ = false;
} else {
time_zone_ = date::locate_zone(zone_name.asString());
fixed_time_zone_ = true;
}
update();
return true;
}
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string {
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time()); const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time());
const auto ymd = date::year_month_day(daypoint); const auto ymd = date::year_month_day(daypoint);
@ -99,7 +133,12 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
os << '\n'; os << '\n';
} }
if (d == curr_day) { if (d == curr_day) {
os << "<b><u>" << date::format("%e", d) << "</u></b>"; if (config_["today-format"].isString()) {
auto today_format = config_["today-format"].asString();
os << fmt::format(today_format, date::format("%e", d));
} else {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
}
} else { } else {
os << date::format("%e", d); os << date::format("%e", d);
} }

View File

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

View File

@ -47,13 +47,14 @@ auto waybar::modules::Disk::update() -> void {
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true); auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true);
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks;
label_.set_markup(fmt::format(format_ label_.set_markup(fmt::format(format_
, stats.f_bavail * 100 / stats.f_blocks , stats.f_bavail * 100 / stats.f_blocks
, fmt::arg("free", free) , fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used) , fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks) , fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total) , fmt::arg("total", total)
, fmt::arg("path", path_) , fmt::arg("path", path_)
)); ));
@ -67,12 +68,13 @@ auto waybar::modules::Disk::update() -> void {
, fmt::arg("free", free) , fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used) , fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks) , fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total) , fmt::arg("total", total)
, fmt::arg("path", path_) , fmt::arg("path", path_)
)); ));
} }
event_box_.show(); event_box_.show();
getState(percentage_used);
// Call parent update // Call parent update
ALabel::update(); ALabel::update();
} }

View File

@ -36,7 +36,17 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("used", used_ram_gigabytes), fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes))); fmt::arg("avail", available_ram_gigabytes)));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1)); if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(tooltip_format,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
} else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
} }
event_box_.show(); event_box_.show();
} else { } else {

View File

@ -63,20 +63,22 @@ auto waybar::modules::MPD::update() -> void {
std::thread waybar::modules::MPD::event_listener() { std::thread waybar::modules::MPD::event_listener() {
return std::thread([this] { return std::thread([this] {
try { while (true) {
if (connection_ == nullptr) { try {
// Retry periodically if no connection if (connection_ == nullptr) {
dp.emit(); // Retry periodically if no connection
std::this_thread::sleep_for(interval_); dp.emit();
} else { std::this_thread::sleep_for(interval_);
waitForEvent(); } else {
dp.emit(); waitForEvent();
} dp.emit();
} catch (const std::exception& e) { }
if (strcmp(e.what(), "Connection to MPD closed") == 0) { } catch (const std::exception& e) {
spdlog::debug("{}: {}", module_name_, e.what()); if (strcmp(e.what(), "Connection to MPD closed") == 0) {
} else { spdlog::debug("{}: {}", module_name_, e.what());
spdlog::warn("{}: {}", module_name_, e.what()); } else {
spdlog::warn("{}: {}", module_name_, e.what());
}
} }
} }
}); });
@ -131,6 +133,7 @@ void waybar::modules::MPD::setLabel() {
auto format = format_; auto format = format_;
std::string artist, album_artist, album, title, date; std::string artist, album_artist, album, title, date;
int song_pos, queue_length;
std::chrono::seconds elapsedTime, totalTime; std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = ""; std::string stateIcon = "";
@ -159,6 +162,8 @@ void waybar::modules::MPD::setLabel() {
album = getTag(MPD_TAG_ALBUM); album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE); title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE); date = getTag(MPD_TAG_DATE);
song_pos = mpd_status_get_song_pos(status_.get());
queue_length = mpd_status_get_queue_length(status_.get());
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));
} }
@ -182,6 +187,8 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon), fmt::arg("randomIcon", randomIcon),
@ -198,6 +205,8 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("album", album), fmt::arg("album", album),
fmt::arg("title", title), fmt::arg("title", title),
fmt::arg("date", date), fmt::arg("date", date),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon), fmt::arg("randomIcon", randomIcon),

View File

@ -4,7 +4,9 @@
#include <fstream> #include <fstream>
#include <cassert> #include <cassert>
#include "util/format.hpp" #include "util/format.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp" #include "util/rfkill.hpp"
#endif
namespace { namespace {
@ -81,11 +83,15 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
: ALabel(config, "network", id, "{ifname}", 60), : ALabel(config, "network", id, "{ifname}", 60),
ifid_(-1), ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
ev_fd_(-1),
cidr_(-1), cidr_(-1),
signal_strength_dbm_(0), signal_strength_dbm_(0),
signal_strength_(0), signal_strength_(0),
frequency_(0), #ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_WLAN} { rfkill_{RFKILL_TYPE_WLAN},
#endif
frequency_(0) {
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
if (down_octets) { if (down_octets) {
@ -115,6 +121,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
} }
waybar::modules::Network::~Network() { waybar::modules::Network::~Network() {
if (ev_fd_ > -1) {
close(ev_fd_);
}
if (efd_ > -1) {
close(efd_);
}
if (ev_sock_ != nullptr) { if (ev_sock_ != nullptr) {
nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK); nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK);
if (family_ == AF_INET) { if (family_ == AF_INET) {
@ -146,6 +158,30 @@ void waybar::modules::Network::createEventSocket() {
} else { } else {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
} }
efd_ = epoll_create1(EPOLL_CLOEXEC);
if (efd_ < 0) {
throw std::runtime_error("Can't create epoll");
}
{
ev_fd_ = eventfd(0, EFD_NONBLOCK);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET;
event.data.fd = ev_fd_;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
{
auto fd = nl_socket_get_fd(ev_sock_);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
event.data.fd = fd;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
} }
void waybar::modules::Network::createInfoSocket() { void waybar::modules::Network::createInfoSocket() {
@ -174,6 +210,7 @@ void waybar::modules::Network::worker() {
} }
thread_timer_.sleep_for(interval_); thread_timer_.sleep_for(interval_);
}; };
#ifdef WANT_RFKILL
thread_rfkill_ = [this] { thread_rfkill_ = [this] {
rfkill_.waitForEvent(); rfkill_.waitForEvent();
{ {
@ -184,12 +221,30 @@ void waybar::modules::Network::worker() {
} }
} }
}; };
#else
spdlog::warn("Waybar has been built without rfkill support.");
#endif
thread_ = [this] {
std::array<struct epoll_event, EPOLL_MAX> events{};
int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1);
if (ec > 0) {
for (auto i = 0; i < ec; i++) {
if (events[i].data.fd != nl_socket_get_fd(ev_sock_) || nl_recvmsgs_default(ev_sock_) < 0) {
thread_.stop();
break;
}
}
}
};
} }
const std::string waybar::modules::Network::getNetworkState() const { const std::string waybar::modules::Network::getNetworkState() const {
if (ifid_ == -1) { if (ifid_ == -1) {
#ifdef WANT_RFKILL
if (rfkill_.getState()) if (rfkill_.getState())
return "disabled"; return "disabled";
#endif
return "disconnected"; return "disconnected";
} }
if (ipaddr_.empty()) return "linked"; if (ipaddr_.empty()) return "linked";
@ -277,7 +332,7 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s"))); fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")));
if (label_.get_tooltip_text() != text) { if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_text(tooltip_text); label_.set_tooltip_text(tooltip_text);
} }
} else if (label_.get_tooltip_text() != text) { } else if (label_.get_tooltip_text() != text) {

View File

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

View File

@ -248,23 +248,26 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString()); auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString());
auto &&button = pair.first->second; auto &&button = pair.first->second;
box_.pack_start(button, false, false, 0); box_.pack_start(button, false, false, 0);
button.set_name("sway-workspace-" + node["name"].asString());
button.set_relief(Gtk::RELIEF_NONE); button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, node] { if (!config_["disable-click"].asBool()) {
try { button.signal_pressed().connect([this, node] {
if (node["target_output"].isString()) { try {
ipc_.sendCmd( if (node["target_output"].isString()) {
IPC_COMMAND, ipc_.sendCmd(
fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + workspace_switch_cmd_, IPC_COMMAND,
node["name"].asString(), fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + workspace_switch_cmd_,
node["target_output"].asString(), node["name"].asString(),
node["name"].asString())); node["target_output"].asString(),
} else { node["name"].asString()));
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, node["name"].asString())); } else {
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, node["name"].asString()));
}
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
} }
} catch (const std::exception &e) { });
spdlog::error("Workspaces: {}", e.what()); }
}
});
return button; return button;
} }
@ -280,6 +283,8 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node
return config_["format-icons"]["persistent"].asString(); return config_["format-icons"]["persistent"].asString();
} else if (config_["format-icons"][key].isString()) { } else if (config_["format-icons"][key].isString()) {
return config_["format-icons"][key].asString(); return config_["format-icons"][key].asString();
} else if (config_["format-icons"][trimWorkspaceName(key)].isString()) {
return config_["format-icons"][trimWorkspaceName(key)].asString();
} }
} }
return name; return name;

View File

@ -460,38 +460,51 @@ bool Task::operator!=(const Task &o) const
void Task::update() void Task::update()
{ {
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
std::string title = title_;
std::string app_id = app_id_;
if (markup) {
title = Glib::Markup::escape_text(title);
app_id = Glib::Markup::escape_text(app_id);
}
if (!format_before_.empty()) { if (!format_before_.empty()) {
text_before_.set_label( auto txt = fmt::format(format_before_,
fmt::format(format_before_, fmt::arg("title", title),
fmt::arg("title", title_), fmt::arg("app_id", app_id),
fmt::arg("app_id", app_id_),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
) );
); if (markup)
text_before_.set_markup(txt);
else
text_before_.set_label(txt);
text_before_.show(); text_before_.show();
} }
if (!format_after_.empty()) { if (!format_after_.empty()) {
text_after_.set_label( auto txt = fmt::format(format_after_,
fmt::format(format_after_, fmt::arg("title", title),
fmt::arg("title", title_), fmt::arg("app_id", app_id),
fmt::arg("app_id", app_id_),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
) );
); if (markup)
text_after_.set_markup(txt);
else
text_after_.set_label(txt);
text_after_.show(); text_after_.show();
} }
if (!format_tooltip_.empty()) { if (!format_tooltip_.empty()) {
button_.set_tooltip_markup( auto txt = fmt::format(format_tooltip_,
fmt::format(format_tooltip_, fmt::arg("title", title),
fmt::arg("title", title_), fmt::arg("app_id", app_id),
fmt::arg("app_id", app_id_),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
) );
); if (markup)
button_.set_tooltip_markup(txt);
else
button_.set_tooltip_text(txt);
} }
} }
@ -709,7 +722,7 @@ bool Taskbar::show_output(struct wl_output *output) const
bool Taskbar::all_outputs() const bool Taskbar::all_outputs() const
{ {
return config_["all_outputs"].isBool() && config_["all_outputs"].asBool(); return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
} }
std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const

View File

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

View File

@ -1,9 +1,9 @@
[wrap-file] [wrap-file]
source_url=https://github.com/HowardHinnant/date/archive/v2.4.1.tar.gz source_url=https://github.com/HowardHinnant/date/archive/v3.0.0.tar.gz
source_filename=date-2.4.1.tar.gz source_filename=date-3.0.0.tar.gz
source_hash=98907d243397483bd7ad889bf6c66746db0d7d2a39cc9aacc041834c40b65b98 source_hash=87bba2eaf0ebc7ec539e5e62fc317cb80671a337c1fb1b84cb9e4d42c6dbebe3
directory=date-2.4.1 directory=date-3.0.0
patch_url = https://github.com/mesonbuild/hinnant-date/releases/download/2.4.1-1/hinnant-date.zip patch_url = https://github.com/mesonbuild/hinnant-date/releases/download/3.0.0-1/hinnant-date.zip
patch_filename = hinnant-date-2.4.1-1-wrap.zip patch_filename = hinnant-date-3.0.0-1-wrap.zip
patch_hash = 2061673a6f8e6d63c3a40df4da58fa2b3de2835fd9b3e74649e8279599f3a8f6 patch_hash = 6ccaf70732d8bdbd1b6d5fdf3e1b935c23bf269bda12fdfd0e561276f63432fe

View File

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