quick commit
This commit is contained in:
24
include/connection/dbus/dbus.hpp
Normal file
24
include/connection/dbus/dbus.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <giomm.h>
|
||||
#include <sigc++/sigc++.h>
|
||||
|
||||
class DbusConnection {
|
||||
public:
|
||||
virtual ~DbusConnection() = default;
|
||||
|
||||
protected:
|
||||
Glib::RefPtr<Gio::DBus::Connection> connection;
|
||||
|
||||
void connect_session_async(const sigc::slot<void(const Glib::RefPtr<Gio::AsyncResult> &)> &callback) {
|
||||
Gio::DBus::Connection::get(Gio::DBus::BusType::SESSION, callback);
|
||||
}
|
||||
|
||||
static void ensure_gio_init() {
|
||||
try {
|
||||
Gio::init();
|
||||
} catch (const Glib::Error &) {
|
||||
// Already initialized.
|
||||
}
|
||||
}
|
||||
};
|
||||
46
include/connection/dbus/messages.hpp
Normal file
46
include/connection/dbus/messages.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
#include "gdkmm/pixbuf.h"
|
||||
#include "glibmm/variant.h"
|
||||
|
||||
struct MprisPlayer2Message {
|
||||
std::string title;
|
||||
std::vector<std::string> artist;
|
||||
std::string artwork_url;
|
||||
std::string track_id;
|
||||
int64_t length_ms;
|
||||
|
||||
std::function<void()> play_pause;
|
||||
std::function<void()> next;
|
||||
std::function<void()> previous;
|
||||
};
|
||||
|
||||
enum NotificationUrgency {
|
||||
LOW = 0,
|
||||
NORMAL = 1,
|
||||
CRITICAL = 2
|
||||
};
|
||||
|
||||
struct NotifyMessage {
|
||||
std::string app_name;
|
||||
uint32_t replaces_id;
|
||||
std::string app_icon;
|
||||
std::string summary;
|
||||
std::string body;
|
||||
std::vector<std::string> actions;
|
||||
NotificationUrgency urgency = NORMAL;
|
||||
int32_t expire_timeout;
|
||||
// Callback to invoke when an action is triggered
|
||||
std::function<void(const std::string& action_id)> on_action;
|
||||
// Guard to prevent multiple action invocations across mirrors
|
||||
std::shared_ptr<bool> actionInvoked;
|
||||
// image data (if any) from dbus
|
||||
std::optional<Glib::RefPtr<Gdk::Pixbuf>> imageData;
|
||||
};
|
||||
81
include/connection/dbus/mpris.hpp
Normal file
81
include/connection/dbus/mpris.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <giomm.h>
|
||||
#include <sigc++/sigc++.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "connection/dbus/dbus.hpp"
|
||||
#include "connection/dbus/messages.hpp"
|
||||
|
||||
class MprisController : public DbusConnection {
|
||||
public:
|
||||
struct PlayerState {
|
||||
std::string title;
|
||||
std::vector<std::string> artist;
|
||||
std::string artwork_url;
|
||||
int64_t length_ms;
|
||||
};
|
||||
enum class PlaybackStatus {
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped,
|
||||
};
|
||||
|
||||
static std::shared_ptr<MprisController> getInstance();
|
||||
static std::shared_ptr<MprisController> createForPlayer(const std::string &bus_name);
|
||||
|
||||
std::vector<std::string> get_registered_players() const;
|
||||
|
||||
void toggle_play();
|
||||
void pause();
|
||||
void next_song();
|
||||
void previous_song();
|
||||
void emit_seeked(int64_t position_us);
|
||||
void set_position(const std::string &track_id, int64_t position_us);
|
||||
|
||||
sigc::signal<void(const MprisPlayer2Message &)> &signal_mpris_updated();
|
||||
sigc::signal<void(PlaybackStatus)> &signal_playback_status_changed();
|
||||
sigc::signal<void(int64_t)> &signal_playback_position_changed();
|
||||
sigc::signal<void(bool)> &signal_can_seek_changed();
|
||||
sigc::signal<void(const std::string &)> &signal_player_registered();
|
||||
sigc::signal<void(const std::string &)> &signal_player_deregistered();
|
||||
|
||||
private:
|
||||
MprisController();
|
||||
explicit MprisController(const std::string &bus_name);
|
||||
std::map<std::string, PlaybackStatus> playbackStatusMap = {
|
||||
{"Playing", PlaybackStatus::Playing},
|
||||
{"Paused", PlaybackStatus::Paused},
|
||||
{"Stopped", PlaybackStatus::Stopped},
|
||||
};
|
||||
|
||||
PlaybackStatus currentPlaybackStatus = PlaybackStatus::Stopped;
|
||||
|
||||
Glib::RefPtr<Gio::DBus::Proxy> m_proxy;
|
||||
Glib::RefPtr<Gio::DBus::Proxy> m_dbus_proxy;
|
||||
std::string m_player_bus_name = "org.mpris.MediaPlayer2.spotify";
|
||||
std::set<std::string> registeredPlayers;
|
||||
|
||||
sigc::signal<void(const MprisPlayer2Message &)> mprisUpdatedSignal;
|
||||
sigc::signal<void(PlaybackStatus)> playbackStatusChangedSignal;
|
||||
sigc::signal<void(int64_t)> playbackPositionChangedSignal;
|
||||
sigc::signal<void(bool)> canSeekChangedSignal;
|
||||
sigc::signal<void(const std::string &)> playerRegisteredSignal;
|
||||
sigc::signal<void(const std::string &)> playerDeregisteredSignal;
|
||||
|
||||
void on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &result);
|
||||
void signalNotification();
|
||||
void emit_cached_playback_status();
|
||||
void emit_cached_position();
|
||||
void emit_cached_can_seek();
|
||||
void on_dbus_signal(const Glib::ustring &sender_name,
|
||||
const Glib::ustring &signal_name,
|
||||
const Glib::VariantContainerBase ¶meters);
|
||||
void handle_player_registered(const std::string &bus_name);
|
||||
void handle_player_deregistered(const std::string &bus_name);
|
||||
|
||||
// Called when the song changes
|
||||
void on_properties_changed(const Gio::DBus::Proxy::MapChangedProperties &changed_properties,
|
||||
const std::vector<Glib::ustring> &invalidated_properties);
|
||||
};
|
||||
71
include/connection/dbus/notification.hpp
Normal file
71
include/connection/dbus/notification.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <giomm.h>
|
||||
#include <gtkmm.h>
|
||||
#include <sigc++/sigc++.h>
|
||||
|
||||
#include "connection/dbus/dbus.hpp"
|
||||
#include "giomm/dbusconnection.h"
|
||||
#include "giomm/dbusownname.h"
|
||||
#include "glib.h"
|
||||
|
||||
const Glib::ustring introspection_xml = R"(
|
||||
<node>
|
||||
<interface name="org.freedesktop.Notifications">
|
||||
<method name="GetCapabilities">
|
||||
<arg name="capabilities" type="as" direction="out"/>
|
||||
</method>
|
||||
<method name="Notify">
|
||||
<arg name="app_name" type="s" direction="in"/>
|
||||
<arg name="replaces_id" type="u" direction="in"/>
|
||||
<arg name="app_icon" type="s" direction="in"/>
|
||||
<arg name="summary" type="s" direction="in"/>
|
||||
<arg name="body" type="s" direction="in"/>
|
||||
<arg name="actions" type="as" direction="in"/>
|
||||
<arg name="hints" type="a{sv}" direction="in"/>
|
||||
<arg name="expire_timeout" type="i" direction="in"/>
|
||||
<arg name="id" type="u" direction="out"/>
|
||||
</method>
|
||||
<method name="CloseNotification">
|
||||
<arg name="id" type="u" direction="in"/>
|
||||
</method>
|
||||
<method name="GetServerInformation">
|
||||
<arg name="name" type="s" direction="out"/>
|
||||
<arg name="vendor" type="s" direction="out"/>
|
||||
<arg name="version" type="s" direction="out"/>
|
||||
<arg name="spec_version" type="s" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
)";
|
||||
|
||||
class NotificationService : public DbusConnection {
|
||||
public:
|
||||
NotificationService() : notificationIdCounter(1) {
|
||||
Gio::DBus::own_name(
|
||||
Gio::DBus::BusType::SESSION,
|
||||
"org.freedesktop.Notifications",
|
||||
sigc::mem_fun(*this, &NotificationService::onBusAcquired),
|
||||
{}, // Name acquired slot (optional)
|
||||
{}, // Name lost slot (optional)
|
||||
Gio::DBus::BusNameOwnerFlags::REPLACE);
|
||||
}
|
||||
|
||||
void onBusAcquired(const Glib::RefPtr<Gio::DBus::Connection> &connection, const Glib::ustring &name);
|
||||
|
||||
private:
|
||||
guint notificationIdCounter;
|
||||
const Gio::DBus::InterfaceVTable &getMessageInterfaceVTable();
|
||||
void on_method_call(const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &method_name,
|
||||
const Glib::VariantContainerBase ¶meters,
|
||||
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation);
|
||||
|
||||
void handle_notify(const Glib::VariantContainerBase ¶meters,
|
||||
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation);
|
||||
void createNotificationPopup(const Glib::ustring &title, const Glib::ustring &message);
|
||||
};
|
||||
163
include/connection/dbus/tray.hpp
Normal file
163
include/connection/dbus/tray.hpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include <gdkmm/memorytexture.h>
|
||||
#include <gio/gio.h>
|
||||
#include <giomm/actiongroup.h>
|
||||
#include <giomm/dbusconnection.h>
|
||||
#include <giomm/init.h>
|
||||
#include <giomm/menumodel.h>
|
||||
#include <glibmm/bytes.h>
|
||||
#include <glibmm/refptr.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sigc++/sigc++.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "connection/dbus/dbus.hpp"
|
||||
|
||||
class TrayService : public DbusConnection {
|
||||
inline static TrayService *instance = nullptr;
|
||||
public:
|
||||
struct Item {
|
||||
std::string id;
|
||||
std::string busName;
|
||||
std::string objectPath;
|
||||
std::string title;
|
||||
std::string status;
|
||||
std::string iconName;
|
||||
std::string menuPath;
|
||||
bool menuAvailable = false;
|
||||
Glib::RefPtr<Gdk::Paintable> iconPaintable;
|
||||
};
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
std::vector<Item> snapshotItems() const;
|
||||
const Item *findItem(const std::string &id) const;
|
||||
|
||||
void activate(const std::string &id, int32_t x, int32_t y);
|
||||
void secondaryActivate(const std::string &id, int32_t x, int32_t y);
|
||||
void contextMenu(const std::string &id, int32_t x, int32_t y);
|
||||
|
||||
Glib::RefPtr<Gio::MenuModel> get_menu_model(const std::string &id);
|
||||
Glib::RefPtr<Gio::ActionGroup> get_menu_action_group(const std::string &id);
|
||||
struct MenuNode {
|
||||
int id = 0;
|
||||
std::string label;
|
||||
bool enabled = true;
|
||||
bool visible = true;
|
||||
bool separator = false;
|
||||
std::vector<MenuNode> children;
|
||||
};
|
||||
using MenuLayoutCallback = sigc::slot<void(std::optional<MenuNode>)>;
|
||||
void request_menu_layout(const std::string &id, MenuLayoutCallback callback);
|
||||
bool activate_menu_item(const std::string &id, int itemId, int32_t x = -1,
|
||||
int32_t y = -1, uint32_t button = 1,
|
||||
uint32_t timestampMs = 0);
|
||||
|
||||
sigc::signal<void(const Item &)> &signal_item_added();
|
||||
sigc::signal<void(const std::string &)> &signal_item_removed();
|
||||
sigc::signal<void(const Item &)> &signal_item_updated();
|
||||
|
||||
static TrayService *getInstance() {
|
||||
if (TrayService::instance == nullptr) {
|
||||
TrayService::instance = new TrayService();
|
||||
}
|
||||
|
||||
return TrayService::instance;
|
||||
}
|
||||
|
||||
private:
|
||||
TrayService();
|
||||
~TrayService();
|
||||
|
||||
struct TrackedItem {
|
||||
Item publicData;
|
||||
guint signalSubscriptionId = 0;
|
||||
guint ownerWatchId = 0;
|
||||
Glib::RefPtr<Gio::MenuModel> menuModel;
|
||||
Glib::RefPtr<Gio::ActionGroup> menuActions;
|
||||
|
||||
guint refreshSourceId = 0;
|
||||
bool refreshInFlight = false;
|
||||
bool refreshQueued = false;
|
||||
bool addSignalPending = false;
|
||||
};
|
||||
|
||||
Glib::RefPtr<Gio::DBus::NodeInfo> nodeInfo;
|
||||
Gio::DBus::InterfaceVTable vtable;
|
||||
|
||||
guint nameOwnerId = 0;
|
||||
guint registrationId = 0;
|
||||
bool hostRegistered = false;
|
||||
|
||||
std::map<std::string, std::unique_ptr<TrackedItem>> items;
|
||||
|
||||
sigc::signal<void(const Item &)> itemAddedSignal;
|
||||
sigc::signal<void(const std::string &)> itemRemovedSignal;
|
||||
sigc::signal<void(const Item &)> itemUpdatedSignal;
|
||||
|
||||
void on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &name);
|
||||
void on_name_acquired(const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &name);
|
||||
void on_name_lost(const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &name);
|
||||
|
||||
void handle_method_call(
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender, const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name, const Glib::ustring &method_name,
|
||||
const Glib::VariantContainerBase ¶meters,
|
||||
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation);
|
||||
|
||||
void handle_get_property_slot(
|
||||
Glib::VariantBase &result,
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender, const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &property_name);
|
||||
bool handle_set_property_slot(
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender, const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name, const Glib::ustring &property_name,
|
||||
const Glib::VariantBase &value);
|
||||
|
||||
Glib::VariantBase handle_get_property(const Glib::ustring &property_name);
|
||||
bool handle_set_property(const Glib::ustring &property_name,
|
||||
const Glib::VariantBase &value);
|
||||
|
||||
void register_item(const Glib::ustring &sender, const std::string &service);
|
||||
void unregister_item(const std::string &id);
|
||||
|
||||
void schedule_refresh(const std::string &id);
|
||||
void begin_refresh(const std::string &id);
|
||||
static gboolean refresh_timeout_cb(gpointer user_data);
|
||||
static void on_refresh_finished_static(GObject *source, GAsyncResult *res,
|
||||
gpointer user_data);
|
||||
void emit_registered_items_changed();
|
||||
|
||||
Glib::Variant<std::vector<Glib::ustring>>
|
||||
create_registered_items_variant() const;
|
||||
|
||||
void emit_watcher_signal(const Glib::ustring &signal_name,
|
||||
const Glib::VariantContainerBase ¶meters);
|
||||
|
||||
static void on_dbus_signal_static(GDBusConnection *connection,
|
||||
const gchar *sender_name,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *signal_name,
|
||||
GVariant *parameters, gpointer user_data);
|
||||
void on_dbus_signal(const gchar *sender_name, const gchar *object_path,
|
||||
const gchar *interface_name, const gchar *signal_name,
|
||||
GVariant *parameters);
|
||||
static void on_name_vanished_static(GDBusConnection *connection,
|
||||
const gchar *name, gpointer user_data);
|
||||
void on_name_vanished(const gchar *bus_name);
|
||||
|
||||
static Glib::RefPtr<Gdk::Paintable> parse_icon_pixmap(GVariant *variant);
|
||||
};
|
||||
36
include/connection/httpConnection.hpp
Normal file
36
include/connection/httpConnection.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
struct HttpResponse {
|
||||
long status_code = 0;
|
||||
std::string body;
|
||||
std::map<std::string, std::string> headers;
|
||||
std::string error;
|
||||
|
||||
bool ok() const {
|
||||
return error.empty();
|
||||
}
|
||||
};
|
||||
|
||||
class HttpConnection {
|
||||
public:
|
||||
static HttpResponse get(const std::string &url,
|
||||
const std::map<std::string, std::string> &headers = {},
|
||||
long timeout_ms = 0);
|
||||
|
||||
static HttpResponse post(const std::string &url,
|
||||
const std::string &body,
|
||||
const std::map<std::string, std::string> &headers = {},
|
||||
const std::string &content_type = "application/json",
|
||||
long timeout_ms = 0);
|
||||
|
||||
private:
|
||||
static HttpResponse performRequest(const std::string &method,
|
||||
const std::string &url,
|
||||
const std::string &body,
|
||||
const std::map<std::string, std::string> &headers,
|
||||
const std::string &content_type,
|
||||
long timeout_ms);
|
||||
};
|
||||
Reference in New Issue
Block a user