no compile errors, fully functional workspace indicators

This commit is contained in:
2026-01-31 20:00:35 +01:00
parent 7ad6f46b3c
commit f3b250759e
17 changed files with 400 additions and 809 deletions

View File

@@ -5,7 +5,8 @@ set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -DNDEBUG -Wall -Wextra -Wpedantic -Werror")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG -Wall -Wextra -Wpedantic -Werror")
find_package(PkgConfig REQUIRED)
@@ -22,26 +23,23 @@ link_directories(${GTKMM_LIBRARY_DIRS} ${LAYERSHELL_LIBRARY_DIRS} ${WEBKIT_LIBRA
add_library(bar_lib)
target_sources(bar_lib
PUBLIC
src/app.cpp
src/bar/bar.cpp
src/app.cpp
src/bar/bar.cpp
src/widgets/clock.cpp
src/widgets/date.cpp
src/widgets/volumeWidget.cpp
src/widgets/webWidget.cpp
src/widgets/clock.cpp
src/widgets/date.cpp
src/widgets/volumeWidget.cpp
src/widgets/webWidget.cpp
src/services/hyprland.cpp
src/services/tray.cpp
src/services/notifications.cpp
src/services/bluetooth.cpp
src/services/hyprland.cpp
src/services/tray.cpp
src/widgets/tray.cpp
src/widgets/bluetooth.cpp
src/widgets/controlCenter.cpp
src/widgets/tray.cpp
src/widgets/controlCenter.cpp
src/components/popover.cpp
src/components/workspaceIndicator.cpp
src/components/base/button.cpp
src/components/popover.cpp
src/components/workspaceIndicator.cpp
src/components/base/button.cpp
)
include_directories(bar_lib PRIVATE
include

View File

@@ -4,8 +4,6 @@
#include "bar/bar.hpp"
#include "services/hyprland.hpp"
#include "services/notifications.hpp"
#include "services/tray.hpp"
#include "glibmm/refptr.h"
#include "gtkmm/application.h"
@@ -18,9 +16,8 @@ class App {
private:
Glib::RefPtr<Gtk::Application> app;
std::vector<Bar *> bars;
std::vector<std::shared_ptr<Bar>> bars;
HyprlandService *hyprlandService = nullptr;
NotificationService notificationService;
TrayService *trayService = TrayService::getInstance();
void setupServices();

View File

@@ -1,14 +1,35 @@
#pragma once
#include <string>
#include "gtkmm/box.h"
#include "gtkmm/button.h"
#include "gtkmm/overlay.h"
class WorkspaceIndicator : public Gtk::Box {
public:
WorkspaceIndicator(std::string label, sigc::slot<void()> onClick = {});
public:
private:
Gtk::Button button;
enum InidicatorState {
EMPTY,
ALIVE,
FOCUSED,
PRESENTING,
URGENT,
};
// meh, Maybe try WorkspaceState struct later
WorkspaceIndicator(int id, std::string label, sigc::slot<void(int)> onClick);
void setIndicatorState(InidicatorState state);
private:
void clearCssClass();
InidicatorState currentState = EMPTY;
std::shared_ptr<Gtk::Overlay> overlay;
std::map<InidicatorState, std::string> stateToCssClass = {
{FOCUSED, "workspace-pill-focused"},
{URGENT, "workspace-pill-urgent"},
{ALIVE, "workspace-pill-alive"},
{PRESENTING, "workspace-pill-presenting"},
{EMPTY, "workspace-pill-empty"}, // Default class
};
};

View File

@@ -9,12 +9,13 @@
#include "helpers/string.hpp"
class SocketHelper {
public:
typedef struct SocketMessage {
std::string eventType;
std::string eventData;
} SocketMessage;
public:
static std::vector<SocketMessage> parseSocketMessage(int socketFd, const std::string &delimiter) {
char buffer[4096];
std::string data;

View File

@@ -3,43 +3,50 @@
#include <glibmm.h>
#include <map>
#include <memory>
#include <set>
#include <sigc++/sigc++.h>
#include <string>
#include <sys/stat.h>
#include <vector>
#include "bar/bar.hpp"
#include "components/workspaceIndicator.hpp"
#include "helpers/socket.hpp"
#include "gtkmm/box.h"
#define NUM_WORKSPACES 7
class HyprlandService {
inline static HyprlandService *instance = nullptr;
inline static HyprlandService *instance = nullptr;
public:
struct WorkspaceState {
int id;
int monitorId;
bool alive = false; // window count above 0 (exists in hyprctl workspaces array)
bool presenting = false; // $(hyprctl monitors).activeWorkspace == id
bool focused = false; // $(hyprctl monitors).activeWorkspace == id && (hyprctl monitors).focused
bool urgent = false; // check ctl clients, and find ws with urgent window
struct Client {
std::string address;
int workspaceId;
std::string title;
};
struct WorkspaceData {
int id;
std::string monitorName;
std::string label;
std::map<std::string, std::shared_ptr<Client>> clients;
std::set<std::string> urgentClients;
};
struct Workspace {
WorkspaceState state;
std::shared_ptr<WorkspaceData> state;
std::shared_ptr<WorkspaceIndicator> view;
};
struct Monitor {
int id;
int activeWorkspaceId;
bool focused = false;
bool focused;
std::string name;
std::map<int, std::shared_ptr<Workspace>> monitorWorkspaces;
std::shared_ptr<Bar> bar;
};
static HyprlandService *getInstance() {
@@ -49,22 +56,57 @@ inline static HyprlandService *instance = nullptr;
return instance;
}
std::shared_ptr<Gtk::Box> getWorkspaceIndicatorsForMonitor(int monitorId);
std::shared_ptr<Gtk::Box> getWorkspaceIndicatorsForMonitor(std::string monitorName);
void addBar(std::shared_ptr<Bar> bar, std::string monitorName);
private:
enum SocketEventType {
WORKSPACE_CHANGED,
ACTIVE_WINDOW,
OPEN_WINDOW,
CLOSE_WINDOW,
URGENT,
FOCUSED_MONITOR,
MONITOR_REMOVED,
};
std::map<std::string, SocketEventType> socketEventTypeMap = {
{"workspace", WORKSPACE_CHANGED},
{"activewindowv2", ACTIVE_WINDOW},
{"openwindow", OPEN_WINDOW},
{"closewindow", CLOSE_WINDOW},
{"urgent", URGENT},
{"focusedmon", FOCUSED_MONITOR},
{"monitorremoved", MONITOR_REMOVED},
};
void onWorkspaceChanged(int workspaceId);
void onFocusedMonitorChanged(std::string monitorData);
void onOpenWindow(std::string windowData);
void onCloseWindow(std::string windowData);
void onUrgent(std::string windowAddress);
void onActiveWindowChanged(std::string windowAddress);
void onMonitorRemoved(std::string monitorName);
// void onMonitorAdded(std::string monitorName);
HyprlandService();
std::map<int, std::shared_ptr<Monitor>> monitors;
std::map<std::string, std::shared_ptr<Monitor>> monitors;
std::map<int, std::shared_ptr<Workspace>> workspaces;
std::map<std::string, std::shared_ptr<Client>> clients;
/// maybe refactor into reusable class
std::string socketBuffer;
int socketFd;
///
std::string getSocketPath();
void bindSocket();
void init();
void updateIndicator(Workspace &workspace, const WorkspaceState newState);
void switchToWorkspace(int workspaceId);
void refreshIndicator(std::shared_ptr<Workspace> workspace);
void handleSocketMessage(SocketHelper::SocketMessage message);
};

View File

@@ -1,20 +0,0 @@
#pragma once
#include <gio/gio.h>
class NotificationService {
public:
void intialize();
NotificationService() = default;
~NotificationService();
guint32 allocateNotificationId(guint32 replacesId);
GDBusConnection *getConnection() const { return connection; }
private:
GDBusConnection *connection = nullptr;
guint registrationId = 0;
GDBusNodeInfo *nodeInfo = nullptr;
guint32 nextNotificationId = 1;
};

View File

@@ -1,8 +1,6 @@
#pragma once
#include "components/popover.hpp"
#include "services/bluetooth.hpp"
#include "widgets/bluetooth.hpp"
#include "gtkmm/box.h"
class ControlCenter : public Popover {
@@ -11,7 +9,4 @@ class ControlCenter : public Popover {
private:
Gtk::Box container;
BluetoothWidget *bluetoothWidget = nullptr;
BluetoothService *bluetoothService = BluetoothService::getInstance();
};

View File

@@ -1,6 +1,6 @@
#include "app.hpp"
int main(int argc, char *argv[])
int main()
{
App app;

View File

@@ -74,8 +74,14 @@ button {
color: #ffffff;
}
.workspace-pill-active {
.workspace-pill-alive {
background-color: rgba(255, 255, 255, 0.153);
}
.workspace-pill-presenting {
background-color: #666666;
color: #ffffff;
/* animation: workspace-updown 1.2s ease-in-out infinite; */
}
.workspace-pill-focused {
@@ -136,35 +142,3 @@ button {
opacity: 1;
}
}
.todo-tag-area {
min-height: 40px;
padding: 5px;
}
.tag-button {
background-color: #444444;
color: #ffffff;
padding: 2px 8px;
margin: 2px;
border-radius: 12px;
font-size: 12px;
font-family: "Hack Nerd Font Mono", sans-serif;
min-height: 24px;
min-width: 50px;
}
.tag-button:hover {
background-color: #555555;
}
.tag-button.suggested-action {
background-color: #3498db;
color: #ffffff;
}
.todo-entry-box {
margin-top: 5px;
margin-bottom: 5px;
}

View File

@@ -6,9 +6,7 @@
App::App() {
this->app = Gtk::Application::create("org.example.mybar");
this->setupServices();
this->notificationService.intialize();
this->hyprlandService = HyprlandService::getInstance();
app->signal_activate().connect([&]() {
@@ -21,11 +19,15 @@ App::App() {
);
if (monitor) {
auto bar = new Bar(monitor->gobj());
auto bar = std::make_shared<Bar>(monitor->gobj());
bar->set_application(app);
bar->show();
bar->addLeftWidget(hyprlandService->getWorkspaceIndicatorsForMonitor(i));
std::string monitorName = monitor->get_connector();
bar->addLeftWidget(hyprlandService->getWorkspaceIndicatorsForMonitor(monitorName));
hyprlandService->addBar(bar, monitorName);
bars.push_back(bar);
}
}
@@ -33,16 +35,12 @@ App::App() {
app->signal_shutdown().connect([&]() {
for (auto bar : bars) {
delete bar;
}
bars.clear();
this->trayService->stop();
});
}
void App::setupServices() {
this->trayService->start();
}

View File

@@ -3,6 +3,8 @@
Popover::Popover(const std::string icon, std::string name): Button(icon) {
signal_clicked().connect(sigc::mem_fun(*this, &Popover::on_toggle_window));
set_name(name);
popover = new Gtk::Popover();
popover->set_parent(*this);
popover->set_autohide(true);

View File

@@ -1,42 +1,52 @@
#include "components/workspaceIndicator.hpp"
#include <iostream>
#include "gtkmm/button.h"
#include "gtkmm/gestureclick.h"
#include "gtkmm/label.h"
#include "gtkmm/overlay.h"
WorkspaceIndicator::WorkspaceIndicator(std::string label, sigc::slot<void()> onClick)
WorkspaceIndicator::WorkspaceIndicator(int id, std::string label, sigc::slot<void(int)> onClick)
: Gtk::Box(Gtk::Orientation::HORIZONTAL) {
auto overlay = Gtk::make_managed<Gtk::Overlay>();
auto numLabel = Gtk::make_managed<Gtk::Label>(label);
auto pillContainer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
overlay = std::make_shared<Gtk::Overlay>();
auto numLabel = Gtk::make_managed<Gtk::Label>(label);
auto pillContainer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
// auto gesture = Gtk::GestureClick::create();
// gesture->set_button(GDK_BUTTON_PRIMARY);
// gesture->signal_released().connect([this, i](int, double, double) {
// this->service->switchToWorkspace(
// i + this->monitorId * HyprlandService::kWorkspaceSlotCount);
// });
// overlay->add_controller(gesture);
overlay->add_css_class("workspace-pill");
// if (i == 6 || i == 7) {
// auto indicator = Gtk::make_managed<Gtk::Label>(i == 6 ? "🫱🏻" : "🫲🏻");
// indicator->add_css_class(i == 6 ? "workspace-pill-six" : "workspace-pill-seven");
// indicator->set_valign(Gtk::Align::END);
// overlay->set_child(*indicator);
// overlay->add_overlay(*numLabel);
// pillContainer->append(*overlay);
// } else {
overlay->set_child(*numLabel);
pillContainer->append(*overlay);
// }
append(*pillContainer);
auto gesture = Gtk::GestureClick::create();
gesture->set_button(GDK_BUTTON_PRIMARY);
gesture->signal_released().connect([this, id, onClick](int, double, double) {
onClick(
id);
});
overlay->add_controller(gesture);
overlay->add_css_class("workspace-pill");
if (id == 6 || id == 7) {
auto indicator = Gtk::make_managed<Gtk::Label>(id == 6 ? "🫱🏻" : "🫲🏻");
indicator->add_css_class(id == 6 ? "workspace-pill-six" : "workspace-pill-seven");
indicator->set_valign(Gtk::Align::END);
overlay->set_child(*indicator);
overlay->add_overlay(*numLabel);
pillContainer->append(*overlay);
} else {
overlay->set_child(*numLabel);
pillContainer->append(*overlay);
}
append(*pillContainer);
}
void WorkspaceIndicator::setIndicatorState(InidicatorState state) {
this->clearCssClass();
this->currentState = state;
auto cssClass = this->stateToCssClass[state];
this->overlay->add_css_class(cssClass);
}
void WorkspaceIndicator::clearCssClass() {
for (const auto &[_, cssClass] : stateToCssClass) {
this->overlay->remove_css_class(cssClass);
}
}

View File

@@ -1,237 +0,0 @@
#include "services/bluetooth.hpp"
#include <cassert>
#include <iostream>
#include <string>
#include <vector>
#include "glib.h"
BluetoothService::BluetoothService() {
GError *error = nullptr;
this->adapter_proxy = g_dbus_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
nullptr,
"org.bluez",
"/org/bluez/hci0",
"org.bluez.Adapter1",
nullptr,
&error);
if (error) {
std::cerr << "Error creating Bluetooth adapter proxy: "
<< error->message << std::endl;
g_error_free(error);
assert(false);
}
this->powerState = this->getPowerState();
this->isDiscovering = this->getIsDiscovering();
g_signal_connect(
this->adapter_proxy,
"g-properties-changed",
G_CALLBACK(BluetoothService::onPropertyChangedStatic),
this);
}
bool BluetoothService::getPowerState() {
GVariant *result = g_dbus_proxy_get_cached_property(
this->adapter_proxy,
"Powered");
if (!result) {
std::cerr << "Error getting Powered property." << std::endl;
return false;
}
gboolean powered;
g_variant_get(result, "b", &powered);
g_variant_unref(result);
return powered;
}
bool BluetoothService::getIsDiscovering() {
GVariant *result = g_dbus_proxy_get_cached_property(
this->adapter_proxy,
"Discovering");
if (!result) {
std::cerr << "Error getting Discovering property." << std::endl;
return false;
}
gboolean discovering;
g_variant_get(result, "b", &discovering);
g_variant_unref(result);
return discovering;
}
void BluetoothService::togglePowerState() {
GError *error = nullptr;
bool state = !this->powerState;
GDBusConnection *connection = g_dbus_proxy_get_connection(this->adapter_proxy);
GVariant *reply = g_dbus_connection_call_sync(
connection,
"org.bluez",
"/org/bluez/hci0",
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new(
"(ssv)",
"org.bluez.Adapter1",
"Powered",
g_variant_new_boolean(state)),
nullptr,
G_DBUS_CALL_FLAGS_NONE,
-1,
nullptr,
&error);
if (error) {
std::cerr << "Error setting Powered property: "
<< error->message << std::endl;
g_error_free(error);
}
if (reply) {
g_variant_unref(reply);
}
if (!error) {
this->powerState = state;
}
}
void BluetoothService::toggleIsDiscovering() {
bool newState = !this->isDiscovering;
GError *error = nullptr;
const char *method = newState ? "StartDiscovery" : "StopDiscovery";
GVariant *reply = g_dbus_proxy_call_sync(
this->adapter_proxy,
method,
nullptr,
G_DBUS_CALL_FLAGS_NONE,
-1,
nullptr,
&error);
if (error) {
std::cerr << "Error calling " << method << ": "
<< error->message << std::endl;
g_error_free(error);
if (reply) {
g_variant_unref(reply);
}
return;
}
this->isDiscovering = newState;
if (reply) {
g_variant_unref(reply);
}
}
void BluetoothService::onPropertyChanged(GDBusProxy *proxy,
GVariant *changed_properties,
const gchar *const *invalidated_properties,
gpointer user_data) {
gboolean is_powered = FALSE;
gboolean is_discovering = FALSE;
if (g_variant_lookup(changed_properties, "Powered", "b", &is_powered)) {
if (!is_powered) {
this->isDiscovering = is_discovering;
isDiscoveringChangedSignal.emit(isDiscovering);
}
this->powerState = is_powered;
powerStateChangedSignal.emit(powerState);
}
if (g_variant_lookup(changed_properties, "Discovering", "b", &is_discovering)) {
this->isDiscovering = is_discovering;
isDiscoveringChangedSignal.emit(isDiscovering);
getDeviceObjectPaths();
}
}
void BluetoothService::onPropertyChangedStatic(GDBusProxy *proxy,
GVariant *changed_properties,
const gchar *const *invalidated_properties,
gpointer user_data) {
BluetoothService *service = static_cast<BluetoothService *>(user_data);
if (service) {
service->onPropertyChanged(proxy, changed_properties, invalidated_properties, user_data);
} else {
std::cerr << "Error: BluetoothService instance is null in static callback." << std::endl;
assert(false);
}
}
std::vector<std::string> BluetoothService::getDeviceObjectPaths() {
std::vector<std::string> device_paths;
GError *error = nullptr;
GDBusConnection *connection = g_dbus_proxy_get_connection(this->adapter_proxy);
GVariant *reply = g_dbus_connection_call_sync(
connection,
"org.bluez",
"/",
"org.freedesktop.DBus.ObjectManager",
"GetManagedObjects",
nullptr,
G_VARIANT_TYPE("(a{oa{sa{sv}}})"),
G_DBUS_CALL_FLAGS_NONE,
-1,
nullptr,
&error);
if (error) {
std::cerr << "Error calling GetManagedObjects: " << error->message << std::endl;
g_error_free(error);
return device_paths;
}
if (!reply) {
return device_paths;
}
GVariant *objects = g_variant_get_child_value(reply, 0);
g_variant_unref(reply);
if (!objects) {
return device_paths;
}
GVariantIter iter;
g_variant_iter_init(&iter, objects);
const gchar *object_path = nullptr;
GVariant *interfaces = nullptr;
while (g_variant_iter_next(&iter, "{&o@a{sa{sv}}}", &object_path, &interfaces)) {
GVariant *device_props = nullptr;
if (g_variant_lookup(interfaces, "org.bluez.Device1", "@a{sv}", &device_props)) {
device_paths.emplace_back(object_path);
g_variant_unref(device_props);
}
g_variant_unref(interfaces);
interfaces = nullptr;
}
g_variant_unref(objects);
return device_paths;
}

View File

@@ -13,34 +13,38 @@
#include <unistd.h>
#include "helpers/hypr.hpp"
#include "helpers/socket.hpp"
#include "helpers/string.hpp"
#include "gtkmm/box.h"
#include "gtkmm/button.h"
HyprlandService::HyprlandService() {
auto monitorDataJson = HyprctlHelper::getMonitorData();
init();
bindSocket();
}
void HyprlandService::init() {
auto monitorDataJson = HyprctlHelper::getMonitorData();
auto onClick = sigc::mem_fun(*this, &HyprlandService::switchToWorkspace);
for (const auto &item : monitorDataJson) {
auto monitorPtr = std::make_shared<Monitor>();
int monitorId = item["id"].get<int>();
std::string monitorName = item["name"].get<std::string>();
int monitorId = item["id"].get<int>();
monitorPtr->id = monitorId;
monitorPtr->name = item["name"].get<std::string>();
this->monitors[monitorPtr->id] = monitorPtr;
monitorPtr->name = monitorName;
monitorPtr->activeWorkspaceId = item["activeWorkspace"]["id"].get<int>();
monitorPtr->focused = item["focused"].get<bool>();
this->monitors[monitorPtr->name] = monitorPtr;
// Create inidcators for hyprsplit
for (int i = 1; i <= NUM_WORKSPACES; i++) {
WorkspaceState state;
int workspaceId = i + (NUM_WORKSPACES * monitorId);
state.id = workspaceId;
state.monitorId = monitorId;
auto view = std::make_shared<WorkspaceIndicator>(std::to_string(i));
std::shared_ptr<WorkspaceData> state = std::make_shared<WorkspaceData>();
int workspaceId = i + (NUM_WORKSPACES * monitorId);
state->id = workspaceId;
state->monitorName = monitorName;
auto view = std::make_shared<WorkspaceIndicator>(workspaceId, std::to_string(i), onClick);
auto workSpace = std::make_shared<Workspace>();
workSpace->state = state;
workSpace->view = view;
@@ -50,30 +54,38 @@ HyprlandService::HyprlandService() {
}
}
auto clientsDataJson = HyprctlHelper::getClientData();
for (const auto &client : clientsDataJson) {
auto clientPtr = std::make_shared<Client>();
clientPtr->address = client["address"].get<std::string>();
clientPtr->workspaceId = client["workspace"]["id"].get<int>();
clientPtr->title = client["title"].get<std::string>();
this->clients[clientPtr->address] = clientPtr;
auto workspacePtr = workspaces[clientPtr->workspaceId];
workspacePtr->state->clients[clientPtr->address] = clientPtr;
if (client.contains("urgent") && client["urgent"].get<bool>()) {
workspacePtr->state->urgentClients.insert(clientPtr->address);
}
}
auto workspaceDataJson = HyprctlHelper::getWorkspaceData();
for (const auto &workspace : workspaceDataJson) {
auto workspacePtr = std::make_shared<Workspace>();
workspacePtr->state.id = workspace["id"].get<int>();
workspacePtr->state.monitorId = workspace["monitorID"].get<int>();
workspaces[workspacePtr->state.id] = workspacePtr;
auto workspacePtr = workspaces[workspace["id"].get<int>()];
auto state = workspacePtr->state;
state->id = workspace["id"].get<int>();
state->monitorName = workspace["monitor"].get<std::string>();
refreshIndicator(workspacePtr);
}
bindSocket();
}
void HyprlandService::init() {
}
std::string HyprlandService::getSocketPath() {
auto sig = std::string(std::getenv("HYPRLAND_INSTANCE_SIGNATURE"));
auto runtime = std::string(std::getenv("XDG_RUNTIME_DIR"));
return std::string(runtime) + "/hypr/" + sig + "/.socket2.sock";
}
void HyprlandService::bindSocket() {
std::string socketPath = getSocketPath();
std::string socketPath = HyprSocketHelper::getHyprlandSocketPath();
socketFd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketFd == -1) {
@@ -96,29 +108,220 @@ void HyprlandService::bindSocket() {
GSource *source = g_unix_fd_source_new(socketFd, static_cast<GIOCondition>(G_IO_IN | G_IO_HUP | G_IO_ERR));
auto onSocketEvent = [](gint fd, GIOCondition condition, gpointer user_data) -> gboolean {
auto onSocketEvent = [](gint fd, GIOCondition , gpointer user_data) -> gboolean {
HyprlandService *self = static_cast<HyprlandService *>(user_data);
auto messages = SocketHelper::parseSocketMessage(fd, ">>");
for (const auto &message : messages) {
self->handleSocketMessage(message);
}
return G_SOURCE_CONTINUE;
};
g_source_set_callback(source, (GSourceFunc)(+onSocketEvent), this, nullptr);
g_source_set_callback(source, reinterpret_cast<GSourceFunc>(reinterpret_cast<void*>(+onSocketEvent)), this, nullptr);
g_source_attach(source, g_main_context_default());
g_source_unref(source);
}
std::shared_ptr<Gtk::Box> HyprlandService::getWorkspaceIndicatorsForMonitor(int monitorId) {
auto box = std::make_shared<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
void HyprlandService::onWorkspaceChanged(int workspaceId) {
auto newActive = workspaces[workspaceId];
auto state = newActive->state;
auto monitorPtr = monitors[state->monitorName];
auto monitor = monitors[monitorId];
int oldActiveWorkspaceId = monitorPtr->activeWorkspaceId;
auto oldActive = workspaces[oldActiveWorkspaceId];
int i = 0;
for (const auto &[wsId, wsPair] : monitor->monitorWorkspaces) {
box->append((Gtk::Box &)*wsPair->view);
i++;
monitorPtr->activeWorkspaceId = workspaceId;
refreshIndicator(newActive);
refreshIndicator(oldActive);
}
void HyprlandService::onFocusedMonitorChanged(std::string monitorData) {
std::string monitorName = StringHelper::split(monitorData, ',')[0];
for (const auto &[_, monitorPtr] : monitors) {
std::string itMonitorName = monitorPtr->name;
if (itMonitorName == monitorName) {
monitorPtr->focused = true;
int activeWorkspaceId = monitorPtr->activeWorkspaceId;
auto activeWorkspace = workspaces[activeWorkspaceId];
refreshIndicator(activeWorkspace);
} else {
monitorPtr->focused = false;
int activeWorkspaceId = monitorPtr->activeWorkspaceId;
auto activeWorkspace = workspaces[activeWorkspaceId];
refreshIndicator(activeWorkspace);
}
}
}
void HyprlandService::onMonitorRemoved(std::string monitorName) {
auto monitorPtr = this->monitors[monitorName];
for (const auto &[wsId, wsPtr] : monitorPtr->monitorWorkspaces) {
this->workspaces.erase(wsId);
}
monitorPtr->monitorWorkspaces.clear();
monitorPtr->bar->close();
this->monitors.erase(monitorName);
}
// void HyprlandService::onMonitorAdded(std::string monitorName) {
// // this->signalMonitorAdded.emit();
// }
void HyprlandService::onOpenWindow(std::string windowData) {
auto parts = StringHelper::split(windowData, ',');
std::string addr = "0x" + parts[0];
int workspaceId = std::stoi(parts[1]);
std::string title = parts[2];
auto clientPtr = std::make_shared<Client>();
clientPtr->address = addr;
clientPtr->workspaceId = workspaceId;
clientPtr->title = title;
this->clients[clientPtr->address] = clientPtr;
auto workspacePtr = workspaces[clientPtr->workspaceId];
workspacePtr->state->clients[clientPtr->address] = clientPtr;
refreshIndicator(workspacePtr);
}
void HyprlandService::onCloseWindow(std::string windowData) {
auto parts = StringHelper::split(windowData, ',');
std::string addr = "0x" + parts[0];
if (this->clients.find(addr) == this->clients.end()) {
return;
}
auto clientPtr = this->clients[addr];
int workspaceId = clientPtr->workspaceId;
auto workspacePtr = workspaces[workspaceId];
workspacePtr->state->clients.erase(addr);
this->clients.erase(addr);
refreshIndicator(workspacePtr);
}
void HyprlandService::handleSocketMessage(SocketHelper::SocketMessage message) {
if (socketEventTypeMap.find(message.eventType) == socketEventTypeMap.end()) {
return;
}
SocketEventType eventType = socketEventTypeMap[message.eventType];
std::string eventData = message.eventData;
switch (eventType) {
case FOCUSED_MONITOR: {
onFocusedMonitorChanged(eventData);
break;
}
case WORKSPACE_CHANGED: {
this->onWorkspaceChanged(std::stoi(eventData));
break;
}
case OPEN_WINDOW: {
this->onOpenWindow(eventData);
break;
}
case CLOSE_WINDOW: {
this->onCloseWindow(eventData);
break;
}
case URGENT: {
this->onUrgent(eventData);
break;
}
case ACTIVE_WINDOW: {
this->onActiveWindowChanged(eventData);
break;
}
case MONITOR_REMOVED: {
this->onMonitorRemoved(eventData);
break;
}
}
}
void HyprlandService::onUrgent(std::string windowAddress) {
std::string addr = "0x" + windowAddress;
if (this->clients.find(addr) == this->clients.end()) {
return;
}
auto clientPtr = this->clients[addr];
int workspaceId = clientPtr->workspaceId;
auto workspacePtr = workspaces[workspaceId];
workspacePtr->state->urgentClients.insert(addr);
refreshIndicator(workspacePtr);
}
void HyprlandService::onActiveWindowChanged(std::string windowAddress) {
std::string addr = "0x" + windowAddress;
if (this->clients.find(addr) == this->clients.end()) {
return;
}
auto clientPtr = this->clients[addr];
int workspaceId = clientPtr->workspaceId;
auto workspacePtr = workspaces[workspaceId];
workspacePtr->state->urgentClients.erase(addr);
refreshIndicator(workspacePtr);
}
std::shared_ptr<Gtk::Box> HyprlandService::getWorkspaceIndicatorsForMonitor(std::string monitorName) {
auto box = std::make_shared<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
auto monitor = monitors[monitorName];
for (const auto &[wsId, wsPair] : monitor->monitorWorkspaces) {
box->append((Gtk::Box &)*wsPair->view);
}
return box;
}
void HyprlandService::switchToWorkspace(int workspaceId) {
HyprctlHelper::dispatchWorkspace(workspaceId);
}
void HyprlandService::refreshIndicator(std::shared_ptr<Workspace> workspace) {
auto view = workspace->view;
auto state = workspace->state;
auto monitorsPtr = monitors[state->monitorName];
bool isUrgent = !state->urgentClients.empty();
bool isEmpty = state->clients.empty();
bool isPreseneting = state->id == monitorsPtr->activeWorkspaceId;
bool isFocused = monitorsPtr->focused && isPreseneting;
if (isUrgent) {
view->setIndicatorState(WorkspaceIndicator::URGENT);
} else if (isFocused) {
view->setIndicatorState(WorkspaceIndicator::FOCUSED);
} else if (isPreseneting) {
view->setIndicatorState(WorkspaceIndicator::PRESENTING);
} else if (!isEmpty) {
view->setIndicatorState(WorkspaceIndicator::ALIVE);
} else {
view->setIndicatorState(WorkspaceIndicator::EMPTY);
}
}
void HyprlandService::addBar(std::shared_ptr<Bar> bar, std::string monitorName) {
this->monitors[monitorName]->bar = bar;
}

View File

@@ -1,305 +0,0 @@
#include "services/notifications.hpp"
#include <gio/gio.h>
#include <iostream>
static constexpr const char *kNotificationsObjectPath = "/org/freedesktop/Notifications";
static constexpr const char *kNotificationsInterface = "org.freedesktop.Notifications";
static const char *kNotificationsIntrospectionXml = R"XML(
<node>
<interface name="org.freedesktop.Notifications">
<method name="Notify">
<arg type="s" direction="in"/>
<arg type="u" direction="in"/>
<arg type="s" direction="in"/>
<arg type="s" direction="in"/>
<arg type="s" direction="in"/>
<arg type="as" direction="in"/>
<arg type="a{sv}" direction="in"/>
<arg type="i" direction="in"/>
<arg type="u" direction="out"/>
</method>
<method name="CloseNotification">
<arg type="u" direction="in"/>
</method>
<method name="GetCapabilities">
<arg type="as" direction="out"/>
</method>
<method name="GetServerInformation">
<arg type="s" direction="out"/>
<arg type="s" direction="out"/>
<arg type="s" direction="out"/>
<arg type="s" direction="out"/>
</method>
<signal name="NotificationClosed">
<arg type="u"/>
<arg type="u"/>
</signal>
<signal name="ActionInvoked">
<arg type="u"/>
<arg type="s"/>
</signal>
<signal name="ActivationToken">
<arg type="u"/>
<arg type="s"/>
</signal>
</interface>
</node>)XML";
static void on_method_call(GDBusConnection * /*connection*/,
const gchar * /*sender*/,
const gchar * /*object_path*/,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data) {
auto *self = static_cast<NotificationService *>(user_data);
if (g_strcmp0(interface_name, kNotificationsInterface) != 0) {
g_dbus_method_invocation_return_dbus_error(
invocation,
"org.freedesktop.DBus.Error.UnknownInterface",
"Unknown interface");
return;
}
if (g_strcmp0(method_name, "Notify") == 0) {
const gchar *app_name = "";
guint32 replaces_id = 0;
const gchar *app_icon = "";
const gchar *summary = "";
const gchar *body = "";
GVariant *actions = nullptr;
GVariant *hints = nullptr;
gint32 expire_timeout = -1;
g_variant_get(parameters, "(&su&s&s&s@as@a{sv}i)",
&app_name,
&replaces_id,
&app_icon,
&summary,
&body,
&actions,
&hints,
&expire_timeout);
std::cout << "--- Notification ---" << std::endl;
std::cout << "App: " << (app_name ? app_name : "") << std::endl;
std::cout << "Title: " << (summary ? summary : "") << std::endl;
std::cout << "Body: " << (body ? body : "") << std::endl;
if (actions)
g_variant_unref(actions);
if (hints)
g_variant_unref(hints);
guint32 id = self->allocateNotificationId(replaces_id);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", id));
return;
}
if (g_strcmp0(method_name, "GetCapabilities") == 0) {
// Advertise common capabilities so clients don't disable notifications.
// (Many apps probe this first and may skip Notify if it's empty.)
const gchar *caps[] = {
"body",
"actions",
"body-markup",
"icon-static",
"persistence",
nullptr};
GVariant *capsV = g_variant_new_strv(caps, -1);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(@as)", capsV));
return;
}
if (g_strcmp0(method_name, "GetServerInformation") == 0) {
g_dbus_method_invocation_return_value(
invocation,
g_variant_new("(ssss)", "bar", "bar", "0.1", "1.2"));
return;
}
if (g_strcmp0(method_name, "CloseNotification") == 0) {
guint32 id = 0;
g_variant_get(parameters, "(u)", &id);
// reason: 3 = closed by call to CloseNotification
if (self && self->getConnection()) {
g_dbus_connection_emit_signal(
self->getConnection(),
nullptr,
kNotificationsObjectPath,
kNotificationsInterface,
"NotificationClosed",
g_variant_new("(uu)", id, 3u),
nullptr);
}
g_dbus_method_invocation_return_value(invocation, nullptr);
return;
}
g_dbus_method_invocation_return_dbus_error(
invocation,
"org.freedesktop.DBus.Error.UnknownMethod",
"Unknown method");
}
guint32 NotificationService::allocateNotificationId(guint32 replacesId) {
if (replacesId != 0)
return replacesId;
return this->nextNotificationId++;
}
static const GDBusInterfaceVTable kVTable = {
.method_call = on_method_call,
.get_property = nullptr,
.set_property = nullptr,
};
NotificationService::~NotificationService() {
if (this->connection) {
// Best-effort release of the well-known name.
{
GError *error = nullptr;
GVariant *releaseResult = g_dbus_connection_call_sync(
this->connection,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"ReleaseName",
g_variant_new("(s)", "org.freedesktop.Notifications"),
G_VARIANT_TYPE("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
nullptr,
&error);
if (releaseResult)
g_variant_unref(releaseResult);
if (error)
g_error_free(error);
}
if (this->registrationId != 0) {
g_dbus_connection_unregister_object(this->connection, this->registrationId);
this->registrationId = 0;
}
g_object_unref(this->connection);
this->connection = nullptr;
}
if (this->nodeInfo) {
g_dbus_node_info_unref(this->nodeInfo);
this->nodeInfo = nullptr;
}
}
void NotificationService::intialize() {
GError *error = nullptr;
if (this->connection) {
return;
}
gchar *address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, nullptr, &error);
if (!address) {
std::cerr << "Failed to get session bus address: " << (error ? error->message : "unknown error") << std::endl;
if (error)
g_error_free(error);
return;
}
if (error) {
g_error_free(error);
error = nullptr;
}
this->connection = g_dbus_connection_new_for_address_sync(
address,
static_cast<GDBusConnectionFlags>(
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION),
nullptr,
nullptr,
&error);
g_free(address);
if (!this->connection) {
std::cerr << "Failed to connect to session bus: " << (error ? error->message : "unknown error") << std::endl;
if (error)
g_error_free(error);
return;
}
if (error) {
g_error_free(error);
error = nullptr;
}
this->nodeInfo = g_dbus_node_info_new_for_xml(kNotificationsIntrospectionXml, &error);
if (!this->nodeInfo) {
std::cerr << "Failed to create introspection data: " << (error ? error->message : "unknown error") << std::endl;
if (error)
g_error_free(error);
return;
}
GDBusInterfaceInfo *iface = g_dbus_node_info_lookup_interface(this->nodeInfo, kNotificationsInterface);
if (!iface) {
std::cerr << "Missing interface info for org.freedesktop.Notifications" << std::endl;
return;
}
this->registrationId = g_dbus_connection_register_object(
this->connection,
kNotificationsObjectPath,
iface,
&kVTable,
this,
nullptr,
&error);
if (this->registrationId == 0) {
std::cerr << "Failed to register notifications object: " << (error ? error->message : "unknown error") << std::endl;
if (error)
g_error_free(error);
return;
}
// Request the well-known name synchronously so we can detect conflicts.
// Reply codes: 1=PRIMARY_OWNER, 2=IN_QUEUE, 3=EXISTS, 4=ALREADY_OWNER
GVariant *requestResult = g_dbus_connection_call_sync(
this->connection,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"RequestName",
g_variant_new("(su)", "org.freedesktop.Notifications", 0u),
G_VARIANT_TYPE("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
nullptr,
&error);
if (!requestResult) {
std::cerr << "Failed to RequestName(org.freedesktop.Notifications): "
<< (error ? error->message : "unknown error") << std::endl;
if (error)
g_error_free(error);
return;
}
guint32 reply = 0;
g_variant_get(requestResult, "(u)", &reply);
g_variant_unref(requestResult);
if (reply != 1u && reply != 4u) {
std::cerr << "org.freedesktop.Notifications is already owned (RequestName reply=" << reply
<< "). Stop your existing notification daemon (e.g. dunst/mako/swaync) or allow replacement." << std::endl;
return;
}
}

View File

@@ -1,72 +0,0 @@
#include "widgets/bluetooth.hpp"
#include "gtkmm/label.h"
BluetoothWidget::BluetoothWidget() : Gtk::Box() {
this->set_orientation(Gtk::Orientation::VERTICAL);
this->add_css_class("bluetooth-popover-container");
this->statusArea.add_css_class("bluetooth-status-area");
this->statusArea.set_hexpand(true);
this->statusArea.set_halign(Gtk::Align::FILL);
this->append(this->statusArea);
this->powerButton = Gtk::make_managed<Button>("\ue1a8");
this->powerButton->set_tooltip_text("Turn Bluetooth Off");
this->powerButton->signal_clicked().connect(sigc::mem_fun(*this, &BluetoothWidget::onPowerButtonClicked));
this->powerButton->add_css_class("toggle-button");
this->scanButton = Gtk::make_managed<Button>("\ue1aa");
this->scanButton->set_tooltip_text("Scan for Devices");
this->scanButton->add_css_class("toggle-button");
this->scanButton->signal_clicked().connect(sigc::mem_fun(*this, &BluetoothWidget::onScanButtonClicked));
this->statusArea.append(*this->powerButton);
this->statusArea.append(*this->scanButton);
}
void BluetoothWidget::onPowerButtonClicked() {
onPowerStateButtonClickedSignal.emit();
}
void BluetoothWidget::onScanButtonClicked() {
onIsDiscoveringButtonClickedSignal.emit();
}
void BluetoothWidget::toggleButton(Button *button, bool state) {
if (state) {
button->add_css_class("toggle-button-on");
button->remove_css_class("toggle-button-off");
} else {
button->add_css_class("toggle-button-off");
button->remove_css_class("toggle-button-on");
}
}
void BluetoothWidget::setPowerState(bool state) {
this->isPowered = state;
this->scanButton->set_sensitive(state);
if (!state) {
this->scanButton->add_css_class("toggle-button-disabled");
// this->add_css_class("disabled-popover-icon");
this->setIsDiscovering(false);
} else {
this->scanButton->remove_css_class("toggle-button-disabled");
// this->remove_css_class("disabled-popover-icon");
}
this->toggleButton(this->powerButton, state);
}
void BluetoothWidget::setIsDiscovering(bool state) {
this->isDiscovering = state;
this->toggleButton(this->scanButton, state);
}
void BluetoothWidget::update() {
setPowerState(isPowered);
setIsDiscovering(isDiscovering);
}

View File

@@ -5,20 +5,4 @@ ControlCenter::ControlCenter(std::string icon, std::string name)
this->popover->set_size_request(200, -1);
set_popover_child(this->container);
this->bluetoothWidget = Gtk::make_managed<BluetoothWidget>();
this->container.append(*this->bluetoothWidget);
bluetoothService->powerStateChangedSignal.connect(
sigc::mem_fun(*this->bluetoothWidget, &BluetoothWidget::setPowerState));
bluetoothWidget->onPowerStateButtonClickedSignal.connect(
sigc::mem_fun(*this->bluetoothService, &BluetoothService::togglePowerState));
bluetoothWidget->setPowerState(bluetoothService->getPowerState());
bluetoothService->isDiscoveringChangedSignal.connect(
sigc::mem_fun(*this->bluetoothWidget, &BluetoothWidget::setIsDiscovering));
bluetoothWidget->onIsDiscoveringButtonClickedSignal.connect(
sigc::mem_fun(*this->bluetoothService, &BluetoothService::toggleIsDiscovering));
bluetoothWidget->setIsDiscovering(bluetoothService->getIsDiscovering());
}