Compare commits

..

11 Commits
0.0.2 ... 0.0.3

Author SHA1 Message Date
c3bd6da1d0 chore: v0.0.3 2018-08-15 14:50:19 +02:00
e3e099f836 feat(workspaces): icons 2018-08-15 14:48:08 +02:00
767d9dd5b4 fix(workspaces): buttons iterator 2018-08-15 14:30:01 +02:00
d1d51b76aa fix(client): try to fix #20 2018-08-15 01:53:43 +02:00
cee031d2fa Workspaces scroll event (#19) 2018-08-14 11:26:06 +02:00
18c7ad0026 fix(workspaces): lock mutex inside click callback 2018-08-13 23:43:35 +02:00
1555cb71e1 feat(pulseaudio): volume icons 2018-08-13 22:33:07 +02:00
ea9a08d473 refactor(workspaces): listen ipc event 2018-08-13 21:23:43 +02:00
68f9ea3065 fix(battery): add check for sys files 2018-08-13 17:11:47 +02:00
a423f7032d Battery event (#18) 2018-08-13 14:05:13 +02:00
01894f18cd chore: clean headers 2018-08-12 20:25:19 +02:00
29 changed files with 413 additions and 258 deletions

View File

@ -1,5 +1,7 @@
#pragma once
#include <gtkmm.h>
namespace waybar {
class IModule {
public:

View File

@ -3,16 +3,11 @@
#include <unistd.h>
#include <wordexp.h>
#include <iostream>
#include <fmt/format.h>
#include <gdk/gdk.h>
#include <gtkmm.h>
#include <wayland-client.h>
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include <gdk/gdkwayland.h>
#include "bar.hpp"
@ -26,11 +21,12 @@ namespace waybar {
Gtk::Main gtk_main;
Glib::RefPtr<Gdk::Display> gdk_display;
struct wl_display *wlDisplay;
struct wl_registry *registry;
struct zwlr_layer_shell_v1 *layer_shell;
struct zxdg_output_manager_v1 *xdg_output_manager;
struct wl_seat *seat;
struct wl_display *wlDisplay = nullptr;
struct wl_registry *registry = nullptr;
struct zwlr_layer_shell_v1 *layer_shell = nullptr;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
struct wl_seat *seat = nullptr;
struct wl_output *wlOutput = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
Client(int argc, char* argv[]);

View File

@ -15,7 +15,7 @@ namespace waybar {
class Factory {
public:
Factory(Bar &bar, Json::Value config);
IModule &makeModule(std::string name);
IModule *makeModule(std::string name);
private:
Bar &_bar;
Json::Value _config;

View File

@ -3,9 +3,10 @@
#include <json/json.h>
#include <filesystem>
#include <fstream>
#include <gtkmm.h>
#include <iostream>
#include <fmt/format.h>
#include <sys/inotify.h>
#include <algorithm>
#include "util/chrono.hpp"
#include "IModule.hpp"
@ -19,7 +20,7 @@ namespace waybar::modules {
auto update() -> void;
operator Gtk::Widget&();
private:
std::string _getIcon(uint32_t percentage);
std::string _getIcon(uint16_t percentage);
static inline const fs::path _data_dir = "/sys/class/power_supply/";
std::vector<fs::path> _batteries;
util::SleeperThread _thread;

View File

@ -1,7 +1,6 @@
#pragma once
#include <json/json.h>
#include <gtkmm.h>
#include <fmt/format.h>
#include "util/chrono.hpp"
#include "IModule.hpp"

View File

@ -1,7 +1,6 @@
#pragma once
#include <json/json.h>
#include <gtkmm.h>
#include <fmt/format.h>
#include <sys/sysinfo.h>
#include "util/chrono.hpp"

View File

@ -1,7 +1,6 @@
#pragma once
#include <json/json.h>
#include <gtkmm.h>
#include <fmt/format.h>
#include "util/chrono.hpp"
#include "IModule.hpp"

View File

@ -1,7 +1,6 @@
#pragma once
#include <json/json.h>
#include <gtkmm.h>
#include <fmt/format.h>
#include <sys/sysinfo.h>
#include "util/chrono.hpp"

View File

@ -5,9 +5,7 @@
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <linux/nl80211.h>
#include <iwlib.h> // TODO
#include <json/json.h>
#include <gtkmm.h>
#include <fmt/format.h>
#include "util/chrono.hpp"
#include "IModule.hpp"

View File

@ -2,8 +2,8 @@
#include <pulse/pulseaudio.h>
#include <json/json.h>
#include <gtkmm.h>
#include <fmt/format.h>
#include <algorithm>
#include "IModule.hpp"
namespace waybar::modules {
@ -14,6 +14,7 @@ namespace waybar::modules {
auto update() -> void;
operator Gtk::Widget &();
private:
std::string _getIcon(uint16_t percentage);
static void _subscribeCb(pa_context *context,
pa_subscription_event_type_t type, uint32_t idx, void *data);
static void _contextStateCb(pa_context *c, void *data);

View File

@ -4,24 +4,34 @@
#include "bar.hpp"
#include "client.hpp"
#include "util/chrono.hpp"
#include "util/json.hpp"
#include "IModule.hpp"
namespace waybar::modules {
class Workspaces : public IModule {
public:
Workspaces(waybar::Bar &bar);
Workspaces(waybar::Bar &bar, Json::Value config);
auto update() -> void;
operator Gtk::Widget &();
private:
void _addWorkspace(Json::Value node);
Json::Value _getWorkspaces();
std::string _getIcon(std::string name);
Json::Value _getWorkspaces(const std::string data);
bool _handleScroll(GdkEventScroll *e);
int _getPrevWorkspace();
int _getNextWorkspace();
Bar &_bar;
Json::Value _config;
waybar::util::SleeperThread _thread;
Gtk::Box _box;
util::JsonParser _parser;
std::mutex _mutex;
bool _scrolling;
std::unordered_map<int, Gtk::Button> _buttons;
int _ipcSocketfd;
int _ipcEventSocketfd;
Json::Value _workspaces;
int _ipcfd;
int _ipcEventfd;
};
}

View File

@ -39,7 +39,9 @@ namespace waybar::util {
func();
} while (do_run);
}}
{}
{
defined = true;
}
SleeperThread& operator=(std::function<void()> func)
{
@ -48,6 +50,7 @@ namespace waybar::util {
func();
} while (do_run);
});
defined = true;
return *this;
}
@ -72,14 +75,17 @@ namespace waybar::util {
~SleeperThread()
{
do_run = false;
if (defined) {
condvar.notify_all();
thread.join();
}
}
private:
std::thread thread;
std::condition_variable condvar;
std::mutex mutex;
bool defined = false;
bool do_run = true;
};

34
include/util/json.hpp Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <json/json.h>
namespace waybar::util {
struct JsonParser {
JsonParser()
: _reader(_builder.newCharReader())
{}
Json::Value parse(const std::string data)
{
Json::Value root;
std::string err;
bool res =
_reader->parse(data.c_str(), data.c_str() + data.size(), &root, &err);
if (!res)
throw std::runtime_error(err);
return root;
}
~JsonParser()
{
delete _reader;
}
private:
Json::CharReaderBuilder _builder;
Json::CharReader *_reader;
};
}

View File

@ -1,6 +1,6 @@
project(
'waybar', 'cpp', 'c',
version: '0.0.2',
version: '0.0.3',
license: 'MIT',
default_options : ['cpp_std=c++17'],
)

View File

@ -7,6 +7,15 @@
"modules-left": ["workspaces", "custom/spotify"],
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "clock"],
// Modules configuration
"workspaces": {
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": ""
}
},
"cpu": {
"format": "{}% "
},
@ -14,7 +23,7 @@
"format": "{}% "
},
"battery": {
"format": "{value}% {icon}",
"format": "{capacity}% {icon}",
"format-icons": ["", "", "", "", ""]
},
"network": {
@ -22,8 +31,9 @@
"format": "{essid} ({signalStrength}%) "
},
"pulseaudio": {
"format": "{}% ",
"format-muted": ""
"format": "{volume}% {icon}",
"format-muted": "",
"format-icons": ["", ""]
},
"custom/spotify": {
"format": " {}",

View File

@ -11,33 +11,37 @@ window {
color: white;
}
.workspaces button {
padding: 0 5px;
#workspaces button {
padding: 0 8px;
background: transparent;
color: white;
border-bottom: 3px solid transparent;
}
.workspaces button.current {
#workspaces button label {
font-size: 12px;
}
#workspaces button.current {
background: #64727D;
border-bottom: 3px solid white;
}
.clock, .battery, .cpu, .memory, .network, .pulseaudio, .custom-spotify {
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify {
padding: 0 10px;
margin: 0 5px;
}
.clock {
#clock {
background-color: #64727D;
}
.battery {
#battery {
background-color: #ffffff;
color: black;
}
.battery.charging {
#battery.charging {
color: white;
background-color: #26A65B;
}
@ -49,7 +53,7 @@ window {
}
}
.battery.warning {
#battery.warning {
background: #f53c3c;
color: white;
animation-name: blink;
@ -59,30 +63,30 @@ window {
animation-direction: alternate;
}
.cpu {
#cpu {
background: #2ecc71;
color: #000000;
}
.memory {
#memory {
background: #9b59b6;
}
.network {
#network {
background: #2980b9;
}
.pulseaudio {
#pulseaudio {
background: #f1c40f;
color: black;
}
.pulseaudio.muted {
#pulseaudio.muted {
background: #90b1b1;
color: #2a5c45;
}
.custom-spotify {
#custom-spotify {
background: #66cc99;
color: #2a5c45;
}

View File

@ -1,10 +1,7 @@
#include <condition_variable>
#include <gdk/gdkwayland.h>
#include <thread>
#include <fstream>
#include "bar.hpp"
#include "client.hpp"
#include "factory.hpp"
#include "util/json.hpp"
waybar::Bar::Bar(Client &client, std::unique_ptr<struct wl_output *> &&p_output)
: client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL},
@ -129,19 +126,13 @@ auto waybar::Bar::toggle() -> void
auto waybar::Bar::_setupConfig() -> void
{
Json::Value root;
Json::CharReaderBuilder builder;
Json::CharReader* reader = builder.newCharReader();
std::string err;
util::JsonParser parser;
std::ifstream file(client.configFile);
if (!file.is_open())
throw std::runtime_error("Can't open config file");
std::string str((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
bool res = reader->parse(str.c_str(), str.c_str() + str.size(), &_config, &err);
delete reader;
if (!res)
throw std::runtime_error(err);
_config = parser.parse(str);
}
auto waybar::Bar::_setupCss() -> void
@ -174,21 +165,24 @@ auto waybar::Bar::_setupWidgets() -> void
if (_config["modules-left"]) {
for (auto name : _config["modules-left"]) {
auto &module = factory.makeModule(name.asString());
left.pack_start(module, false, true, 0);
auto module = factory.makeModule(name.asString());
if (module)
left.pack_start(*module, false, true, 0);
}
}
if (_config["modules-center"]) {
for (auto name : _config["modules-center"]) {
auto &module = factory.makeModule(name.asString());
center.pack_start(module, true, false, 10);
auto module = factory.makeModule(name.asString());
if (module)
center.pack_start(*module, true, false, 10);
}
}
if (_config["modules-right"]) {
std::reverse(_config["modules-right"].begin(), _config["modules-right"].end());
for (auto name : _config["modules-right"]) {
auto &module = factory.makeModule(name.asString());
right.pack_end(module, false, false, 0);
auto module = factory.makeModule(name.asString());
if (module)
right.pack_end(*module, false, false, 0);
}
}
}

View File

@ -46,9 +46,11 @@ void waybar::Client::_handle_global(void *data, struct wl_registry *registry,
o->layer_shell = (zwlr_layer_shell_v1 *)wl_registry_bind(registry, name,
&zwlr_layer_shell_v1_interface, version);
} else if (!strcmp(interface, wl_output_interface.name)) {
auto output = std::make_unique<struct wl_output *>();
*output = (struct wl_output *)wl_registry_bind(registry, name,
o->wlOutput = (struct wl_output *)wl_registry_bind(registry, name,
&wl_output_interface, version);
auto output = std::make_unique<struct wl_output *>();
*output = o->wlOutput;
if (o->xdg_output_manager)
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output)));
} else if (!strcmp(interface, wl_seat_interface.name)) {
o->seat = (struct wl_seat *)wl_registry_bind(registry, name,
@ -58,6 +60,11 @@ void waybar::Client::_handle_global(void *data, struct wl_registry *registry,
o->xdg_output_manager =
(struct zxdg_output_manager_v1 *)wl_registry_bind(registry, name,
&zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION);
if (o->wlOutput) {
auto output = std::make_unique<struct wl_output *>();
*output = o->wlOutput;
o->bars.emplace_back(std::make_unique<Bar>(*o, std::move(output)));
}
}
}

View File

@ -4,23 +4,32 @@ waybar::Factory::Factory(Bar &bar, Json::Value config)
: _bar(bar), _config(config)
{}
waybar::IModule &waybar::Factory::makeModule(std::string name)
waybar::IModule *waybar::Factory::makeModule(std::string name)
{
try {
if (name == "battery")
return *new waybar::modules::Battery(_config[name]);
return new waybar::modules::Battery(_config[name]);
if (name == "workspaces")
return *new waybar::modules::Workspaces(_bar);
return new waybar::modules::Workspaces(_bar, _config[name]);
if (name == "memory")
return *new waybar::modules::Memory(_config[name]);
return new waybar::modules::Memory(_config[name]);
if (name == "cpu")
return *new waybar::modules::Cpu(_config[name]);
return new waybar::modules::Cpu(_config[name]);
if (name == "clock")
return *new waybar::modules::Clock(_config[name]);
return new waybar::modules::Clock(_config[name]);
if (name == "network")
return *new waybar::modules::Network(_config[name]);
return new waybar::modules::Network(_config[name]);
if (name == "pulseaudio")
return *new waybar::modules::Pulseaudio(_config[name]);
return new waybar::modules::Pulseaudio(_config[name]);
if (!name.compare(0, 7, "custom/") && name.size() > 7)
return *new waybar::modules::Custom(name.substr(7), _config[name]);
throw std::runtime_error("Unknown module: " + name);
return new waybar::modules::Custom(name.substr(7), _config[name]);
std::cerr << "Unknown module: " + name << std::endl;
} catch (const std::exception& e) {
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what());
std::cerr << err << std::endl;
} catch (...) {
auto err = fmt::format("Disabling module \"{}\", Unknown reason", name);
std::cerr << err << std::endl;
}
return nullptr;
}

View File

@ -48,10 +48,11 @@ int ipc_open_socket(std::string socket_path) {
}
struct ipc_response ipc_recv_response(int socketfd) {
struct ipc_response response;
char data[ipc_header_size];
uint32_t *data32 = (uint32_t *)(data + sizeof(ipc_magic));
size_t total = 0;
while (total < ipc_header_size) {
ssize_t received = recv(socketfd, data + total, ipc_header_size - total, 0);
if (received <= 0) {
@ -60,8 +61,6 @@ struct ipc_response ipc_recv_response(int socketfd) {
total += received;
}
struct ipc_response response;
total = 0;
response.size = data32[0];
response.type = data32[1];
@ -86,14 +85,10 @@ std::string ipc_single_command(int socketfd, uint32_t type, const char *payload,
data32[0] = *len;
data32[1] = type;
if (send(socketfd, data, ipc_header_size, 0) == -1) {
if (send(socketfd, data, ipc_header_size, 0) == -1)
throw std::runtime_error("Unable to send IPC header");
}
if (send(socketfd, payload, *len, 0) == -1) {
if (send(socketfd, payload, *len, 0) == -1)
throw std::runtime_error("Unable to send IPC payload");
}
struct ipc_response resp = ipc_recv_response(socketfd);
*len = resp.size;
return resp.payload;

View File

@ -1,7 +1,5 @@
#include <gtkmm.h>
#include <wayland-client.hpp>
#include <gdk/gdkwayland.h>
#include <csignal>
#include <iostream>
#include "client.hpp"
namespace waybar {

View File

@ -6,65 +6,73 @@ waybar::modules::Battery::Battery(Json::Value config)
try {
for (auto &node : fs::directory_iterator(_data_dir)) {
if (fs::is_directory(node) && fs::exists(node / "capacity")
&& fs::exists(node / "status")) {
&& fs::exists(node / "status") && fs::exists(node / "uevent"))
_batteries.push_back(node);
}
}
} catch (fs::filesystem_error &e) {
std::cerr << e.what() << std::endl;
throw std::runtime_error(e.what());
}
if (!_batteries.size()) {
std::cerr << "No batteries." << std::endl;
if (!_batteries.size())
throw std::runtime_error("No batteries.");
auto fd = inotify_init();
if (fd == -1)
throw std::runtime_error("Unable to listen batteries.");
for (auto &bat : _batteries)
inotify_add_watch(fd, (bat / "uevent").c_str(), IN_ACCESS);
// Trigger first value
update();
_label.set_name("battery");
_thread = [this, fd] {
struct inotify_event event;
int nbytes = read(fd, &event, sizeof(event));
if (nbytes != sizeof(event))
return;
}
_label.get_style_context()->add_class("battery");
int interval = _config["interval"] ? _config["inveral"].asInt() : 1;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Battery::update));
_thread.sleep_for(chrono::seconds(interval));
};
}
auto waybar::modules::Battery::update() -> void
{
try {
int total = 0;
uint16_t total = 0;
bool charging = false;
for (auto &bat : _batteries) {
int capacity;
std::string status;
for (auto &bat : _batteries) {
uint16_t capacity;
std::ifstream(bat / "capacity") >> capacity;
total += capacity;
std::ifstream(bat / "status") >> status;
if (status == "Charging") {
if (status == "Charging")
charging = true;
total += capacity;
}
}
auto format = _config["format"] ? _config["format"].asString() : "{}%";
auto value = total / _batteries.size();
_label.set_text(fmt::format(format, fmt::arg("value", value),
fmt::arg("icon", _getIcon(value))));
_label.set_tooltip_text(charging ? "Charging" : "Discharging");
uint16_t capacity = total / _batteries.size();
auto format = _config["format"]
? _config["format"].asString() : "{capacity}%";
_label.set_text(fmt::format(format, fmt::arg("capacity", capacity),
fmt::arg("icon", _getIcon(capacity))));
_label.set_tooltip_text(status);
if (charging)
_label.get_style_context()->add_class("charging");
else
_label.get_style_context()->remove_class("charging");
if (value < 16 && !charging)
auto critical = _config["critical"] ? _config["critical"].asUInt() : 15;
if (capacity <= critical && !charging)
_label.get_style_context()->add_class("warning");
else
_label.get_style_context()->remove_class("warning");
} catch (std::exception &e) {
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
std::string waybar::modules::Battery::_getIcon(uint32_t percentage)
std::string waybar::modules::Battery::_getIcon(uint16_t percentage)
{
if (!_config["format-icons"] || !_config["format-icons"].isArray()) return "";
auto step = 100 / _config["format-icons"].size();
return _config["format-icons"][percentage / step].asString();
auto size = _config["format-icons"].size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1);
return _config["format-icons"][idx].asString();
}
waybar::modules::Battery::operator Gtk::Widget &()

View File

@ -3,7 +3,7 @@
waybar::modules::Clock::Clock(Json::Value config)
: _config(config)
{
_label.get_style_context()->add_class("clock");
_label.set_name("clock");
_thread = [this] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Clock::update));
auto now = waybar::chrono::clock::now();

View File

@ -3,7 +3,7 @@
waybar::modules::Cpu::Cpu(Json::Value config)
: _config(config)
{
_label.get_style_context()->add_class("cpu");
_label.set_name("cpu");
int interval = _config["interval"] ? _config["inveral"].asInt() : 10;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Cpu::update));

View File

@ -4,10 +4,8 @@
waybar::modules::Custom::Custom(std::string name, Json::Value config)
: _name(name), _config(config)
{
if (!_config["exec"]) {
std::cerr << name + " has no exec path." << std::endl;
return;
}
if (!_config["exec"])
throw std::runtime_error(name + " has no exec path.");
int interval = _config["interval"] ? _config["inveral"].asInt() : 30;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Custom::update));
@ -36,10 +34,10 @@ auto waybar::modules::Custom::update() -> void
// Hide label if output is empty
if (output.empty()) {
_label.get_style_context()->remove_class("custom-" + _name);
_label.set_name("");
_label.hide();
} else {
_label.get_style_context()->add_class("custom-" + _name);
_label.set_name("custom-" + _name);
auto format = _config["format"] ? _config["format"].asString() : "{}";
_label.set_text(fmt::format(format, output));
_label.show();

View File

@ -3,7 +3,7 @@
waybar::modules::Memory::Memory(Json::Value config)
: _config(config)
{
_label.get_style_context()->add_class("memory");
_label.set_name("memory");
int interval = _config["interval"] ? _config["inveral"].asInt() : 30;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Memory::update));

View File

@ -1,9 +1,11 @@
#include "modules/network.hpp"
waybar::modules::Network::Network(Json::Value config)
: _config(config), _ifid(if_nametoindex(config["interface"].asString().c_str()))
: _config(config), _ifid(if_nametoindex(config["interface"].asCString()))
{
_label.get_style_context()->add_class("network");
if (_ifid == 0)
throw std::runtime_error("Can't found network interface");
_label.set_name("network");
int interval = _config["interval"] ? _config["inveral"].asInt() : 30;
_thread = [this, interval] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Network::update));
@ -14,7 +16,7 @@ waybar::modules::Network::Network(Json::Value config)
auto waybar::modules::Network::update() -> void
{
_getInfo();
auto format = _config["format"] ? _config["format"].asString() : "{}";
auto format = _config["format"] ? _config["format"].asString() : "{essid}";
_label.set_text(fmt::format(format,
fmt::arg("essid", _essid),
fmt::arg("signaldBm", _signalStrengthdBm),
@ -103,8 +105,6 @@ bool waybar::modules::Network::_associatedOrJoined(struct nlattr** bss)
auto waybar::modules::Network::_getInfo() -> void
{
if (_ifid == 0)
return;
struct nl_sock *sk = nl_socket_alloc();
if (genl_connect(sk) != 0) {
nl_socket_free(sk);

View File

@ -4,7 +4,7 @@ waybar::modules::Pulseaudio::Pulseaudio(Json::Value config)
: _config(config), _mainloop(nullptr), _mainloop_api(nullptr),
_context(nullptr), _sinkIdx(0), _volume(0), _muted(false)
{
_label.get_style_context()->add_class("pulseaudio");
_label.set_name("pulseaudio");
_mainloop = pa_threaded_mainloop_new();
if (!_mainloop)
throw std::runtime_error("pa_mainloop_new() failed.");
@ -97,17 +97,27 @@ void waybar::modules::Pulseaudio::_serverInfoCb(pa_context *context,
auto waybar::modules::Pulseaudio::update() -> void
{
auto format = _config["format"] ? _config["format"].asString() : "{}%";
auto format = _config["format"] ? _config["format"].asString() : "{volume}%";
if (_muted) {
format =
_config["format-muted"] ? _config["format-muted"].asString() : format;
_label.get_style_context()->add_class("muted");
} else
_label.get_style_context()->remove_class("muted");
_label.set_label(fmt::format(format, _volume));
_label.set_label(fmt::format(format,
fmt::arg("volume", _volume),
fmt::arg("icon", _getIcon(_volume))));
_label.set_tooltip_text(_desc);
}
std::string waybar::modules::Pulseaudio::_getIcon(uint16_t percentage)
{
if (!_config["format-icons"] || !_config["format-icons"].isArray()) return "";
auto size = _config["format-icons"].size();
auto idx = std::clamp(percentage / (100 / size), 0U, size - 1);
return _config["format-icons"][idx].asString();
}
waybar::modules::Pulseaudio::operator Gtk::Widget &() {
return _label;
}

View File

@ -1,104 +1,182 @@
#include "modules/workspaces.hpp"
#include "ipc/client.hpp"
waybar::modules::Workspaces::Workspaces(Bar &bar)
: _bar(bar)
waybar::modules::Workspaces::Workspaces(Bar &bar, Json::Value config)
: _bar(bar), _config(config), _scrolling(false)
{
_box.get_style_context()->add_class("workspaces");
try {
_box.set_name("workspaces");
std::string socketPath = get_socketpath();
_ipcSocketfd = ipc_open_socket(socketPath);
_ipcEventSocketfd = ipc_open_socket(socketPath);
const char *subscribe = "[ \"workspace\", \"mode\" ]";
_ipcfd = ipc_open_socket(socketPath);
_ipcEventfd = ipc_open_socket(socketPath);
const char *subscribe = "[ \"workspace\" ]";
uint32_t len = strlen(subscribe);
ipc_single_command(_ipcEventSocketfd, IPC_SUBSCRIBE, subscribe, &len);
ipc_single_command(_ipcEventfd, IPC_SUBSCRIBE, subscribe, &len);
_thread = [this] {
try {
if (_bar.outputName.empty()) {
// Wait for the name of the output
while (_bar.outputName.empty())
_thread.sleep_for(chrono::milliseconds(150));
} else
ipc_recv_response(_ipcEventfd);
uint32_t len = 0;
std::lock_guard<std::mutex> lock(_mutex);
auto str = ipc_single_command(_ipcfd, IPC_GET_WORKSPACES, nullptr, &len);
_workspaces = _getWorkspaces(str);
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Workspaces::update));
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return;
}
_thread = [this] {
Glib::signal_idle().connect_once(sigc::mem_fun(*this, &Workspaces::update));
_thread.sleep_for(chrono::milliseconds(250));
};
}
auto waybar::modules::Workspaces::update() -> void
{
if (_bar.outputName.empty()) return;
Json::Value workspaces = _getWorkspaces();
std::lock_guard<std::mutex> lock(_mutex);
bool needReorder = false;
for (auto it = _buttons.begin(); it != _buttons.end(); ++it) {
auto ws = std::find_if(workspaces.begin(), workspaces.end(),
for (auto it = _buttons.begin(); it != _buttons.end();) {
auto ws = std::find_if(_workspaces.begin(), _workspaces.end(),
[it](auto node) -> bool { return node["num"].asInt() == it->first; });
if (ws == workspaces.end()) {
if (ws == _workspaces.end()) {
it = _buttons.erase(it);
needReorder = true;
} else
++it;
}
}
for (auto node : workspaces) {
for (auto node : _workspaces) {
if (_bar.outputName != node["output"].asString())
continue;
auto it = _buttons.find(node["num"].asInt());
if (it == _buttons.end() && _bar.outputName == node["output"].asString()) {
if (it == _buttons.end()) {
_addWorkspace(node);
needReorder = true;
} else {
auto styleContext = it->second.get_style_context();
bool isCurrent = node["focused"].asBool();
if (!isCurrent) {
styleContext->remove_class("current");
} else if (isCurrent) {
styleContext->add_class("current");
}
auto &button = it->second;
if (node["focused"].asBool())
button.get_style_context()->add_class("current");
else
button.get_style_context()->remove_class("current");
if (needReorder)
_box.reorder_child(it->second, node["num"].asInt() - 1);
it->second.show();
_box.reorder_child(button, node["num"].asInt());
button.show();
}
}
if (_scrolling)
_scrolling = false;
}
void waybar::modules::Workspaces::_addWorkspace(Json::Value node)
{
auto pair = _buttons.emplace(node["num"].asInt(), node["name"].asString());
auto pair = _buttons.emplace(node["num"].asInt(),
_getIcon(node["name"].asString()));
auto &button = pair.first->second;
_box.pack_start(button, false, false, 0);
button.set_relief(Gtk::RELIEF_NONE);
button.signal_clicked().connect([this, pair] {
try {
std::lock_guard<std::mutex> lock(_mutex);
auto value = fmt::format("workspace \"{}\"", pair.first->first);
uint32_t size = value.size();
ipc_single_command(_ipcSocketfd, IPC_COMMAND, value.c_str(), &size);
ipc_single_command(_ipcfd, IPC_COMMAND, value.c_str(), &size);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
});
_box.reorder_child(button, node["num"].asInt() - 1);
if (node["focused"].asBool()) {
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
button.signal_scroll_event()
.connect(sigc::mem_fun(*this, &Workspaces::_handleScroll));
_box.reorder_child(button, node["num"].asInt());
if (node["focused"].asBool())
button.get_style_context()->add_class("current");
}
button.show();
}
Json::Value waybar::modules::Workspaces::_getWorkspaces()
std::string waybar::modules::Workspaces::_getIcon(std::string name)
{
uint32_t len = 0;
Json::Value root;
Json::CharReaderBuilder builder;
Json::CharReader* reader = builder.newCharReader();
try {
std::string str = ipc_single_command(_ipcSocketfd, IPC_GET_WORKSPACES,
nullptr, &len);
std::string err;
bool res =
reader->parse(str.c_str(), str.c_str() + str.size(), &root, &err);
delete reader;
if (!res) {
std::cerr << err << std::endl;
return root;
if (_config["format-icons"][name])
return _config["format-icons"][name].asString();
if (_config["format-icons"]["default"])
return _config["format-icons"]["default"].asString();
return name;
}
bool waybar::modules::Workspaces::_handleScroll(GdkEventScroll *e)
{
std::lock_guard<std::mutex> lock(_mutex);
// Avoid concurrent scroll event
if (_scrolling)
return false;
_scrolling = true;
int id = -1;
uint16_t idx = 0;
for (; idx < _workspaces.size(); idx += 1)
if (_workspaces[idx]["focused"].asBool()) {
id = _workspaces[idx]["num"].asInt();
break;
}
if (id == -1) {
_scrolling = false;
return false;
}
if (e->direction == GDK_SCROLL_UP)
id = _getNextWorkspace();
if (e->direction == GDK_SCROLL_DOWN)
id = _getPrevWorkspace();
if (e->direction == GDK_SCROLL_SMOOTH) {
gdouble delta_x, delta_y;
gdk_event_get_scroll_deltas ((const GdkEvent *) e, &delta_x, &delta_y);
if (delta_y < 0)
id = _getNextWorkspace();
else if (delta_y > 0)
id = _getPrevWorkspace();
}
if (id == _workspaces[idx]["num"].asInt()) {
_scrolling = false;
return false;
}
auto value = fmt::format("workspace \"{}\"", id);
uint32_t size = value.size();
ipc_single_command(_ipcfd, IPC_COMMAND, value.c_str(), &size);
std::this_thread::sleep_for(std::chrono::milliseconds(150));
return true;
}
int waybar::modules::Workspaces::_getPrevWorkspace()
{
int current = -1;
for (uint16_t i = 0; i != _workspaces.size(); i += 1)
if (_workspaces[i]["focused"].asBool()) {
current = _workspaces[i]["num"].asInt();
if (i > 0)
return _workspaces[i - 1]["num"].asInt();
return _workspaces[_workspaces.size() - 1]["num"].asInt();
}
return current;
}
int waybar::modules::Workspaces::_getNextWorkspace()
{
int current = -1;
for (uint16_t i = 0; i != _workspaces.size(); i += 1)
if (_workspaces[i]["focused"].asBool()) {
current = _workspaces[i]["num"].asInt();
if (i + 1U < _workspaces.size())
return _workspaces[i + 1]["num"].asInt();
return _workspaces[0]["num"].asInt();
}
return current;
}
Json::Value waybar::modules::Workspaces::_getWorkspaces(const std::string data)
{
Json::Value res;
try {
std::string err;
res = _parser.parse(data);
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
return root;
return res;
}
waybar::modules::Workspaces::operator Gtk::Widget &() {