add move window to hyprland service
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "services/dbus/mpris.hpp"
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "helpers/string.hpp"
|
||||
|
||||
@@ -13,7 +14,6 @@ std::shared_ptr<MprisController> MprisController::getInstance() {
|
||||
}
|
||||
|
||||
MprisController::MprisController() {
|
||||
// 1. Connect to the Session Bus
|
||||
Gio::DBus::Connection::get(
|
||||
Gio::DBus::BusType::SESSION,
|
||||
sigc::mem_fun(*this, &MprisController::on_bus_connected));
|
||||
@@ -44,7 +44,6 @@ void MprisController::on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &res
|
||||
if (m_proxy) {
|
||||
std::cout << "Connected to: " << player_bus_name << std::endl;
|
||||
|
||||
// uncomment if launch notification on start
|
||||
signalNotification();
|
||||
|
||||
m_proxy->signal_properties_changed().connect(
|
||||
@@ -69,6 +68,11 @@ void MprisController::signalNotification() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!metadata_var.is_of_type(Glib::VariantType("a{sv}"))) {
|
||||
std::cout << "Unexpected metadata type." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
using MetadataMap = std::map<Glib::ustring, Glib::VariantBase>;
|
||||
MetadataMap metadata_map;
|
||||
|
||||
@@ -78,26 +82,46 @@ void MprisController::signalNotification() {
|
||||
metadata_map = variant_dict.get();
|
||||
|
||||
std::string title, artist, artwork_url;
|
||||
|
||||
if (metadata_map.count("xesam:title")) {
|
||||
auto title_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(metadata_map["xesam:title"]);
|
||||
title = title_var.get();
|
||||
const auto &title_base = metadata_map["xesam:title"];
|
||||
if (title_base.is_of_type(Glib::VariantType("s"))) {
|
||||
auto title_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(title_base);
|
||||
title = title_var.get();
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata_map.count("xesam:artist")) {
|
||||
auto artist_var = metadata_map["xesam:artist"];
|
||||
const auto &artist_var = metadata_map["xesam:artist"];
|
||||
|
||||
if (artist_var.is_of_type(Glib::VariantType("as"))) {
|
||||
auto artists = Glib::VariantBase::cast_dynamic<Glib::Variant<std::vector<Glib::ustring>>>(artist_var).get();
|
||||
if (!artists.empty()) {
|
||||
artist = artists[0]; // Take the first artist
|
||||
}
|
||||
} else if (artist_var.is_of_type(Glib::VariantType("s"))) {
|
||||
auto artist_str = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(artist_var);
|
||||
artist = artist_str.get();
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata_map.count("mpris:artUrl")) {
|
||||
auto art_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(metadata_map["mpris:artUrl"]);
|
||||
artwork_url = art_var.get();
|
||||
const auto &art_base = metadata_map["mpris:artUrl"];
|
||||
if (art_base.is_of_type(Glib::VariantType("s"))) {
|
||||
auto art_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(art_base);
|
||||
artwork_url = art_var.get();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t length_us = 0;
|
||||
if (metadata_map.count("mpris:length")) {
|
||||
const auto &length_base = metadata_map["mpris:length"];
|
||||
if (length_base.is_of_type(Glib::VariantType("x"))) {
|
||||
auto length_var = Glib::VariantBase::cast_dynamic<Glib::Variant<gint64>>(length_base);
|
||||
length_us = static_cast<int64_t>(length_var.get());
|
||||
} else if (length_base.is_of_type(Glib::VariantType("t"))) {
|
||||
auto length_var = Glib::VariantBase::cast_dynamic<Glib::Variant<guint64>>(length_base);
|
||||
length_us = static_cast<int64_t>(length_var.get());
|
||||
}
|
||||
}
|
||||
|
||||
MprisPlayer2Message mpris;
|
||||
@@ -107,6 +131,7 @@ void MprisController::signalNotification() {
|
||||
mpris.play_pause = [this]() { this->toggle_play(); };
|
||||
mpris.next = [this]() { this->next_song(); };
|
||||
mpris.previous = [this]() { this->previous_song(); };
|
||||
mpris.length_ms = length_us;
|
||||
mprisUpdatedSignal.emit(mpris);
|
||||
}
|
||||
|
||||
@@ -136,3 +161,18 @@ void MprisController::next_song() {
|
||||
m_proxy->call("Next");
|
||||
}
|
||||
}
|
||||
|
||||
void MprisController::emit_seeked(int64_t position_us) {
|
||||
if (!m_proxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Glib::VariantContainerBase params = Glib::VariantContainerBase::create_tuple(
|
||||
Glib::Variant<gint64>::create(position_us));
|
||||
|
||||
m_proxy->call("Seek", params);
|
||||
} catch (const Glib::Error &ex) {
|
||||
std::cerr << "Error seeking: " << ex.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,24 +29,24 @@ void HyprlandService::init() {
|
||||
auto monitorPtr = std::make_shared<Monitor>();
|
||||
|
||||
std::string monitorName = item["name"].get<std::string>();
|
||||
int monitorId = item["id"].get<int>();
|
||||
int monitorId = item["id"].get<int>();
|
||||
|
||||
monitorPtr->id = monitorId;
|
||||
monitorPtr->name = monitorName;
|
||||
monitorPtr->activeWorkspaceId = item["activeWorkspace"]["id"].get<int>();
|
||||
monitorPtr->focused = item["focused"].get<bool>();
|
||||
monitorPtr->id = monitorId;
|
||||
monitorPtr->name = monitorName;
|
||||
monitorPtr->activeWorkspaceId = item["activeWorkspace"]["id"].get<int>();
|
||||
monitorPtr->focused = item["focused"].get<bool>();
|
||||
this->monitors[monitorPtr->name] = monitorPtr;
|
||||
|
||||
for (int i = 1; i <= NUM_WORKSPACES; i++) {
|
||||
std::shared_ptr<WorkspaceData> state = std::make_shared<WorkspaceData>();
|
||||
int workspaceId = i + (NUM_WORKSPACES * monitorId);
|
||||
|
||||
state->id = workspaceId;
|
||||
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;
|
||||
auto view = std::make_shared<WorkspaceIndicator>(workspaceId, std::to_string(i), onClick);
|
||||
auto workSpace = std::make_shared<Workspace>();
|
||||
workSpace->state = state;
|
||||
workSpace->view = view;
|
||||
|
||||
monitorPtr->monitorWorkspaces[workspaceId] = workSpace;
|
||||
this->workspaces[workspaceId] = workSpace;
|
||||
@@ -76,7 +76,7 @@ void HyprlandService::init() {
|
||||
auto workspacePtr = workspaces[workspace["id"].get<int>()];
|
||||
auto state = workspacePtr->state;
|
||||
|
||||
state->id = workspace["id"].get<int>();
|
||||
state->id = workspace["id"].get<int>();
|
||||
state->monitorName = workspace["monitor"].get<std::string>();
|
||||
|
||||
refreshIndicator(workspacePtr);
|
||||
@@ -106,9 +106,9 @@ void HyprlandService::bindHyprlandSocket() {
|
||||
}
|
||||
|
||||
auto socket_conditions = static_cast<GIOCondition>(G_IO_IN | G_IO_HUP | G_IO_ERR); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
|
||||
GSource *source = g_unix_fd_source_new(socketFd, socket_conditions);
|
||||
GSource *source = g_unix_fd_source_new(socketFd, socket_conditions);
|
||||
|
||||
auto onSocketEvent = [](gint fd, GIOCondition , 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, ">>");
|
||||
|
||||
@@ -119,7 +119,7 @@ void HyprlandService::bindHyprlandSocket() {
|
||||
return G_SOURCE_CONTINUE;
|
||||
};
|
||||
|
||||
g_source_set_callback(source, reinterpret_cast<GSourceFunc>(reinterpret_cast<void*>(+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);
|
||||
}
|
||||
@@ -173,11 +173,34 @@ void HyprlandService::onMonitorRemoved(std::string monitorName) {
|
||||
this->monitors.erase(monitorName);
|
||||
}
|
||||
|
||||
void HyprlandService::onMoveWindow(std::string windowData) {
|
||||
auto parts = StringHelper::split(windowData, ',');
|
||||
std::string addr = "0x" + parts[0];
|
||||
int newWorkspaceId = std::stoi(parts[1]);
|
||||
|
||||
if (this->clients.find(addr) == this->clients.end()) {
|
||||
std::cerr << "[Hyprland] onMoveWindow: Client not found: " << addr << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
auto clientPtr = this->clients[addr];
|
||||
int oldWorkspaceId = clientPtr->workspaceId;
|
||||
|
||||
auto oldWorkspacePtr = workspaces[oldWorkspaceId];
|
||||
oldWorkspacePtr->state->clients.erase(addr);
|
||||
refreshIndicator(oldWorkspacePtr);
|
||||
|
||||
clientPtr->workspaceId = newWorkspaceId;
|
||||
|
||||
auto newWorkspacePtr = workspaces[newWorkspaceId];
|
||||
newWorkspacePtr->state->clients[addr] = clientPtr;
|
||||
refreshIndicator(newWorkspacePtr);
|
||||
}
|
||||
|
||||
// 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];
|
||||
@@ -250,6 +273,10 @@ void HyprlandService::handleSocketMessage(SocketHelper::SocketMessage message) {
|
||||
this->onMonitorRemoved(eventData);
|
||||
break;
|
||||
}
|
||||
case MOVE_WINDOW: {
|
||||
this->onMoveWindow(eventData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void HyprlandService::onUrgent(std::string windowAddress) {
|
||||
|
||||
20
src/widgets/controlCenter/controlCenter.cpp
Normal file
20
src/widgets/controlCenter/controlCenter.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "widgets/controlCenter/controlCenter.hpp"
|
||||
|
||||
|
||||
ControlCenter::ControlCenter(std::string icon, std::string name)
|
||||
: Popover(icon, name) {
|
||||
this->popover->add_css_class("control-center-popover");
|
||||
this->container.set_orientation(Gtk::Orientation::VERTICAL);
|
||||
this->container.set_spacing(0);
|
||||
this->container.set_margin_top(0);
|
||||
this->container.set_margin_bottom(0);
|
||||
this->container.set_margin_start(0);
|
||||
this->container.set_margin_end(0);
|
||||
|
||||
set_popover_child(this->container);
|
||||
|
||||
|
||||
this->container.append(this->mediaControlWidget);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,37 +1,28 @@
|
||||
#include "widgets/controlCenter.hpp"
|
||||
#include "widgets/controlCenter/mediaControl.hpp"
|
||||
|
||||
#include "services/textureCache.hpp"
|
||||
|
||||
ControlCenter::ControlCenter(std::string icon, std::string name)
|
||||
: Popover(icon, name) {
|
||||
this->popover->add_css_class("control-center-popover");
|
||||
this->container.set_orientation(Gtk::Orientation::VERTICAL);
|
||||
this->container.set_spacing(0);
|
||||
this->container.set_margin_top(0);
|
||||
this->container.set_margin_bottom(0);
|
||||
this->container.set_margin_start(0);
|
||||
this->container.set_margin_end(0);
|
||||
this->container.append(this->spotifyContainer);
|
||||
MediaControlWidget::MediaControlWidget()
|
||||
: Gtk::Box(Gtk::Orientation::VERTICAL) {
|
||||
|
||||
set_popover_child(this->container);
|
||||
this->set_orientation(Gtk::Orientation::VERTICAL);
|
||||
this->set_size_request(200, 240);
|
||||
this->set_hexpand(false);
|
||||
this->set_vexpand(false);
|
||||
this->add_css_class("control-center-spotify-container");
|
||||
|
||||
this->spotifyContainer.set_orientation(Gtk::Orientation::VERTICAL);
|
||||
this->spotifyContainer.set_size_request(200, 240);
|
||||
this->spotifyContainer.set_hexpand(false); // Important: Don't let the main box expand freely
|
||||
this->spotifyContainer.set_vexpand(false);
|
||||
this->spotifyContainer.add_css_class("control-center-spotify-container");
|
||||
|
||||
this->spotifyContainer.append(this->topContainer);
|
||||
this->spotifyContainer.append(this->seekBarContainer);
|
||||
this->spotifyContainer.append(this->bottomContainer);
|
||||
this->append(this->topContainer);
|
||||
this->append(this->seekBarContainer);
|
||||
this->append(this->bottomContainer);
|
||||
|
||||
this->backgroundImage.set_content_fit(Gtk::ContentFit::COVER);
|
||||
this->backgroundImage.set_can_shrink(true);
|
||||
|
||||
this->imageWrapper.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::NEVER);
|
||||
this->imageWrapper.set_child(this->backgroundImage);
|
||||
|
||||
this->topContainer.set_child(this->imageWrapper);
|
||||
|
||||
|
||||
this->topContainer.set_child(this->imageWrapper);
|
||||
|
||||
this->topContainer.set_size_request(200, 100);
|
||||
this->topContainer.set_vexpand(false);
|
||||
this->topContainer.set_hexpand(true);
|
||||
@@ -44,8 +35,7 @@ ControlCenter::ControlCenter(std::string icon, std::string name)
|
||||
this->topContainer.add_overlay(this->infoContainer);
|
||||
|
||||
this->artistLabel.set_halign(Gtk::Align::START);
|
||||
this->titleLabel.set_halign(Gtk::Align::START);
|
||||
|
||||
this->titleLabel.set_halign(Gtk::Align::START);
|
||||
|
||||
this->seekBarContainer.set_orientation(Gtk::Orientation::HORIZONTAL);
|
||||
this->seekBarContainer.set_vexpand(false);
|
||||
@@ -68,6 +58,13 @@ ControlCenter::ControlCenter(std::string icon, std::string name)
|
||||
this->seekBar.set_halign(Gtk::Align::CENTER);
|
||||
this->seekBar.add_css_class("control-center-seek-bar");
|
||||
|
||||
this->seekBar.signal_value_changed().connect([this]() {
|
||||
double fraction = this->seekBar.get_value() / 100.0;
|
||||
int64_t new_position_us =
|
||||
static_cast<int64_t>(fraction * static_cast<double>(this->totalLengthUs));
|
||||
this->mprisController->emit_seeked(new_position_us); // in ms
|
||||
this->resetSeekTimer(new_position_us);
|
||||
});
|
||||
|
||||
this->bottomContainer.set_orientation(Gtk::Orientation::HORIZONTAL);
|
||||
this->bottomContainer.set_vexpand(false);
|
||||
@@ -101,20 +98,70 @@ ControlCenter::ControlCenter(std::string icon, std::string name)
|
||||
});
|
||||
|
||||
this->mprisController->signal_mpris_updated().connect(
|
||||
sigc::mem_fun(*this, &ControlCenter::onSpotifyMprisUpdated)
|
||||
);
|
||||
sigc::mem_fun(*this, &MediaControlWidget::onSpotifyMprisUpdated));
|
||||
|
||||
this->artistLabel.set_text("Artist Name");
|
||||
this->artistLabel.add_css_class("control-center-spotify-artist-label");
|
||||
this->titleLabel.set_text("Song Title");
|
||||
this->titleLabel.add_css_class("control-center-spotify-title-label");
|
||||
|
||||
this->resetSeekTimer(0);
|
||||
}
|
||||
|
||||
void ControlCenter::onSpotifyMprisUpdated(const MprisPlayer2Message &message) {
|
||||
void MediaControlWidget::onSpotifyMprisUpdated(const MprisPlayer2Message &message) {
|
||||
this->artistLabel.set_text(message.artist);
|
||||
this->titleLabel.set_text(message.title);
|
||||
|
||||
|
||||
if (auto texture = TextureCacheService::getInstance()->getTexture(message.artwork_url)) {
|
||||
this->backgroundImage.set_paintable(texture);
|
||||
}
|
||||
|
||||
this->setTotalLength(message.length_ms);
|
||||
this->setCurrentPosition(0);
|
||||
this->resetSeekTimer(0);
|
||||
}
|
||||
|
||||
void MediaControlWidget::setCurrentPosition(int64_t position_us) {
|
||||
this->currentPositionUs = position_us;
|
||||
int64_t seconds = (position_us / 1000000) % 60;
|
||||
int64_t minutes = (position_us / (1000000 * 60)) % 60;
|
||||
this->currentTimeLabel.set_text(
|
||||
std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + std::to_string(seconds));
|
||||
if (totalLengthUs > 0) {
|
||||
double fraction = static_cast<double>(currentPositionUs) / static_cast<double>(totalLengthUs);
|
||||
this->seekBar.set_value(fraction * 100);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaControlWidget::setTotalLength(int64_t length_us) {
|
||||
this->totalLengthUs = length_us;
|
||||
int64_t seconds = (length_us / 1000000) % 60;
|
||||
int64_t minutes = (length_us / (1000000 * 60)) % 60;
|
||||
this->totalTimeLabel.set_text(
|
||||
std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + std::to_string(seconds));
|
||||
}
|
||||
|
||||
void MediaControlWidget::resetSeekTimer(int64_t start_position_us) {
|
||||
if (seekTimerConnection.connected()) {
|
||||
seekTimerConnection.disconnect();
|
||||
}
|
||||
|
||||
setCurrentPosition(start_position_us);
|
||||
|
||||
seekTimerConnection = Glib::signal_timeout().connect(
|
||||
sigc::mem_fun(*this, &MediaControlWidget::onSeekTick),
|
||||
1000);
|
||||
}
|
||||
|
||||
bool MediaControlWidget::onSeekTick() {
|
||||
if (totalLengthUs <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t nextPosition = currentPositionUs + 1000000;
|
||||
if (nextPosition > totalLengthUs) {
|
||||
nextPosition = totalLengthUs;
|
||||
}
|
||||
setCurrentPosition(nextPosition);
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user