mirror of
https://github.com/rad4day/Waybar.git
synced 2023-12-21 10:22:59 +01:00
feat: init repo
This commit is contained in:
169
src/bar.cpp
Normal file
169
src/bar.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
#include <condition_variable>
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <thread>
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "util/chrono.hpp"
|
||||
#include "modules/clock.hpp"
|
||||
#include "modules/workspaces.hpp"
|
||||
#include "modules/battery.hpp"
|
||||
|
||||
static void handle_geometry(void *data, struct wl_output *wl_output, int32_t x,
|
||||
int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel,
|
||||
const char *make, const char *model, int32_t transform)
|
||||
{
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
static void handle_mode(void *data, struct wl_output *wl_output, uint32_t f,
|
||||
int32_t w, int32_t h, int32_t refresh)
|
||||
{
|
||||
auto o = reinterpret_cast<waybar::Bar *>(data);
|
||||
std::cout << fmt::format("Bar width configured: {}", w) << std::endl;
|
||||
o->set_width(w);
|
||||
}
|
||||
|
||||
static void handle_done(void *data, struct wl_output *)
|
||||
{
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
static void handle_scale(void *data, struct wl_output *wl_output,
|
||||
int32_t factor)
|
||||
{
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
static const struct wl_output_listener outputListener = {
|
||||
.geometry = handle_geometry,
|
||||
.mode = handle_mode,
|
||||
.done = handle_done,
|
||||
.scale = handle_scale,
|
||||
};
|
||||
|
||||
static void layer_surface_handle_configure(
|
||||
void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial,
|
||||
uint32_t width, uint32_t height)
|
||||
{
|
||||
auto o = reinterpret_cast<waybar::Bar *>(data);
|
||||
o->window.show_all();
|
||||
zwlr_layer_surface_v1_ack_configure(surface, serial);
|
||||
if (o->client.height != height)
|
||||
{
|
||||
height = o->client.height;
|
||||
std::cout << fmt::format("New Height: {}", height) << std::endl;
|
||||
zwlr_layer_surface_v1_set_size(surface, width, height);
|
||||
zwlr_layer_surface_v1_set_exclusive_zone(surface, o->visible ? height : 0);
|
||||
wl_surface_commit(o->surface);
|
||||
}
|
||||
}
|
||||
|
||||
static void layer_surface_handle_closed(void *data,
|
||||
struct zwlr_layer_surface_v1 *surface)
|
||||
{
|
||||
auto o = reinterpret_cast<waybar::Bar *>(data);
|
||||
zwlr_layer_surface_v1_destroy(o->layer_surface);
|
||||
o->layer_surface = NULL;
|
||||
wl_surface_destroy(o->surface);
|
||||
o->surface = NULL;
|
||||
o->window.close();
|
||||
}
|
||||
|
||||
static const struct zwlr_layer_surface_v1_listener layerSurfaceListener = {
|
||||
.configure = layer_surface_handle_configure,
|
||||
.closed = layer_surface_handle_closed,
|
||||
};
|
||||
|
||||
waybar::Bar::Bar(Client &client, std::unique_ptr<struct wl_output *> &&p_output)
|
||||
: client(client), window{Gtk::WindowType::WINDOW_TOPLEVEL},
|
||||
output(std::move(p_output))
|
||||
{
|
||||
wl_output_add_listener(*output, &outputListener, this);
|
||||
window.set_title("waybar");
|
||||
window.set_decorated(false);
|
||||
// window.set_resizable(false);
|
||||
setup_css();
|
||||
setup_widgets();
|
||||
gtk_widget_realize(GTK_WIDGET(window.gobj()));
|
||||
GdkWindow *gdkWindow = gtk_widget_get_window(GTK_WIDGET(window.gobj()));
|
||||
gdk_wayland_window_set_use_custom_surface(gdkWindow);
|
||||
surface = gdk_wayland_window_get_wl_surface(gdkWindow);
|
||||
layer_surface = zwlr_layer_shell_v1_get_layer_surface(
|
||||
client.layer_shell, surface, *output, ZWLR_LAYER_SHELL_V1_LAYER_TOP,
|
||||
"waybar");
|
||||
zwlr_layer_surface_v1_set_anchor(layer_surface,
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
|
||||
zwlr_layer_surface_v1_set_size(layer_surface, width, client.height);
|
||||
zwlr_layer_surface_v1_add_listener(layer_surface, &layerSurfaceListener,
|
||||
this);
|
||||
wl_surface_commit(surface);
|
||||
}
|
||||
|
||||
auto waybar::Bar::setup_css() -> void
|
||||
{
|
||||
css_provider = Gtk::CssProvider::create();
|
||||
style_context = Gtk::StyleContext::create();
|
||||
|
||||
// load our css file, wherever that may be hiding
|
||||
if (css_provider->load_from_path(client.css_file))
|
||||
{
|
||||
Glib::RefPtr<Gdk::Screen> screen = window.get_screen();
|
||||
style_context->add_provider_for_screen(screen, css_provider,
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::Bar::set_width(int width) -> void
|
||||
{
|
||||
this->width = width;
|
||||
window.set_size_request(width);
|
||||
window.resize(width, client.height);
|
||||
zwlr_layer_surface_v1_set_size(layer_surface, width, 40);
|
||||
wl_surface_commit(surface);
|
||||
}
|
||||
|
||||
auto waybar::Bar::toggle() -> void
|
||||
{
|
||||
visible = !visible;
|
||||
auto zone = visible ? client.height : 0;
|
||||
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone);
|
||||
wl_surface_commit(surface);
|
||||
}
|
||||
|
||||
auto waybar::Bar::setup_widgets() -> void
|
||||
{
|
||||
auto &left = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
|
||||
auto ¢er = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
|
||||
auto &right = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
|
||||
|
||||
auto &box1 = *Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
|
||||
window.add(box1);
|
||||
box1.set_homogeneous(true);
|
||||
box1.pack_start(left, true, true);
|
||||
box1.pack_start(center, false, false);
|
||||
box1.pack_end(right, true, true);
|
||||
|
||||
auto &focused_window = *Gtk::manage(new Gtk::Label());
|
||||
focused_window.get_style_context()->add_class("focused-window-title");
|
||||
client.signals.focused_window_name.connect(
|
||||
[&focused_window](std::string focused_window_name) {
|
||||
if (focused_window_name.size() > 70)
|
||||
{
|
||||
focused_window_name.erase(67);
|
||||
focused_window_name += "...";
|
||||
}
|
||||
focused_window.set_text(focused_window_name);
|
||||
});
|
||||
|
||||
focused_window.set_hexpand(false);
|
||||
|
||||
auto &clock = *new waybar::modules::Clock();
|
||||
auto &workspace_selector = *new waybar::modules::WorkspaceSelector(*this);
|
||||
auto &battery = *new waybar::modules::Battery();
|
||||
|
||||
left.pack_start(workspace_selector, false, true, 0);
|
||||
// center.pack_start(workspace_selector, true, false, 10);
|
||||
right.pack_end(clock, false, false, 0);
|
||||
right.pack_end(battery, false, false, 0);
|
||||
}
|
47
src/client.cpp
Normal file
47
src/client.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "client.hpp"
|
||||
|
||||
static void handle_global(void *data, struct wl_registry *registry,
|
||||
uint32_t name, const char *interface, uint32_t version)
|
||||
{
|
||||
auto o = reinterpret_cast<waybar::Client *>(data);
|
||||
if (!strcmp(interface, zwlr_layer_shell_v1_interface.name)) {
|
||||
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,
|
||||
&wl_output_interface, version);
|
||||
o->bars.emplace_back(*o, std::move(output));
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_global_remove(void *data,
|
||||
struct wl_registry *registry, uint32_t name)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = handle_global,
|
||||
.global_remove = handle_global_remove,
|
||||
};
|
||||
|
||||
waybar::Client::Client(int argc, char* argv[])
|
||||
: gtk_main(argc, argv),
|
||||
gdk_display(Gdk::Display::get_default()),
|
||||
wlDisplay(gdk_wayland_display_get_wl_display(gdk_display->gobj()))
|
||||
{}
|
||||
|
||||
void waybar::Client::bind_interfaces()
|
||||
{
|
||||
registry = wl_display_get_registry(wlDisplay);
|
||||
wl_registry_add_listener(registry, ®istry_listener, this);
|
||||
wl_display_roundtrip(wlDisplay);
|
||||
}
|
||||
|
||||
int waybar::Client::main(int argc, char* argv[])
|
||||
{
|
||||
bind_interfaces();
|
||||
gtk_main.run();
|
||||
return 0;
|
||||
}
|
101
src/ipc/client.cpp
Normal file
101
src/ipc/client.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include "ipc/client.hpp"
|
||||
|
||||
static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'};
|
||||
static const size_t ipc_header_size = sizeof(ipc_magic)+8;
|
||||
|
||||
std::string get_socketpath(void) {
|
||||
const char *env = getenv("SWAYSOCK");
|
||||
if (env) return std::string(env);
|
||||
std::string str;
|
||||
{
|
||||
std::string str_buf;
|
||||
FILE* in;
|
||||
char buf[512] = { 0 };
|
||||
if (!(in = popen("sway --get-socketpath 2>/dev/null", "r"))) {
|
||||
throw std::runtime_error("Failed to get socket path");
|
||||
}
|
||||
while (fgets(buf, sizeof(buf), in) != nullptr) {
|
||||
str_buf.append(buf, sizeof(buf));
|
||||
}
|
||||
pclose(in);
|
||||
str = str_buf;
|
||||
}
|
||||
if (str.back() == '\n') {
|
||||
str.pop_back();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
int ipc_open_socket(std::string socket_path) {
|
||||
struct sockaddr_un addr;
|
||||
int socketfd;
|
||||
if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
||||
throw std::runtime_error("Unable to open Unix socket");
|
||||
}
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1);
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
if (connect(socketfd, (struct sockaddr *)&addr, l) == -1) {
|
||||
throw std::runtime_error("Unable to connect to " + socket_path);
|
||||
}
|
||||
return socketfd;
|
||||
}
|
||||
|
||||
struct ipc_response ipc_recv_response(int socketfd) {
|
||||
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) {
|
||||
throw std::runtime_error("Unable to receive IPC response");
|
||||
}
|
||||
total += received;
|
||||
}
|
||||
|
||||
struct ipc_response response;
|
||||
|
||||
total = 0;
|
||||
response.size = data32[0];
|
||||
response.type = data32[1];
|
||||
char payload[response.size + 1];
|
||||
|
||||
while (total < response.size) {
|
||||
ssize_t received = recv(socketfd, payload + total, response.size - total, 0);
|
||||
if (received < 0) {
|
||||
throw std::runtime_error("Unable to receive IPC response");
|
||||
}
|
||||
total += received;
|
||||
}
|
||||
payload[response.size] = '\0';
|
||||
response.payload = std::string(payload);
|
||||
return response;
|
||||
}
|
||||
|
||||
std::string ipc_single_command(int socketfd, uint32_t type, const char *payload, uint32_t *len) {
|
||||
char data[ipc_header_size];
|
||||
uint32_t *data32 = (uint32_t *)(data + sizeof(ipc_magic));
|
||||
memcpy(data, ipc_magic, sizeof(ipc_magic));
|
||||
data32[0] = *len;
|
||||
data32[1] = type;
|
||||
|
||||
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) {
|
||||
throw std::runtime_error("Unable to send IPC payload");
|
||||
}
|
||||
|
||||
struct ipc_response resp = ipc_recv_response(socketfd);
|
||||
std::string response = resp.payload;
|
||||
*len = resp.size;
|
||||
return response;
|
||||
}
|
30
src/main.cpp
Normal file
30
src/main.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include <gtkmm.h>
|
||||
#include <wayland-client.hpp>
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <csignal>
|
||||
#include "client.hpp"
|
||||
|
||||
namespace waybar {
|
||||
static Client* client;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try {
|
||||
waybar::Client c(argc, argv);
|
||||
waybar::client = &c;
|
||||
std::signal(SIGUSR1, [] (int signal) {
|
||||
for (auto& bar : waybar::client->bars) {
|
||||
bar.toggle();
|
||||
}
|
||||
});
|
||||
|
||||
return c.main(argc, argv);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
return 1;
|
||||
} catch (const Glib::Exception& e) {
|
||||
std::cerr << e.what().c_str() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
49
src/modules/battery.cpp
Normal file
49
src/modules/battery.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "modules/battery.hpp"
|
||||
|
||||
waybar::modules::Battery::Battery()
|
||||
{
|
||||
try {
|
||||
for (auto &node : fs::directory_iterator(_data_dir)) {
|
||||
if (fs::is_directory(node) && fs::exists(node / "charge_now") &&
|
||||
fs::exists(node / "charge_full")) {
|
||||
_batteries.push_back(node);
|
||||
}
|
||||
}
|
||||
} catch (fs::filesystem_error &e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
|
||||
_label.get_style_context()->add_class("battery-status");
|
||||
|
||||
_thread = [this] {
|
||||
update();
|
||||
_thread.sleep_for(chrono::minutes(1));
|
||||
};
|
||||
}
|
||||
|
||||
auto waybar::modules::Battery::update() -> void
|
||||
{
|
||||
try {
|
||||
for (auto &bat : _batteries) {
|
||||
int full, now;
|
||||
std::string status;
|
||||
std::ifstream(bat / "charge_now") >> now;
|
||||
std::ifstream(bat / "charge_full") >> full;
|
||||
std::ifstream(bat / "status") >> status;
|
||||
if (status == "Charging") {
|
||||
_label.get_style_context()->add_class("battery-charging");
|
||||
} else {
|
||||
_label.get_style_context()->remove_class("battery-charging");
|
||||
}
|
||||
int pct = float(now) / float(full) * 100.f;
|
||||
_label.set_text_with_mnemonic(fmt::format("{}% {}", pct, ""));
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
waybar::modules::Battery::operator Gtk::Widget &()
|
||||
{
|
||||
return _label;
|
||||
}
|
20
src/modules/clock.cpp
Normal file
20
src/modules/clock.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include "modules/clock.hpp"
|
||||
|
||||
waybar::modules::Clock::Clock()
|
||||
{
|
||||
_label.get_style_context()->add_class("clock-widget");
|
||||
_thread = [this] {
|
||||
auto now = waybar::chrono::clock::now();
|
||||
auto t = std::time(nullptr);
|
||||
auto localtime = std::localtime(&t);
|
||||
_label.set_text(
|
||||
fmt::format("{:02}:{:02}", localtime->tm_hour, localtime->tm_min));
|
||||
auto timeout =
|
||||
std::chrono::floor<std::chrono::minutes>(now + std::chrono::minutes(1));
|
||||
_thread.sleep_until(timeout);
|
||||
};
|
||||
};
|
||||
|
||||
waybar::modules::Clock::operator Gtk::Widget &() {
|
||||
return _label;
|
||||
}
|
83
src/modules/workspaces.cpp
Normal file
83
src/modules/workspaces.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include "modules/workspaces.hpp"
|
||||
#include "ipc/client.hpp"
|
||||
|
||||
waybar::modules::WorkspaceSelector::WorkspaceSelector(Bar &bar)
|
||||
: _bar(bar), _box(Gtk::manage(new Gtk::Box))
|
||||
{
|
||||
_box->get_style_context()->add_class("workspace-selector");
|
||||
std::string socketPath = get_socketpath();
|
||||
_ipcSocketfd = ipc_open_socket(socketPath);
|
||||
_ipcEventSocketfd = ipc_open_socket(socketPath);
|
||||
const char *subscribe = "[ \"workspace\", \"mode\" ]";
|
||||
uint32_t len = strlen(subscribe);
|
||||
ipc_single_command(_ipcEventSocketfd, IPC_SUBSCRIBE, subscribe, &len);
|
||||
_thread = [this] {
|
||||
update();
|
||||
};
|
||||
}
|
||||
|
||||
auto waybar::modules::WorkspaceSelector::update() -> void
|
||||
{
|
||||
Json::Value workspaces = _getWorkspaces();
|
||||
for (auto it = _buttons.begin(); it != _buttons.end(); ++it) {
|
||||
auto ws = std::find_if(workspaces.begin(), workspaces.end(),
|
||||
[it](auto node) -> bool { return node["num"].asInt() == it->first; });
|
||||
if (ws == workspaces.end()) {
|
||||
it->second.hide();
|
||||
}
|
||||
}
|
||||
for (auto node : workspaces) {
|
||||
auto it = _buttons.find(node["num"].asInt());
|
||||
if (it == _buttons.end()) {
|
||||
_addWorkspace(node);
|
||||
} else {
|
||||
auto styleContext = it->second.get_style_context();
|
||||
bool isCurrent = node["focused"].asBool();
|
||||
if (styleContext->has_class("current") && !isCurrent) {
|
||||
styleContext->remove_class("current");
|
||||
} else if (!styleContext->has_class("current") && isCurrent) {
|
||||
styleContext->add_class("current");
|
||||
}
|
||||
it->second.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::modules::WorkspaceSelector::_addWorkspace(Json::Value node)
|
||||
{
|
||||
auto pair = _buttons.emplace(node["num"].asInt(), node["name"].asString());
|
||||
auto &button = pair.first->second;
|
||||
button.set_relief(Gtk::RELIEF_NONE);
|
||||
button.signal_clicked().connect([this, pair] {
|
||||
auto value = fmt::format("workspace \"{}\"", pair.first->first);
|
||||
uint32_t size = value.size();
|
||||
ipc_single_command(_ipcSocketfd, IPC_COMMAND, value.c_str(), &size);
|
||||
});
|
||||
_box->pack_start(button, false, false, 0);
|
||||
if (node["focused"].asBool()) {
|
||||
button.get_style_context()->add_class("current");
|
||||
}
|
||||
button.show();
|
||||
}
|
||||
|
||||
Json::Value waybar::modules::WorkspaceSelector::_getWorkspaces()
|
||||
{
|
||||
uint32_t len = 0;
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder builder;
|
||||
Json::CharReader* reader = builder.newCharReader();
|
||||
std::string err;
|
||||
std::string str = ipc_single_command(_ipcSocketfd, IPC_GET_WORKSPACES,
|
||||
nullptr, &len);
|
||||
bool res = reader->parse(str.c_str(), str.c_str() + str.size(), &root, &err);
|
||||
delete reader;
|
||||
if (!res) {
|
||||
std::cerr << err << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
waybar::modules::WorkspaceSelector::operator Gtk::Widget &() {
|
||||
return *_box;
|
||||
}
|
Reference in New Issue
Block a user