Compare commits

...

33 Commits
0.5.0 ... 0.5.1

Author SHA1 Message Date
24684ca71b chore: v0.5.1 2019-04-04 12:01:00 +02:00
8351dea292 refactor(network): process all messages 2019-04-04 11:58:27 +02:00
a68011bce6 style(workspaces): urgent style 2019-04-02 09:31:40 +02:00
1f6f443c48 Revert "refactor(network): fix skipped messages"
This reverts commit 1ccf372f8e.
2019-04-01 11:41:43 +02:00
7fac483530 fix: don't bind RTMGRP_IPV(4|6)_ROUTE 2019-03-31 16:33:01 +02:00
0ad9a03d3d Merge pull request #256 from SibrenVasse/spotify-json
Convert spotify module to generic json version
2019-03-31 16:30:16 +02:00
0f689b733d feat(custom): make spotify module generic
Set class via json attribute
Choose icon via alt attribute
2019-03-31 16:20:43 +02:00
618fe80670 chore(custom): fix typo and quoting style 2019-03-31 16:10:42 +02:00
3a8cd91cc0 Revert "refactor: partial revert of 1ccf372f8e9d74cb18e92220c18a0729832fe69e"
This reverts commit 949a4ecf2e.
2019-03-30 09:20:28 +01:00
30725824d0 Merge pull request #251 from DanielVoogsgerd/feature-select-player
Feature select player
2019-03-30 09:19:24 +01:00
6522a7acb4 chore: let compile sway modules even if we can't find the binary 2019-03-30 09:15:51 +01:00
fd3e4ab2d4 Merge branch 'master' into feature-select-player 2019-03-30 09:07:31 +01:00
949a4ecf2e refactor: partial revert of 1ccf372f8e 2019-03-30 09:03:31 +01:00
860a6a1b81 Merge pull request #252 from alebastr/tray-items-refresh
Process tray icon updates
2019-03-30 08:40:55 +01:00
5a2b5624dc feat(tray): process tray icon update signals 2019-03-29 18:40:28 -07:00
82fcee33b3 refactor(tray): use Gio::DBus bindings in SNI Item class 2019-03-29 18:28:29 -07:00
842e8501f9 fix(tray): free icon data on pixbuf update 2019-03-28 10:52:25 -07:00
7bd5454e43 Merge pull request #250 from DanielVoogsgerd/feature-arguments-logging
Feature: Command line arguments/Logging
2019-03-28 09:38:33 +01:00
8340e88795 Merge pull request #249 from DanielVoogsgerd/refactor-main_method
refactor: Add main method / Clean global scope
2019-03-28 09:38:07 +01:00
095294389b feat(Spotify): Add option to select player 2019-03-27 22:27:23 +01:00
3bcf3befec chore(Spotify): Add logging for events 2019-03-27 17:28:02 +01:00
db69b449ba feat(Spotify): Add logger 2019-03-27 16:37:59 +01:00
ed358ef024 feat(Spotify): Add argument parser 2019-03-27 16:33:15 +01:00
faa79ea216 refactor: Add main method / Clean global scope 2019-03-27 16:10:36 +01:00
b17984f75e Merge pull request #248 from ndrewtl/patch-1
Note availability in Arch community repository
2019-03-27 08:15:44 +01:00
4521db66ce Note availability in Arch community repository 2019-03-26 18:20:09 -07:00
7912b7f119 Merge pull request #245 from SibrenVasse/json-icon
[custom] Allow icon selection based on json alt attribute
2019-03-26 09:26:58 +01:00
55a6e4907b feat(custom): allow icon selection based on json alt attribute 2019-03-26 00:35:49 +01:00
dda0cc793e fix: check vertical after parsing multiple outputs 2019-03-25 21:02:00 +01:00
5144426f0e fix(workspace): scroll direction 2019-03-25 11:55:01 +01:00
98c028226d Merge pull request #241 from jlbribeiro/fix/essid-unescaped-input
Fix #240: Escape ESSID text before interpreting as pango
2019-03-24 10:28:00 +01:00
3eb901f800 Escape ESSID text before interpreting as pango
Fixes #240.
2019-03-24 03:11:54 +00:00
47142a61ae feat: allow waybar to be positioned on left/right 2019-03-22 12:25:05 +01:00
16 changed files with 397 additions and 232 deletions

View File

@ -2,7 +2,8 @@
**Proof of concept** **Proof of concept**
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
> Available on [AUR](https://aur.archlinux.org/packages/waybar-git/) and [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar) > Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
[AUR](https://aur.archlinux.org/packages/waybar-git/) and [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar)
**Current features** **Current features**
- Sway (Workspaces, Binding mode, Focused window name) - Sway (Workspaces, Binding mode, Focused window name)

View File

@ -32,6 +32,7 @@ class Bar {
std::string output_name; std::string output_name;
uint32_t wl_name; uint32_t wl_name;
bool visible = true; bool visible = true;
bool vertical = false;
private: private:
static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t, static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t,
int32_t); int32_t);

View File

@ -23,6 +23,7 @@ class Network : public ALabel {
static const uint8_t MAX_RETRY = 5; static const uint8_t MAX_RETRY = 5;
static const uint8_t EPOLL_MAX = 255; static const uint8_t EPOLL_MAX = 255;
static int handleEvents(struct nl_msg*, void*);
static int handleScan(struct nl_msg*, void*); static int handleScan(struct nl_msg*, void*);
void worker(); void worker();
@ -31,7 +32,6 @@ class Network : public ALabel {
void createEventSocket(); void createEventSocket();
int getExternalInterface(); int getExternalInterface();
void getInterfaceAddress(); void getInterfaceAddress();
void handleEvents();
int netlinkRequest(void*, uint32_t, uint32_t groups = 0); int netlinkRequest(void*, uint32_t, uint32_t groups = 0);
int netlinkResponse(void*, uint32_t, uint32_t groups = 0); int netlinkResponse(void*, uint32_t, uint32_t groups = 0);
void parseEssid(struct nlattr**); void parseEssid(struct nlattr**);
@ -45,7 +45,7 @@ class Network : public ALabel {
sa_family_t family_; sa_family_t family_;
struct sockaddr_nl nladdr_ = {0}; struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sk_ = nullptr; struct nl_sock* sk_ = nullptr;
int info_sock_; struct nl_sock* info_sock_ = nullptr;
int efd_; int efd_;
int ev_fd_; int ev_fd_;
int nl80211_id_; int nl80211_id_;

View File

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

View File

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

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.5.0', version: '0.5.1',
license: 'MIT', license: 'MIT',
default_options : [ default_options : [
'cpp_std=c++17', 'cpp_std=c++17',
@ -72,7 +72,7 @@ src_files = files(
'src/client.cpp' 'src/client.cpp'
) )
if find_program('sway', required : false).found() if true # find_program('sway', required : false).found()
add_project_arguments('-DHAVE_SWAY', language: 'cpp') add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [ src_files += [
'src/modules/sway/ipc/client.cpp', 'src/modules/sway/ipc/client.cpp',

View File

@ -1,10 +1,10 @@
{ {
"layer": "top", // Waybar at top layer "layer": "top", // Waybar at top layer
// "position": "bottom", // Waybar at the bottom of your screen // "position": "bottom", // Waybar position (top|bottom|left|right)
// "height": 30, // Waybar height // "height": 30, // Waybar height
// "width": 1280, // Waybar width // "width": 1280, // Waybar width
// Choose the order of the modules // Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"], "modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"], "modules-center": ["sway/window"],
"modules-right": ["idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"], "modules-right": ["idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration // Modules configuration
@ -95,9 +95,15 @@
}, },
"on-click": "pavucontrol" "on-click": "pavucontrol"
}, },
"custom/spotify": { "custom/media": {
"format": " {}", "format": "{icon} {}",
"return-type": "json",
"max-length": 40, "max-length": 40,
"format-icons": {
"spotify": "",
"default": "🎜"
},
"escape": true,
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
} }
} }

View File

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

View File

@ -25,12 +25,16 @@ window#waybar {
border-bottom: 3px solid #ffffff; border-bottom: 3px solid #ffffff;
} }
#workspaces button.urgent {
background-color: #eb4d4b;
}
#mode { #mode {
background: #64727D; background: #64727D;
border-bottom: 3px solid #ffffff; border-bottom: 3px solid #ffffff;
} }
#clock, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #custom-spotify, #tray, #mode, #idle_inhibitor { #clock, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #custom-media, #tray, #mode, #idle_inhibitor {
padding: 0 10px; padding: 0 10px;
margin: 0 5px; margin: 0 5px;
} }
@ -97,11 +101,19 @@ window#waybar {
color: #2a5c45; color: #2a5c45;
} }
#custom-spotify { #custom-media {
background: #66cc99; background: #66cc99;
color: #2a5c45; color: #2a5c45;
} }
.custom-spotify {
background: #66cc99;
}
.custom-vlc {
background: #ffa000;
}
#temperature { #temperature {
background: #f0932b; background: #f0932b;
} }

View File

@ -84,10 +84,10 @@ void waybar::Bar::initBar()
setupAltFormatKeyForModuleList("modules-left"); setupAltFormatKeyForModuleList("modules-left");
setupAltFormatKeyForModuleList("modules-right"); setupAltFormatKeyForModuleList("modules-right");
setupAltFormatKeyForModuleList("modules-center"); setupAltFormatKeyForModuleList("modules-center");
std::size_t layer_top = config_["layer"] == "top" std::size_t layer = config_["layer"] == "top"
? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
layer_surface = zwlr_layer_shell_v1_get_layer_surface( layer_surface = zwlr_layer_shell_v1_get_layer_surface(
client.layer_shell, surface, *output, layer_top, "waybar"); client.layer_shell, surface, *output, layer, "waybar");
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure, .configure = layerSurfaceHandleConfigure,
@ -95,18 +95,35 @@ void waybar::Bar::initBar()
}; };
zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this); zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this);
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT if (config_["position"] == "right" || config_["position"] == "left") {
| ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; height_ = 0;
if (config_["position"] == "bottom") { width_ = 30;
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
} }
auto height = config_["height"].isUInt() ? config_["height"].asUInt() : height_; auto height = config_["height"].isUInt() ? config_["height"].asUInt() : height_;
auto width = config_["width"].isUInt() ? config_["width"].asUInt() : width_; auto width = config_["width"].isUInt() ? config_["width"].asUInt() : width_;
std::size_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (config_["position"] == "bottom") {
anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (config_["position"] == "left") {
anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (config_["position"] == "right") {
anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
if (anchor == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM || anchor == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else if (anchor == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT || anchor == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
vertical = true;
}
zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); zwlr_layer_surface_v1_set_anchor(layer_surface, anchor);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, height); zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, vertical ? width : height);
zwlr_layer_surface_v1_set_size(layer_surface, width, height); zwlr_layer_surface_v1_set_size(layer_surface, width, height);
wl_surface_commit(surface); wl_surface_commit(surface);
@ -213,13 +230,18 @@ void waybar::Bar::layerSurfaceHandleConfigure(void* data,
o->window.set_size_request(o->width_, o->height_); o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_); o->window.resize(o->width_, o->height_);
int dummy_width, min_height; int min_width, min_height;
o->window.get_size(dummy_width, min_height); o->window.get_size(min_width, min_height);
if (o->height_ < static_cast<uint32_t>(min_height)) { if (o->height_ < static_cast<uint32_t>(min_height)) {
std::cout << fmt::format("Requested height: {} exceeds the minimum \ std::cout << fmt::format("Requested height: {} exceeds the minimum \
height: {} required by the modules", o->height_, min_height) << std::endl; height: {} required by the modules", o->height_, min_height) << std::endl;
o->height_ = min_height; o->height_ = min_height;
} }
if (o->width_ < static_cast<uint32_t>(min_width)) {
std::cout << fmt::format("Requested width: {} exceeds the minimum \
width: {} required by the modules", o->height_, min_width) << std::endl;
o->width_ = min_width;
}
std::cout << fmt::format( std::cout << fmt::format(
"Bar configured (width: {}, height: {}) for output: {}", "Bar configured (width: {}, height: {}) for output: {}",
o->width_, o->height_, o->output_name) << std::endl; o->width_, o->height_, o->output_name) << std::endl;

View File

@ -38,7 +38,7 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const
} }
#ifdef HAVE_DBUSMENU #ifdef HAVE_DBUSMENU
if (ref == "tray") { if (ref == "tray") {
return new waybar::modules::SNI::Tray(id, config_[name]); return new waybar::modules::SNI::Tray(id, bar_, config_[name]);
} }
#endif #endif
#ifdef HAVE_LIBNL #ifdef HAVE_LIBNL

View File

@ -98,7 +98,7 @@ auto waybar::modules::Custom::update() -> void
auto str = fmt::format(format_, text_, auto str = fmt::format(format_, text_,
fmt::arg("alt", alt_), fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_)), fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_)); fmt::arg("percentage", percentage_));
label_.set_markup(str); label_.set_markup(str);
if (tooltipEnabled()) { if (tooltipEnabled()) {

View File

@ -2,8 +2,8 @@
#include "modules/network.hpp" #include "modules/network.hpp"
waybar::modules::Network::Network(const std::string& id, const Json::Value& config) waybar::modules::Network::Network(const std::string& id, const Json::Value& config)
: ALabel(config, "{ifname}", 60), family_(AF_INET), info_sock_(-1), efd_(-1), : ALabel(config, "{ifname}", 60), family_(AF_INET), efd_(-1), ev_fd_(-1),
ev_fd_(-1), cidr_(-1), signal_strength_dbm_(0), signal_strength_(0) cidr_(-1), signal_strength_dbm_(0), signal_strength_(0)
{ {
label_.set_name("network"); label_.set_name("network");
if (!id.empty()) { if (!id.empty()) {
@ -39,8 +39,11 @@ waybar::modules::Network::~Network()
if (efd_ > -1) { if (efd_ > -1) {
close(efd_); close(efd_);
} }
if (info_sock_ != -1) { if (info_sock_ != nullptr) {
close(info_sock_); nl_socket_drop_membership(info_sock_, RTMGRP_LINK);
nl_socket_drop_membership(info_sock_, RTMGRP_IPV4_IFADDR);
nl_close(info_sock_);
nl_socket_free(info_sock_);
} }
if (sk_ != nullptr) { if (sk_ != nullptr) {
nl_close(sk_); nl_close(sk_);
@ -50,18 +53,19 @@ waybar::modules::Network::~Network()
void waybar::modules::Network::createInfoSocket() void waybar::modules::Network::createInfoSocket()
{ {
struct sockaddr_nl sa; info_sock_ = nl_socket_alloc();
info_sock_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (nl_connect(info_sock_, NETLINK_ROUTE) != 0) {
if (info_sock_ < 0) {
throw std::runtime_error("Can't connect network socket"); throw std::runtime_error("Can't connect network socket");
} }
sa.nl_family = AF_NETLINK; if (nl_socket_add_membership(info_sock_, RTMGRP_LINK) != 0) {
sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE
| RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
auto ret = bind(info_sock_, (struct sockaddr *)&sa, sizeof(sa));
if (ret < 0) {
throw std::runtime_error("Can't add membership"); throw std::runtime_error("Can't add membership");
} }
if (nl_socket_add_membership(info_sock_, RTMGRP_IPV4_IFADDR) != 0) {
throw std::runtime_error("Can't add membership");
}
nl_socket_disable_seq_check(info_sock_);
nl_socket_set_nonblocking(info_sock_);
nl_socket_modify_cb(info_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
efd_ = epoll_create1(0); efd_ = epoll_create1(0);
if (efd_ < 0) { if (efd_ < 0) {
throw std::runtime_error("Can't create epoll"); throw std::runtime_error("Can't create epoll");
@ -76,10 +80,11 @@ void waybar::modules::Network::createInfoSocket()
} }
} }
{ {
auto fd = nl_socket_get_fd(info_sock_);
struct epoll_event event; struct epoll_event event;
event.events = EPOLLIN | EPOLLET; event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
event.data.fd = info_sock_; event.data.fd = fd;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, info_sock_, &event) == -1) { if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
throw std::runtime_error("Can't add epoll event"); throw std::runtime_error("Can't add epoll event");
} }
} }
@ -109,15 +114,16 @@ void waybar::modules::Network::worker()
} }
thread_timer_.sleep_for(interval_); thread_timer_.sleep_for(interval_);
}; };
struct epoll_event events[EPOLL_MAX] = {0}; struct epoll_event events[EPOLL_MAX];
thread_ = [this, &events] { thread_ = [this, &events] {
int ec = epoll_wait(efd_, events, EPOLL_MAX, -1); int ec = epoll_wait(efd_, events, EPOLL_MAX, -1);
if (ec > 0) { if (ec > 0) {
for (auto i = 0; i < ec; i++) { for (auto i = 0; i < ec; i++) {
if (events[i].data.fd == ev_fd_) { if (events[i].data.fd == nl_socket_get_fd(info_sock_)) {
nl_recvmsgs_default(info_sock_);
} else {
thread_.stop(); thread_.stop();
} else if (events[i].events & EPOLLIN) { break;
handleEvents();
} }
} }
} else if (ec == -1) { } else if (ec == -1) {
@ -371,7 +377,7 @@ int waybar::modules::Network::netlinkRequest(void *req,
sa.nl_groups = groups; sa.nl_groups = groups;
struct iovec iov = { req, reqlen }; struct iovec iov = { req, reqlen };
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 }; struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 };
return sendmsg(info_sock_, &msg, 0); return sendmsg(nl_socket_get_fd(info_sock_), &msg, 0);
} }
int waybar::modules::Network::netlinkResponse(void *resp, int waybar::modules::Network::netlinkResponse(void *resp,
@ -382,64 +388,58 @@ int waybar::modules::Network::netlinkResponse(void *resp,
sa.nl_groups = groups; sa.nl_groups = groups;
struct iovec iov = { resp, resplen }; struct iovec iov = { resp, resplen };
struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 }; struct msghdr msg = { &sa, sizeof(sa), &iov, 1, nullptr, 0, 0 };
auto ret = recvmsg(info_sock_, &msg, 0); auto ret = recvmsg(nl_socket_get_fd(info_sock_), &msg, 0);
if (msg.msg_flags & MSG_TRUNC) { if (msg.msg_flags & MSG_TRUNC) {
return -1; return -1;
} }
return ret; return ret;
} }
void waybar::modules::Network::handleEvents() { int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
struct sockaddr_nl addr; int ret = 0;
char buff[2048] = {0}; auto net = static_cast<waybar::modules::Network *>(data);
socklen_t len = 0; bool need_update = false;
for (nlmsghdr *nh = nlmsg_hdr(msg); NLMSG_OK(nh, ret);
while (true) { nh = NLMSG_NEXT(nh, ret)) {
len = sizeof(addr); if (nh->nlmsg_type == RTM_NEWADDR) {
auto ret = recvfrom(info_sock_, (void *)buff, sizeof(buff), 0, need_update = true;
(struct sockaddr *)&addr, &len); }
auto nh = (struct nlmsghdr *)buff; if (nh->nlmsg_type < RTM_NEWADDR) {
for(; NLMSG_OK(nh, ret); nh = NLMSG_NEXT(nh, ret)) { auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
bool need_update = false; if (rtif->ifi_index == static_cast<int>(net->ifid_)) {
if (nh->nlmsg_type == RTM_NEWADDR) {
need_update = true; need_update = true;
} if (!(rtif->ifi_flags & IFF_RUNNING)) {
if (nh->nlmsg_type < RTM_NEWADDR) { net->disconnected();
auto rtif = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh)); net->dp.emit();
if (rtif->ifi_index == static_cast<int>(ifid_)) { return NL_SKIP;
need_update = true;
if (!(rtif->ifi_flags & IFF_RUNNING)) {
disconnected();
dp.emit();
return;
}
} }
} }
if (ifid_ <= 0 && !config_["interface"].isString()) { }
for (uint8_t i = 0; i < MAX_RETRY; i += 1) { if (need_update) break;
ifid_ = getExternalInterface(); }
if (ifid_ > 0) { if (net->ifid_ <= 0 && !net->config_["interface"].isString()) {
break; for (uint8_t i = 0; i < MAX_RETRY; i += 1) {
} net->ifid_ = net->getExternalInterface();
// Need to wait before get external interface if (net->ifid_ > 0) {
thread_.sleep_for(std::chrono::seconds(1)); break;
}
if (ifid_ > 0) {
char ifname[IF_NAMESIZE];
if_indextoname(ifid_, ifname);
ifname_ = ifname;
need_update = true;
}
} }
if (need_update) { // Need to wait before get external interface
if (ifid_ > 0) { net->thread_.sleep_for(std::chrono::seconds(1));
getInfo(); }
} if (net->ifid_ > 0) {
dp.emit(); char ifname[IF_NAMESIZE];
} if_indextoname(net->ifid_, ifname);
break; net->ifname_ = ifname;
need_update = true;
} }
} }
if (need_update) {
if (net->ifid_ > 0) {
net->getInfo();
}
net->dp.emit();
}
return NL_SKIP;
} }
int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) { int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {
@ -491,7 +491,9 @@ void waybar::modules::Network::parseEssid(struct nlattr **bss)
if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) { if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
auto essid_begin = ies + hdr_len; auto essid_begin = ies + hdr_len;
auto essid_end = essid_begin + ies[1]; auto essid_end = essid_begin + ies[1];
std::copy(essid_begin, essid_end, std::back_inserter(essid_)); std::string essid_raw;
std::copy(essid_begin, essid_end, std::back_inserter(essid_raw));
essid_ = Glib::Markup::escape_text(essid_raw);
} }
} }
} }

View File

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

View File

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

View File

@ -2,7 +2,9 @@
waybar::modules::sway::Workspaces::Workspaces(const std::string& id, const Bar& bar, waybar::modules::sway::Workspaces::Workspaces(const std::string& id, const Bar& bar,
const Json::Value& config) const Json::Value& config)
: bar_(bar), config_(config), scrolling_(false) : bar_(bar), config_(config),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
scrolling_(false)
{ {
box_.set_name("workspaces"); box_.set_name("workspaces");
if (!id.empty()) { if (!id.empty()) {
@ -171,19 +173,19 @@ bool waybar::modules::sway::Workspaces::handleScroll(GdkEventScroll *e)
} }
std::string name; std::string name;
if (e->direction == GDK_SCROLL_UP) { if (e->direction == GDK_SCROLL_UP) {
name = getCycleWorkspace(idx, false); name = getCycleWorkspace(idx, true);
} }
if (e->direction == GDK_SCROLL_DOWN) { if (e->direction == GDK_SCROLL_DOWN) {
name = getCycleWorkspace(idx, true); name = getCycleWorkspace(idx, false);
} }
if (e->direction == GDK_SCROLL_SMOOTH) { if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y; gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e),
&delta_x, &delta_y); &delta_x, &delta_y);
if (delta_y < 0) { if (delta_y < 0) {
name = getCycleWorkspace(idx, false);
} else if (delta_y > 0) {
name = getCycleWorkspace(idx, true); name = getCycleWorkspace(idx, true);
} else if (delta_y > 0) {
name = getCycleWorkspace(idx, false);
} }
} }
if (name.empty() || name == workspaces_[idx]["name"].asString()) { if (name.empty() || name == workspaces_[idx]["name"].asString()) {