fix bar crashing on monitor add/remove

This commit is contained in:
2026-02-09 13:49:52 +01:00
parent a90d1c2f6c
commit e1217305a5
30 changed files with 1186 additions and 247 deletions

View File

@@ -1,31 +1,36 @@
#pragma once
#include <vector>
#include <memory>
#include "bar/bar.hpp"
#include "connection/dbus/notification.hpp"
#include "connection/dbus/tray.hpp"
#include "services/hyprland.hpp"
#include "widgets/wallpaperWindow.hpp"
#include "services/notificationController.hpp"
#include "gdkmm/monitor.h"
#include "glibmm/refptr.h"
#include "gtkmm/application.h"
class MprisController;
class App {
public:
struct BarMonitorPair {
std::shared_ptr<Bar> bar;
std::shared_ptr<Gdk::Monitor> window;
};
App();
int run();
private:
Glib::RefPtr<Gtk::Application> app;
std::vector<std::shared_ptr<Bar>> bars;
std::vector<std::shared_ptr<WallpaperWindow>> wallpaperWindows;
std::shared_ptr<NotificationService> notificationService = nullptr;
std::shared_ptr<MprisController> mprisController = nullptr;
HyprlandService *hyprlandService = nullptr;
std::map<std::string, BarMonitorPair> bars;
std::shared_ptr<NotificationService> notificationService = NotificationService::getInstance();
std::shared_ptr<NotificationController> notificationController = NotificationController::getInstance();
std::shared_ptr<MprisController> mprisController = MprisController::getInstance();
std::shared_ptr<HyprlandService> hyprlandService = HyprlandService::getInstance();
TrayService *trayService = TrayService::getInstance();
void setupServices();

View File

@@ -7,6 +7,6 @@
class IconButton : public TextButton {
public:
IconButton(Icon::Type icon, std::string fontFamilyCss = "materia-icons");
IconButton(Icon::Type icon, std::string fontFamilyCss = "material-icons");
void setIcon(Icon::Type icon);
};

View File

@@ -22,6 +22,10 @@ class Icon {
CONTENT_COPY,
TOKEN,
SETTINGS,
POWER_SETTINGS_NEW,
BLUETOOTH_SEARCHING,
};
static const std::string toString(Type type) {
@@ -46,5 +50,9 @@ class Icon {
{CONTENT_COPY, "\ue14d"},
{TOKEN, "\uea25"},
{SETTINGS, "\ue8b8"},
{POWER_SETTINGS_NEW, "\ue8ac"},
{BLUETOOTH_SEARCHING, "\ue1aa"},
};
};

View File

@@ -0,0 +1,111 @@
#pragma once
#include <cstdint>
#include <giomm.h>
#include <map>
#include <memory>
#include <sigc++/sigc++.h>
#include <string>
#include <vector>
#include "connection/dbus/dbus.hpp"
struct BluetoothDevice {
std::string object_path;
std::string address;
std::string name;
std::string icon;
bool paired = false;
bool connected = false;
bool trusted = false;
int16_t rssi = 0;
};
class BluetoothController : public DbusConnection {
using PropertiesMap = std::map<Glib::ustring, Glib::VariantBase>;
public:
static std::shared_ptr<BluetoothController> getInstance() {
if (!instance) {
instance = std::shared_ptr<BluetoothController>(new BluetoothController());
}
return instance;
}
// Adapter control
void setPowered(bool powered);
bool isPowered() const;
// Discovery
void startDiscovery();
void stopDiscovery();
bool isDiscovering() const;
// Device actions (identified by object_path)
void pairDevice(const std::string &object_path);
void unpairDevice(const std::string &object_path);
void connectDevice(const std::string &object_path);
void disconnectDevice(const std::string &object_path);
void trustDevice(const std::string &object_path, bool trusted);
// Queries
std::vector<BluetoothDevice> getDevices() const;
std::vector<BluetoothDevice> getPairedDevices() const;
// Signals
sigc::signal<void(bool)> &signalPoweredChanged();
sigc::signal<void(bool)> &signalDiscoveringChanged();
sigc::signal<void(const BluetoothDevice &)> &signalDeviceAdded();
sigc::signal<void(const std::string &)> &signalDeviceRemoved();
sigc::signal<void(const BluetoothDevice &)> &signalDeviceChanged();
private:
BluetoothController();
inline static std::shared_ptr<BluetoothController> instance = nullptr;
bool m_powered = false;
bool m_discovering = false;
std::string m_adapter_path;
Glib::RefPtr<Gio::DBus::Proxy> m_adapter_proxy;
Glib::RefPtr<Gio::DBus::Proxy> m_object_manager_proxy;
std::map<std::string, BluetoothDevice> m_devices;
std::map<std::string, Glib::RefPtr<Gio::DBus::Proxy>> m_device_proxies;
sigc::signal<void(bool)> m_powered_signal;
sigc::signal<void(bool)> m_discovering_signal;
sigc::signal<void(const BluetoothDevice &)> m_device_added_signal;
sigc::signal<void(const std::string &)> m_device_removed_signal;
sigc::signal<void(const BluetoothDevice &)> m_device_changed_signal;
// Bus setup
void onBusConnected(const Glib::RefPtr<Gio::AsyncResult> &result);
void enumerateObjects();
void parseInterfaces(const std::string &object_path, const Glib::VariantBase &interfaces_var);
// ObjectManager signals
void onObjectManagerSignal(const Glib::ustring &sender_name,
const Glib::ustring &signal_name,
const Glib::VariantContainerBase &parameters);
// Adapter
void setupAdapter(const std::string &path, const PropertiesMap &properties);
void onAdapterPropertiesChanged(const Gio::DBus::Proxy::MapChangedProperties &changed,
const std::vector<Glib::ustring> &invalidated);
// Devices
void addDevice(const std::string &path, const PropertiesMap &properties);
void removeDevice(const std::string &path);
void onDevicePropertiesChanged(const std::string &object_path,
const Gio::DBus::Proxy::MapChangedProperties &changed,
const std::vector<Glib::ustring> &invalidated);
// Helpers
static BluetoothDevice parseDeviceProperties(const std::string &path, const PropertiesMap &properties);
void setDbusProperty(const std::string &object_path,
const std::string &interface,
const std::string &property,
const Glib::VariantBase &value);
};

View File

@@ -14,6 +14,10 @@ class DbusConnection {
Gio::DBus::Connection::get(Gio::DBus::BusType::SESSION, callback);
}
void connect_system_async(const sigc::slot<void(const Glib::RefPtr<Gio::AsyncResult> &)> &callback) {
Gio::DBus::Connection::get(Gio::DBus::BusType::SYSTEM, callback);
}
static void ensure_gio_init() {
try {
Gio::init();

View File

@@ -4,6 +4,7 @@
#include <giomm.h>
#include <gtkmm.h>
#include <sigc++/sigc++.h>
#include <sys/stat.h>
#include "connection/dbus/dbus.hpp"
@@ -43,7 +44,17 @@ const Glib::ustring introspection_xml = R"(
class NotificationService : public DbusConnection {
public:
NotificationService() : notificationIdCounter(1) {
static std::shared_ptr<NotificationService> getInstance() {
if (NotificationService::instance == nullptr) {
NotificationService::instance = std::shared_ptr<NotificationService>(new NotificationService());
}
return NotificationService::instance;
}
void onBusAcquired(const Glib::RefPtr<Gio::DBus::Connection> &connection, const Glib::ustring &name);
private:
NotificationService() {
Gio::DBus::own_name(
Gio::DBus::BusType::SESSION,
"org.freedesktop.Notifications",
@@ -53,10 +64,9 @@ class NotificationService : public DbusConnection {
Gio::DBus::BusNameOwnerFlags::REPLACE);
}
void onBusAcquired(const Glib::RefPtr<Gio::DBus::Connection> &connection, const Glib::ustring &name);
static inline std::shared_ptr<NotificationService> instance = nullptr;
private:
guint notificationIdCounter;
guint notificationIdCounter = 0;
const Gio::DBus::InterfaceVTable &getMessageInterfaceVTable();
void on_method_call(const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,

View File

@@ -1,49 +0,0 @@
#pragma once
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <string>
#include <vector>
#include "sigc++/signal.h"
class BluetoothService {
inline static BluetoothService *instance = nullptr;
public:
sigc::signal<void(bool)> powerStateChangedSignal;
sigc::signal<void(bool)> isDiscoveringChangedSignal;
bool getPowerState();
bool getIsDiscovering();
void togglePowerState();
void toggleIsDiscovering();
static BluetoothService *getInstance() {
if (BluetoothService::instance == nullptr) {
BluetoothService::instance = new BluetoothService();
}
return BluetoothService::instance;
}
private:
BluetoothService();
GDBusProxy *adapter_proxy = nullptr;
std::vector<std::string> getDeviceObjectPaths();
bool powerState = false;
bool isDiscovering = false;
void onPropertyChanged(GDBusProxy *proxy,
GVariant *changed_properties,
const gchar *const *invalidated_properties,
gpointer user_data);
static void onPropertyChangedStatic(GDBusProxy *proxy,
GVariant *changed_properties,
const gchar *const *invalidated_properties,
gpointer user_data);
};

View File

@@ -17,7 +17,8 @@
#define NUM_WORKSPACES 7
class HyprlandService {
inline static HyprlandService *instance = nullptr;
static inline std::shared_ptr<HyprlandService> instance;
public:
struct Client {
@@ -49,9 +50,9 @@ class HyprlandService {
std::shared_ptr<Bar> bar;
};
static HyprlandService *getInstance() {
static std::shared_ptr<HyprlandService> getInstance() {
if (!instance) {
instance = new HyprlandService();
instance = std::shared_ptr<HyprlandService>(new HyprlandService());
}
return instance;
}
@@ -60,7 +61,12 @@ class HyprlandService {
void addBar(std::shared_ptr<Bar> bar, std::string monitorName);
sigc::signal<void(std::string)> &signal_monitor_added() { return m_signal_monitor_added; }
sigc::signal<void(std::string)> &signal_monitor_removed() { return m_signal_monitor_removed; }
private:
HyprlandService();
enum SocketEventType {
WORKSPACE_CHANGED,
@@ -72,6 +78,7 @@ class HyprlandService {
FOCUSED_MONITOR,
MONITOR_REMOVED,
MONITOR_ADDED,
};
std::map<std::string, SocketEventType> socketEventTypeMap = {
@@ -83,7 +90,9 @@ class HyprlandService {
{"urgent", URGENT},
{"focusedmon", FOCUSED_MONITOR},
{"monitorremoved", MONITOR_REMOVED},
{"monitoradded", MONITOR_ADDED},
};
void onWorkspaceChanged(int workspaceId);
void onFocusedMonitorChanged(std::string monitorData);
void onOpenWindow(std::string windowData);
@@ -92,16 +101,15 @@ class HyprlandService {
void onUrgent(std::string windowAddress);
void onActiveWindowChanged(std::string windowAddress);
void onMonitorRemoved(std::string monitorName);
// void onMonitorAdded(std::string monitorName);
void onMonitorAdded(std::string monitorName);
HyprlandService();
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;
int socketFd = -1;
///
void bindHyprlandSocket();
@@ -112,4 +120,7 @@ class HyprlandService {
void refreshIndicator(std::shared_ptr<Workspace> workspace);
void handleSocketMessage(SocketHelper::SocketMessage message);
sigc::signal<void(std::string)> m_signal_monitor_added;
sigc::signal<void(std::string)> m_signal_monitor_removed;
};

View File

@@ -12,11 +12,11 @@
#include "gdkmm/monitor.h"
class NotificationController {
static std::shared_ptr<NotificationController> instance;
inline static std::shared_ptr<NotificationController> instance = nullptr;
public:
static std::shared_ptr<NotificationController> getInstance() {
if (!NotificationController::instance) {
if (NotificationController::instance == nullptr) {
NotificationController::instance = std::shared_ptr<NotificationController>(new NotificationController());
}
return NotificationController::instance;
@@ -26,12 +26,15 @@ class NotificationController {
void showNotificationOnAllMonitors(NotifyMessage notify);
void showCopyNotification(NotifyMessage notify);
void addMonitor(std::shared_ptr<Gdk::Monitor> monitor);
void removeMonitor(std::shared_ptr<Gdk::Monitor> monitor);
private:
uint64_t globalNotificationId = 1;
std::map<uint64_t, std::vector<std::shared_ptr<BaseNotification>>> activeNotifications;
std::map<uint64_t, int> hoverCounts;
NotificationController();
std::vector<std::shared_ptr<Gdk::Monitor>> activeMonitors;
std::map<std::string, std::shared_ptr<Gdk::Monitor>> activeMonitors;
void updateHoverState(uint64_t notificationId, bool isHovered);
void closeNotification(uint64_t notificationId);
};

View File

@@ -0,0 +1,115 @@
#pragma once
#include "components/button/iconButton.hpp"
#include "connection/dbus/bluetooth.hpp"
#include "gtkmm/box.h"
#include "gtkmm/button.h"
#include "gtkmm/image.h"
#include "gtkmm/label.h"
class BluetoothSettingsRow : public Gtk::Box {
public:
BluetoothSettingsRow(const BluetoothDevice &device) : device(device) {
set_orientation(Gtk::Orientation::HORIZONTAL);
set_spacing(10);
set_margin_bottom(6);
if (!device.icon.empty()) {
this->icon.set_from_icon_name(device.icon);
this->icon.set_pixel_size(24);
append(this->icon);
}
nameLabel.set_text(device.name.empty() ? "Unknown Device" : device.name);
nameLabel.set_halign(Gtk::Align::START);
nameLabel.set_valign(Gtk::Align::CENTER);
append(nameLabel);
addressLabel.set_text(device.address);
addressLabel.set_halign(Gtk::Align::START);
addressLabel.set_valign(Gtk::Align::CENTER);
addressLabel.add_css_class("bluetooth-device-address");
append(addressLabel);
pairButton.set_label(device.paired ? "Unpair" : "Pair");
pairButton.signal_clicked().connect([device]() {
if (device.paired) {
BluetoothController::getInstance()->unpairDevice(device.object_path);
} else {
BluetoothController::getInstance()->pairDevice(device.object_path);
}
});
append(pairButton);
connectButton.set_label(device.connected ? "Disconnect" : "Connect");
connectButton.signal_clicked().connect([device]() {
if (device.connected) {
BluetoothController::getInstance()->disconnectDevice(device.object_path);
} else {
BluetoothController::getInstance()->connectDevice(device.object_path);
}
});
append(connectButton);
trustButton.set_label(device.trusted ? "Distrust" : "Trust");
trustButton.signal_clicked().connect([device]() {
BluetoothController::getInstance()->trustDevice(device.object_path, !device.trusted);
});
append(trustButton);
}
void updateDevice(const BluetoothDevice &device) {
this->device = device;
if (!device.icon.empty()) {
this->icon.set_from_icon_name(device.icon);
}
nameLabel.set_text(device.name.empty() ? "Unknown Device" : device.name);
addressLabel.set_text(device.address);
pairButton.set_label(device.paired ? "Unpair" : "Pair");
connectButton.set_label(device.connected ? "Disconnect" : "Connect");
trustButton.set_label(device.trusted ? "Distrust" : "Trust");
}
private:
BluetoothDevice device;
Gtk::Image icon;
Gtk::Label nameLabel;
Gtk::Label addressLabel;
Gtk::Button pairButton;
Gtk::Button connectButton;
Gtk::Button trustButton;
};
class BluetoothSettings : public Gtk::Box {
public:
BluetoothSettings();
private:
std::shared_ptr<BluetoothController> bluetoothController = BluetoothController::getInstance();
std::map<std::string, BluetoothDevice> activeBluetoothDevices;
std::map<std::string, std::shared_ptr<BluetoothSettingsRow>> deviceRows;
std::shared_ptr<IconButton> powerButton = std::make_shared<IconButton>(Icon::POWER_SETTINGS_NEW);
std::shared_ptr<IconButton> scanButton = std::make_shared<IconButton>(Icon::BLUETOOTH_SEARCHING);
Gtk::Box connectedDevicesBox;
Gtk::Box availableDevicesBox;
bool bluetoothIsPowered = false;
bool bluetoothIsScanning = false;
void addBluetoothDevice(const BluetoothDevice &device);
void removeBluetoothDevice(const std::string &object_path);
void updateBluetoothDevice(const BluetoothDevice &device);
void setBluetoothPowered(bool powered);
void setScanning(bool scanning);
};

View File

@@ -6,6 +6,7 @@
#include "components/button/tabButton.hpp"
#include "components/popover.hpp"
#include "widgets/controlCenter/mediaWidget.hpp"
#include "widgets/controlCenter/settings.hpp"
#include "widgets/controlCenter/timer.hpp"
#include "widgets/weather.hpp"
@@ -27,10 +28,12 @@ class ControlCenter : public Popover {
std::unique_ptr<TabButton> mediaTabButton;
std::unique_ptr<TabButton> infoTabButton;
std::unique_ptr<TabButton> timerButton;
std::unique_ptr<TabButton> settingsTabButton;
std::unique_ptr<WeatherWidget> weatherWidget;
std::unique_ptr<MediaWidget> mediaControlWidget;
std::unique_ptr<TimerWidget> timerWidget;
std::unique_ptr<SettingsWidget> settingsWidget;
void addPlayerWidget(const std::string &bus_name);
void removePlayerWidget(const std::string &bus_name);

View File

@@ -0,0 +1,14 @@
#pragma once
#include <map>
#include "connection/dbus/bluetooth.hpp"
#include "widgets/controlCenter/bluetoothSettings.hpp"
#include "gtkmm/box.h"
class SettingsWidget : public Gtk::Box {
public:
SettingsWidget();
private:
BluetoothSettings bluetoothSettings;
};

View File

@@ -1,6 +1,7 @@
#pragma once
#include <memory>
#include <sigc++/connection.h>
#include <sys/types.h>
#include "components/timer.hpp"
#include "services/timerService.hpp"
@@ -9,6 +10,7 @@
class TimerWidget : public Gtk::Box {
public:
TimerWidget();
~TimerWidget();
void addTimer(const std::string &duration, uint64_t timerId);
void removeTimer(uint64_t timerId);
void activateTimer(uint64_t timerId);
@@ -19,4 +21,8 @@ class TimerWidget : public Gtk::Box {
std::string rawDigits;
std::map<uint64_t, std::unique_ptr<Timer>> activeTimers;
sigc::connection timerSetConnection;
sigc::connection timerCancelledConnection;
sigc::connection timerExpiredConnection;
sigc::connection tickConnection;
};

View File

@@ -1,16 +0,0 @@
#pragma once
#include <gtk4-layer-shell/gtk4-layer-shell.h>
#include <gtkmm.h>
#include <string>
class WallpaperWindow : public Gtk::Window {
public:
WallpaperWindow(GdkMonitor *monitor, const std::string &imagePath);
private:
Gtk::Picture picture;
static std::string expand_user_path(const std::string &path);
static Glib::RefPtr<Gdk::Texture> load_texture(const std::string &path);
};