Compare commits
2 Commits
c245fa7277
...
47f052f913
| Author | SHA1 | Date | |
|---|---|---|---|
| 47f052f913 | |||
| 3558fd3ebc |
@@ -9,18 +9,6 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
# Some CMake versions may enable C++ modules and add compiler flags
|
||||
# like `-fmodules-ts`. Older or certain `clang`/`clangd` builds may
|
||||
# not accept this flag and will report "Unknown argument: '-fmodules-ts'".
|
||||
# Strip that flag when using Clang to avoid diagnostics from clang/clangd.
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang")
|
||||
string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
|
||||
string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}")
|
||||
string(REPLACE "-fmodules-ts" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
|
||||
endif()
|
||||
|
||||
pkg_check_modules(GTKMM REQUIRED gtkmm-4.0)
|
||||
pkg_check_modules(LAYERSHELL REQUIRED gtk4-layer-shell-0)
|
||||
pkg_check_modules(WEBKIT REQUIRED webkitgtk-6.0)
|
||||
@@ -40,7 +28,9 @@ target_sources(bar_lib
|
||||
src/widgets/webWidget.cpp
|
||||
src/services/hyprland.cpp
|
||||
src/services/tray.cpp
|
||||
src/services/notifications.cpp
|
||||
src/widgets/tray.cpp
|
||||
src/components/popover.cpp
|
||||
)
|
||||
include_directories(bar_lib PRIVATE
|
||||
include
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "glibmm/refptr.h"
|
||||
#include "gtkmm/application.h"
|
||||
#include "services/hyprland.hpp"
|
||||
#include "services/notifications.hpp"
|
||||
#include "services/tray.hpp"
|
||||
|
||||
class App {
|
||||
@@ -18,6 +19,7 @@ class App {
|
||||
Glib::RefPtr<Gtk::Application> app;
|
||||
std::vector<Bar *> bars;
|
||||
HyprlandService hyprlandService;
|
||||
NotificationService notificationService;
|
||||
TrayService trayService;
|
||||
|
||||
void setupServices();
|
||||
|
||||
19
include/components/popover.hpp
Normal file
19
include/components/popover.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/popover.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
class Popover: public Gtk::Button {
|
||||
public:
|
||||
Popover(std::string icon, std::string name);
|
||||
~Popover() override;
|
||||
|
||||
protected:
|
||||
void on_toggle_window();
|
||||
Gtk::Popover* popover = nullptr;
|
||||
void set_popover_child(Gtk::Widget& child) {
|
||||
gtk_popover_set_child(popover->gobj(), child.gobj());
|
||||
}
|
||||
};
|
||||
20
include/services/notifications.hpp
Normal file
20
include/services/notifications.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#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;
|
||||
};
|
||||
@@ -1,14 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "components/popover.hpp"
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/popover.h>
|
||||
|
||||
class WebWidget : public Gtk::Button {
|
||||
class WebWidget : public Popover {
|
||||
public:
|
||||
WebWidget(std::string icon, std::string title, std::string url);
|
||||
~WebWidget() override;
|
||||
|
||||
private:
|
||||
void on_toggle_window();
|
||||
Gtk::Popover* popover = nullptr;
|
||||
};
|
||||
|
||||
@@ -44,8 +44,6 @@ window {
|
||||
.workspace-pill-urgent {
|
||||
background-color: #ff5555;
|
||||
color: #fff;
|
||||
|
||||
/* base glow (will be animated) */
|
||||
animation: workspace-blink 1s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
App::App() {
|
||||
this->setupServices();
|
||||
this->notificationService.intialize();
|
||||
|
||||
this->app = Gtk::Application::create("org.example.mybar");
|
||||
|
||||
|
||||
35
src/components/popover.cpp
Normal file
35
src/components/popover.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "components/popover.hpp"
|
||||
|
||||
#include "gtkmm/label.h"
|
||||
#include "gtkmm/object.h"
|
||||
|
||||
Popover::Popover(std::string icon, std::string name) {
|
||||
auto label = Gtk::make_managed<Gtk::Label>(icon);
|
||||
label->add_css_class("icon-label");
|
||||
set_child(*label);
|
||||
signal_clicked().connect(
|
||||
sigc::mem_fun(*this, &Popover::on_toggle_window));
|
||||
|
||||
popover = new Gtk::Popover();
|
||||
popover->set_parent(*this);
|
||||
popover->set_autohide(true);
|
||||
|
||||
popover->signal_closed().connect([this]() {
|
||||
this->add_css_class("minimized");
|
||||
this->remove_css_class("restored");
|
||||
});
|
||||
}
|
||||
|
||||
Popover::~Popover() {
|
||||
delete popover;
|
||||
}
|
||||
|
||||
void Popover::on_toggle_window() {
|
||||
if (popover->get_visible()) {
|
||||
popover->popdown();
|
||||
} else {
|
||||
this->remove_css_class("minimized");
|
||||
this->add_css_class("restored");
|
||||
popover->popup();
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,6 @@ void HyprlandService::onUrgentEvent(std::string windowAddress) {
|
||||
it->second->urgentWindows.push_back(windowAddress);
|
||||
workspaceStateChanged.emit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
307
src/services/notifications.cpp
Normal file
307
src/services/notifications.cpp
Normal file
@@ -0,0 +1,307 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
std::cout << "Notifications daemon active (org.freedesktop.Notifications)." << std::endl;
|
||||
}
|
||||
@@ -3,23 +3,7 @@
|
||||
#include <gtkmm/label.h>
|
||||
#include <webkit/webkit.h>
|
||||
|
||||
WebWidget::WebWidget(std::string icon, std::string title, std::string url) {
|
||||
auto label = Gtk::make_managed<Gtk::Label>(icon);
|
||||
label->add_css_class("icon-label");
|
||||
set_child(*label);
|
||||
|
||||
signal_clicked().connect(
|
||||
sigc::mem_fun(*this, &WebWidget::on_toggle_window));
|
||||
|
||||
popover = new Gtk::Popover();
|
||||
popover->set_parent(*this);
|
||||
popover->set_autohide(true);
|
||||
|
||||
popover->signal_closed().connect([this]() {
|
||||
this->add_css_class("minimized");
|
||||
this->remove_css_class("restored");
|
||||
});
|
||||
|
||||
WebWidget::WebWidget(std::string icon, std::string name, std::string url) : Popover(icon, name) {
|
||||
auto webview = webkit_web_view_new();
|
||||
gtk_widget_set_hexpand(webview, true);
|
||||
gtk_widget_set_vexpand(webview, true);
|
||||
@@ -31,19 +15,5 @@ WebWidget::WebWidget(std::string icon, std::string title, std::string url) {
|
||||
|
||||
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url.c_str());
|
||||
|
||||
gtk_popover_set_child(popover->gobj(), webview);
|
||||
}
|
||||
|
||||
WebWidget::~WebWidget() {
|
||||
delete popover;
|
||||
}
|
||||
|
||||
void WebWidget::on_toggle_window() {
|
||||
if (popover->get_visible()) {
|
||||
popover->popdown();
|
||||
} else {
|
||||
popover->popup();
|
||||
this->remove_css_class("minimized");
|
||||
this->add_css_class("restored");
|
||||
}
|
||||
this->set_popover_child(*Glib::wrap(webview));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user