Merge branch 'master' into taskbar/remove-trim

This commit is contained in:
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
env:
before_install:
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu
- export CPPFLAGS+=-isystem/usr/local/include LDFLAGS+=-L/usr/local/lib # sndio
- sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio
libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog
script:
- meson build -Dman-pages=enabled

View File

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

View File

@ -3,4 +3,4 @@
FROM archlinux/base:latest
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
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

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>
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
**Current features**
#### Current features
- Sway (Workspaces, Binding mode, Focused window name)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time
@ -22,11 +22,21 @@
- Multiple output configuration
- And much more customizations
**Configuration and Styling**
#### Configuration and Styling
[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
$ git clone https://github.com/Alexays/Waybar
@ -57,6 +67,7 @@ libnl [Network module]
libappindicator-gtk3 [Tray module]
libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module]
libsndio [sndio module]
```
**Build dependencies**

View File

@ -10,7 +10,7 @@ namespace waybar {
class ALabel : public AModule {
public:
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 auto update() -> void;
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 *);
#ifdef HAVE_GTK_LAYER_SHELL
/* gtk-layer-shell code */
void initGtkLayerShell();
void onConfigureGLS(GdkEventConfigure *ev);
void onMapGLS(GdkEventAny *ev);
#endif
/* fallback layer-surface code */
void onConfigure(GdkEventConfigure *ev);
void onRealize();
void onMap(GdkEventAny *ev);
void setExclusiveZone(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;
void getModules(const Factory &, const std::string &);
void setupAltFormatKeyForModule(const std::string &module_name);

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -7,6 +7,9 @@
#include <array>
extern std::mutex reap_mtx;
extern std::list<pid_t> reap;
namespace waybar::util::command {
struct res {
@ -32,10 +35,11 @@ inline std::string read(FILE* fp) {
inline int close(FILE* fp, pid_t pid) {
int stat = -1;
pid_t ret;
fclose(fp);
do {
waitpid(pid, &stat, WCONTINUED | WUNTRACED);
ret = waitpid(pid, &stat, WCONTINUED | WUNTRACED);
if (WIFEXITED(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));
} else if (WIFCONTINUED(stat)) {
spdlog::debug("Cmd continued");
} else if (ret == -1) {
spdlog::debug("waitpid failed: {}", strerror(errno));
} else {
break;
}
@ -65,6 +71,12 @@ inline FILE* open(const std::string& cmd, int& 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]);
dup2(fd[1], 1);
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) {
if (cmd == "") return -1;
int32_t pid = fork();
pid_t pid = fork();
if (pid < 0) {
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
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);
signal(SIGCHLD, SIG_DFL);
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0);
} 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;

View File

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

View File

@ -26,11 +26,21 @@ The *clock* module displays the current date and time.
default: inferred local timezone ++
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*: ++
typeof: string ++
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.
*today-format*: ++
typeof: string ++
default: <b><u>{}</u></b> ++
The format of today's date in the calendar.
*max-length*: ++
typeof: integer ++
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.
*exec* will be executed if the exit code of *exec-if* equals 0.
*exec-on-event*: ++
typeof: bool ++
default: true ++
If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after
executing the event command.
*return-type*: ++
typeof: string ++
See *return-type*

View File

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

View File

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

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

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

View File

@ -31,6 +31,11 @@ Addressed by *sway/workspaces*
default: false ++
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*: ++
typeof: double ++
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.persistent*
- *#workspaces button.current_output*
- *#workspaces button#sway-workspace-${name}*

View File

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

View File

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

View File

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

View File

@ -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('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')

View File

@ -25,7 +25,7 @@
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="1">
<interface name="zwlr_layer_shell_v1" version="3">
<description summary="create surfaces that are layers of the desktop">
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
@ -82,17 +82,27 @@
<entry name="top" value="2"/>
<entry name="overlay" value="3"/>
</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 name="zwlr_layer_surface_v1" version="1">
<interface name="zwlr_layer_surface_v1" version="3">
<description summary="layer metadata interface">
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
environment.
Layer surface state (size, anchor, exclusive zone, margin, interactivity)
is double-buffered, and will be applied at the time wl_surface.commit of
the corresponding wl_surface is called.
Layer surface state (layer, size, anchor, exclusive zone,
margin, interactivity) is double-buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
</description>
<request name="set_size">
@ -115,7 +125,7 @@
<request name="set_anchor">
<description summary="configures the anchor point of the surface">
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
(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.
@ -127,20 +137,25 @@
<request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area of the surface
with other surfaces. The compositor's use of this information is
Requests that the compositor avoids occluding an area with other
surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to an
edge, rather than a corner. The zone is the number of surface-local
coordinates from the edge that are considered exclusive.
A positive value is only meaningful if the surface is anchored to one
edge or an edge and both perpendicular edges. If the surface is not
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
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
surfaces with a positive excluzive zone. If set to -1, the surface
indicates that it would not like to be moved to accomodate for other
surfaces with a positive exclusive zone. If set to -1, the surface
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
it is anchored to.
@ -281,5 +296,16 @@
<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"/>
</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>
</protocol>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,72 @@
#include <csignal>
#include <list>
#include <mutex>
#include <sys/types.h>
#include <sys/wait.h>
#include <spdlog/spdlog.h>
#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[]) {
try {
auto client = waybar::Client::inst();
@ -18,6 +83,7 @@ int main(int argc, char* argv[]) {
}
});
}
startSignalThread();
auto ret = client->main(argc, argv);
delete client;

View File

@ -13,7 +13,7 @@
using waybar::modules::waybar_time;
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()) {
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());
@ -71,6 +71,40 @@ auto waybar::modules::Clock::update() -> void {
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 {
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time());
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';
}
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 {
os << date::format("%e", d);
}

View File

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

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

View File

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

View File

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

View File

@ -4,7 +4,9 @@
#include <fstream>
#include <cassert>
#include "util/format.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
namespace {
@ -81,11 +83,15 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
: ALabel(config, "network", id, "{ifname}", 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
ev_fd_(-1),
cidr_(-1),
signal_strength_dbm_(0),
signal_strength_(0),
frequency_(0),
rfkill_{RFKILL_TYPE_WLAN} {
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_WLAN},
#endif
frequency_(0) {
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
if (down_octets) {
@ -115,6 +121,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
}
waybar::modules::Network::~Network() {
if (ev_fd_ > -1) {
close(ev_fd_);
}
if (efd_ > -1) {
close(efd_);
}
if (ev_sock_ != nullptr) {
nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK);
if (family_ == AF_INET) {
@ -146,6 +158,30 @@ void waybar::modules::Network::createEventSocket() {
} else {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
}
efd_ = epoll_create1(EPOLL_CLOEXEC);
if (efd_ < 0) {
throw std::runtime_error("Can't create epoll");
}
{
ev_fd_ = eventfd(0, EFD_NONBLOCK);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET;
event.data.fd = ev_fd_;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
{
auto fd = nl_socket_get_fd(ev_sock_);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
event.data.fd = fd;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
}
void waybar::modules::Network::createInfoSocket() {
@ -174,6 +210,7 @@ void waybar::modules::Network::worker() {
}
thread_timer_.sleep_for(interval_);
};
#ifdef WANT_RFKILL
thread_rfkill_ = [this] {
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 {
if (ifid_ == -1) {
#ifdef WANT_RFKILL
if (rfkill_.getState())
return "disabled";
#endif
return "disconnected";
}
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("bandwidthDownOctets", pow_format(bandwidth_down / 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);
}
} else if (label_.get_tooltip_text() != text) {

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

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

View File

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

View File

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

View File

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

View File

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

View File

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