diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..400828b --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,34 @@ +Checks: 'clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type' +WarningsAsErrors: true +HeaderFilterRegex: '' +FormatStyle: google +CheckOptions: + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: '0' + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: '1' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + diff --git a/CMakeLists.txt b/CMakeLists.txt index 046c5dd..b4963ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ target_sources(bar_lib src/bar/bar.cpp + src/components/mediaPlayer.cpp src/components/button/iconButton.cpp src/components/button/textButton.cpp src/components/button/tabButton.cpp @@ -54,21 +55,22 @@ target_sources(bar_lib src/widgets/notification/copyNotification.cpp src/widgets/notification/notificationWindow.cpp src/widgets/notification/spotifyNotification.cpp + src/widgets/controlCenter/controlCenter.cpp + src/widgets/controlCenter/mediaWidget.cpp src/widgets/volumeWidget.cpp src/widgets/weather.cpp src/widgets/webWidget.cpp + src/widgets/tray.cpp src/services/hyprland.cpp src/services/notificationController.cpp src/services/textureCache.cpp - src/widgets/tray.cpp - src/widgets/controlCenter/controlCenter.cpp - src/widgets/controlCenter/mediaControl.cpp src/components/popover.cpp src/components/workspaceIndicator.cpp ) + include_directories(bar_lib PRIVATE include ) diff --git a/include/app.hpp b/include/app.hpp index 2dea63c..d97883b 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -3,9 +3,9 @@ #include #include "bar/bar.hpp" -#include "services/hyprland.hpp" #include "connection/dbus/notification.hpp" #include "connection/dbus/tray.hpp" +#include "services/hyprland.hpp" #include "glibmm/refptr.h" #include "gtkmm/application.h" @@ -22,8 +22,8 @@ class App { Glib::RefPtr app; std::vector> bars; std::shared_ptr notificationService = nullptr; - std::shared_ptr mprisController = nullptr; - HyprlandService *hyprlandService = nullptr; + std::shared_ptr mprisController = nullptr; + HyprlandService *hyprlandService = nullptr; TrayService *trayService = TrayService::getInstance(); void setupServices(); diff --git a/include/bar/bar.hpp b/include/bar/bar.hpp index e8efee6..bc5b9b3 100644 --- a/include/bar/bar.hpp +++ b/include/bar/bar.hpp @@ -6,11 +6,11 @@ #include "components/button/iconButton.hpp" #include "widgets/clock.hpp" +#include "widgets/controlCenter/controlCenter.hpp" #include "widgets/date.hpp" #include "widgets/tray.hpp" #include "widgets/volumeWidget.hpp" #include "widgets/webWidget.hpp" -#include "widgets/controlCenter/controlCenter.hpp" class Bar : public Gtk::Window { public: @@ -29,11 +29,10 @@ class Bar : public Gtk::Window { Clock clock; Date date; WebWidget homeAssistant{Icon::HOME_ASSISTANT, "Home Assistant", "https://home.rivercry.com"}; - ControlCenter controlCenter{Icon::CONTROL_CENTER, "Control Center"}; - - std::shared_ptr trayWidget = nullptr; - std::shared_ptr volumeWidget = nullptr; + ControlCenter controlCenter{Icon::MENU, "Control Center"}; + std::shared_ptr trayWidget = nullptr; + std::shared_ptr volumeWidget = nullptr; void setup_ui(); void setup_left_box(); diff --git a/include/components/button/iconButton.hpp b/include/components/button/iconButton.hpp index 99e52f1..1931f64 100644 --- a/include/components/button/iconButton.hpp +++ b/include/components/button/iconButton.hpp @@ -6,7 +6,7 @@ #include "components/types/icon.hpp" class IconButton : public TextButton { - public: - IconButton(Icon::Type icon, std::string fontFamilyCss = "materia-icons"); - void setIcon(Icon::Type icon); + public: + IconButton(Icon::Type icon, std::string fontFamilyCss = "materia-icons"); + void setIcon(Icon::Type icon); }; \ No newline at end of file diff --git a/include/components/button/tabButton.hpp b/include/components/button/tabButton.hpp index 4187dd0..68a8adc 100644 --- a/include/components/button/tabButton.hpp +++ b/include/components/button/tabButton.hpp @@ -3,7 +3,7 @@ #include "components/button/iconButton.hpp" class TabButton : public IconButton { - public: - TabButton(Icon::Type icon); - void setActive(bool active); + public: + TabButton(Icon::Type icon); + void setActive(bool active); }; \ No newline at end of file diff --git a/include/components/button/textButton.hpp b/include/components/button/textButton.hpp index a40b868..5a0847f 100644 --- a/include/components/button/textButton.hpp +++ b/include/components/button/textButton.hpp @@ -1,7 +1,5 @@ #pragma once - - #include #include "gtkmm/button.h" diff --git a/include/widgets/controlCenter/mediaControl.hpp b/include/components/mediaPlayer.hpp similarity index 87% rename from include/widgets/controlCenter/mediaControl.hpp rename to include/components/mediaPlayer.hpp index 391e960..517a89c 100644 --- a/include/widgets/controlCenter/mediaControl.hpp +++ b/include/components/mediaPlayer.hpp @@ -1,18 +1,17 @@ #pragma once +#include "components/button/iconButton.hpp" +#include "connection/dbus/mpris.hpp" + #include "gtkmm/box.h" -#include "gtkmm/button.h" #include "gtkmm/label.h" #include "gtkmm/overlay.h" #include "gtkmm/picture.h" #include "gtkmm/scale.h" #include "gtkmm/scrolledwindow.h" -#include "components/button/iconButton.hpp" -#include "connection/dbus/mpris.hpp" - -class MediaControlWidget : public Gtk::Box { - public: - explicit MediaControlWidget(std::shared_ptr controller); +class MediaPlayer : public Gtk::Box { + public: + explicit MediaPlayer(std::shared_ptr controller); private: std::shared_ptr mprisController; @@ -23,7 +22,7 @@ class MediaControlWidget : public Gtk::Box { bool suppressSeekSignal = false; std::string currentTrackId; MprisController::PlaybackStatus playbackStatus = MprisController::PlaybackStatus::Stopped; - bool canSeek = true; + bool canSeek = true; void setCurrentPosition(int64_t position_us); void setTotalLength(int64_t length_us); @@ -32,8 +31,6 @@ class MediaControlWidget : public Gtk::Box { void schedulePauseAfterSeek(); void setCanSeek(bool can_seek); - Gtk::Box spotifyContainer; - // image as background, artist, title Gtk::Overlay topContainer; Gtk::Picture backgroundImage; diff --git a/include/components/popover.hpp b/include/components/popover.hpp index c75e6bf..63b2139 100644 --- a/include/components/popover.hpp +++ b/include/components/popover.hpp @@ -3,6 +3,7 @@ #include #include #include + #include "components/button/iconButton.hpp" #include "components/button/textButton.hpp" diff --git a/include/components/tab.hpp b/include/components/tab.hpp new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/include/components/tab.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/include/components/types/icon.hpp b/include/components/types/icon.hpp index 815b893..7e8e34e 100644 --- a/include/components/types/icon.hpp +++ b/include/components/types/icon.hpp @@ -4,12 +4,10 @@ #include class Icon { - - public: + public: enum Type { - HOME_ASSISTANT, - CONTROL_CENTER, + MENU, SKIP_PREVIOUS, SKIP_NEXT, @@ -22,6 +20,8 @@ class Icon { SAVE, CONTENT_COPY, + + TOKEN, }; static const std::string toString(Type type) { @@ -31,7 +31,7 @@ class Icon { private: static inline std::map typeToString = { {HOME_ASSISTANT, "\uf024"}, - {CONTROL_CENTER, "\ue062"}, + {MENU, "\ue5d2"}, {SKIP_PREVIOUS, "\ue045"}, {SKIP_NEXT, "\ue044"}, @@ -44,5 +44,7 @@ class Icon { {SAVE, "\ue161"}, {CONTENT_COPY, "\ue14d"}, + + {TOKEN, "\uea25"}, }; }; \ No newline at end of file diff --git a/include/components/workspaceIndicator.hpp b/include/components/workspaceIndicator.hpp index 42bc525..587df4e 100644 --- a/include/components/workspaceIndicator.hpp +++ b/include/components/workspaceIndicator.hpp @@ -8,7 +8,6 @@ class WorkspaceIndicator : public Gtk::Box { public: - enum InidicatorState { EMPTY, ALIVE, diff --git a/include/connection/dbus/dbus.hpp b/include/connection/dbus/dbus.hpp index 6d52f7f..c14846a 100644 --- a/include/connection/dbus/dbus.hpp +++ b/include/connection/dbus/dbus.hpp @@ -5,20 +5,20 @@ class DbusConnection { public: - virtual ~DbusConnection() = default; + virtual ~DbusConnection() = default; protected: - Glib::RefPtr connection; + Glib::RefPtr connection; - void connect_session_async(const sigc::slot &)> &callback) { - Gio::DBus::Connection::get(Gio::DBus::BusType::SESSION, callback); - } + void connect_session_async(const sigc::slot &)> &callback) { + Gio::DBus::Connection::get(Gio::DBus::BusType::SESSION, callback); + } - static void ensure_gio_init() { - try { - Gio::init(); - } catch (const Glib::Error &) { - // Already initialized. - } - } + static void ensure_gio_init() { + try { + Gio::init(); + } catch (const Glib::Error &) { + // Already initialized. + } + } }; diff --git a/include/connection/dbus/messages.hpp b/include/connection/dbus/messages.hpp index 978fb05..f820b2c 100644 --- a/include/connection/dbus/messages.hpp +++ b/include/connection/dbus/messages.hpp @@ -7,6 +7,7 @@ #include #include #include + #include "gdkmm/pixbuf.h" #include "glibmm/variant.h" @@ -23,8 +24,8 @@ struct MprisPlayer2Message { }; enum NotificationUrgency { - LOW = 0, - NORMAL = 1, + LOW = 0, + NORMAL = 1, CRITICAL = 2 }; @@ -38,7 +39,7 @@ struct NotifyMessage { NotificationUrgency urgency = NORMAL; int32_t expire_timeout; // Callback to invoke when an action is triggered - std::function on_action; + std::function on_action; // Guard to prevent multiple action invocations across mirrors std::shared_ptr actionInvoked; // image data (if any) from dbus diff --git a/include/connection/dbus/mpris.hpp b/include/connection/dbus/mpris.hpp index 55560e9..1f49921 100644 --- a/include/connection/dbus/mpris.hpp +++ b/include/connection/dbus/mpris.hpp @@ -2,9 +2,10 @@ #include #include -#include #include +#include #include + #include "connection/dbus/dbus.hpp" #include "connection/dbus/messages.hpp" @@ -56,7 +57,7 @@ class MprisController : public DbusConnection { Glib::RefPtr m_dbus_proxy; std::string m_player_bus_name = "org.mpris.MediaPlayer2.spotify"; std::set registeredPlayers; - + sigc::signal mprisUpdatedSignal; sigc::signal playbackStatusChangedSignal; sigc::signal playbackPositionChangedSignal; @@ -70,8 +71,8 @@ class MprisController : public DbusConnection { 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); + 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); diff --git a/include/connection/dbus/notification.hpp b/include/connection/dbus/notification.hpp index ec7f4d1..d4a2246 100644 --- a/include/connection/dbus/notification.hpp +++ b/include/connection/dbus/notification.hpp @@ -6,6 +6,7 @@ #include #include "connection/dbus/dbus.hpp" + #include "giomm/dbusconnection.h" #include "giomm/dbusownname.h" #include "glib.h" diff --git a/include/connection/dbus/tray.hpp b/include/connection/dbus/tray.hpp index 79d0d2b..943954c 100644 --- a/include/connection/dbus/tray.hpp +++ b/include/connection/dbus/tray.hpp @@ -19,6 +19,7 @@ class TrayService : public DbusConnection { inline static TrayService *instance = nullptr; + public: struct Item { std::string id; @@ -66,7 +67,7 @@ class TrayService : public DbusConnection { if (TrayService::instance == nullptr) { TrayService::instance = new TrayService(); } - + return TrayService::instance; } @@ -137,7 +138,7 @@ class TrayService : public DbusConnection { 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); + gpointer user_data); void emit_registered_items_changed(); Glib::Variant> diff --git a/include/helpers/command.hpp b/include/helpers/command.hpp index 5a9fb72..95e9e38 100644 --- a/include/helpers/command.hpp +++ b/include/helpers/command.hpp @@ -1,17 +1,17 @@ #pragma once #include +#include #include #include #include -#include class CommandHelper { public: static std::string exec(const char *cmd) { std::array buffer; std::string result; - std::unique_ptr pipe(popen(cmd, "r"), pclose); + std::unique_ptr pipe(popen(cmd, "r"), pclose); if (!pipe) { throw std::runtime_error("popen() failed!"); } @@ -23,7 +23,7 @@ class CommandHelper { static void execNoOutput(std::string cmd) { std::string command = cmd + " > /dev/null 2>&1"; - int ret = std::system(command.c_str()); + int ret = std::system(command.c_str()); if (ret != 0) { throw std::runtime_error("Command failed with return code: " + std::to_string(ret)); } diff --git a/include/helpers/hypr.hpp b/include/helpers/hypr.hpp index aefea66..4069ffc 100644 --- a/include/helpers/hypr.hpp +++ b/include/helpers/hypr.hpp @@ -52,14 +52,14 @@ class HyprSocketHelper { public: static std::string getHyprlandSocketPath() { const char *hyprlandInstanceSignature = std::getenv("HYPRLAND_INSTANCE_SIGNATURE"); - const char *xdgRuntimeDir = std::getenv("XDG_RUNTIME_DIR"); + const char *xdgRuntimeDir = std::getenv("XDG_RUNTIME_DIR"); if (!xdgRuntimeDir || !hyprlandInstanceSignature) { return std::string(); } std::string basePath = std::string(xdgRuntimeDir) + "/hypr/" + std::string(hyprlandInstanceSignature) + "/"; - std::string sock1 = basePath + ".socket2.sock"; + std::string sock1 = basePath + ".socket2.sock"; if (access(sock1.c_str(), F_OK) == 0) { return sock1; } diff --git a/include/helpers/string.hpp b/include/helpers/string.hpp index 4375324..7908618 100644 --- a/include/helpers/string.hpp +++ b/include/helpers/string.hpp @@ -3,9 +3,8 @@ #include #include - class StringHelper { -public: + public: static std::vector split(const std::string &input, char delimiter) { std::vector tokens; std::string token; @@ -25,14 +24,14 @@ public: return tokens; } - static std::vector split(const std::string &input, std::string delimiter) { + static std::vector split(const std::string &input, std::string delimiter) { std::vector tokens; size_t start = 0; - size_t end = input.find(delimiter); + size_t end = input.find(delimiter); while (end != std::string::npos) { tokens.push_back(input.substr(start, end - start)); start = end + delimiter.length(); - end = input.find(delimiter, start); + end = input.find(delimiter, start); } tokens.push_back(input.substr(start)); return tokens; diff --git a/include/helpers/system.hpp b/include/helpers/system.hpp index 343812d..93c59f2 100644 --- a/include/helpers/system.hpp +++ b/include/helpers/system.hpp @@ -10,7 +10,7 @@ class SystemHelper { throw std::runtime_error("Could not open file: " + filePath); } std::string content((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); + std::istreambuf_iterator()); file.close(); return content; } diff --git a/include/services/bluetooth.hpp b/include/services/bluetooth.hpp index aa353d5..a45bf50 100644 --- a/include/services/bluetooth.hpp +++ b/include/services/bluetooth.hpp @@ -8,8 +8,8 @@ #include "sigc++/signal.h" class BluetoothService { - inline static BluetoothService *instance = nullptr; - + inline static BluetoothService *instance = nullptr; + public: sigc::signal powerStateChangedSignal; sigc::signal isDiscoveringChangedSignal; diff --git a/include/services/timeSignal.hpp b/include/services/timeSignal.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/widgets/bluetooth.hpp b/include/widgets/bluetooth.hpp index b940dd2..6bb6a0a 100644 --- a/include/widgets/bluetooth.hpp +++ b/include/widgets/bluetooth.hpp @@ -6,8 +6,6 @@ #include "components/base/button.hpp" - - class BluetoothEntry : Gtk::Box { public: BluetoothEntry(std::string name, std::string address) { @@ -38,7 +36,6 @@ class BluetoothEntry : Gtk::Box { sigc::signal connect_clicked; }; - class BluetoothWidget : public Gtk::Box { public: BluetoothWidget(); diff --git a/include/widgets/controlCenter/controlCenter.hpp b/include/widgets/controlCenter/controlCenter.hpp index 7b6e384..e9c7435 100644 --- a/include/widgets/controlCenter/controlCenter.hpp +++ b/include/widgets/controlCenter/controlCenter.hpp @@ -1,35 +1,36 @@ #pragma once +#include + #include "components/button/iconButton.hpp" #include "components/button/tabButton.hpp" #include "components/popover.hpp" +#include "widgets/controlCenter/mediaWidget.hpp" +#include "widgets/weather.hpp" + #include "gtkmm/box.h" #include "gtkmm/scrolledwindow.h" #include "gtkmm/stack.h" -#include "widgets/controlCenter/mediaControl.hpp" -#include "widgets/weather.hpp" - -#include -#include class ControlCenter : public Popover { - public: - ControlCenter(Icon::Type icon, std::string name); - - private: - Gtk::ScrolledWindow scrollview; - Gtk::Box container; - Gtk::Box tabRow; - Gtk::Stack contentStack; - Gtk::Box controlCenterContainer; - WeatherWidget weatherWidget; - std::unique_ptr mediaControl; - std::unique_ptr testTabButton; - std::shared_ptr mprisController = MprisController::getInstance(); - std::unordered_map mediaWidgets; + public: + ControlCenter(Icon::Type icon, std::string name); - void addPlayerWidget(const std::string &bus_name); - void removePlayerWidget(const std::string &bus_name); - void setActiveTab(const std::string &tab_name); + private: + Gtk::ScrolledWindow scrollview; + Gtk::Box container; + Gtk::Box tabRow; + Gtk::Stack contentStack; + + std::unique_ptr mediaTabButton; + std::unique_ptr infoTabButton; + std::unique_ptr timerButton; + + std::unique_ptr weatherWidget; + std::unique_ptr mediaControlWidget; + + void addPlayerWidget(const std::string &bus_name); + void removePlayerWidget(const std::string &bus_name); + void setActiveTab(const std::string &tab_name); }; \ No newline at end of file diff --git a/include/widgets/controlCenter/mediaWidget.hpp b/include/widgets/controlCenter/mediaWidget.hpp new file mode 100644 index 0000000..0065643 --- /dev/null +++ b/include/widgets/controlCenter/mediaWidget.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "components/mediaPlayer.hpp" +#include +#include +#include + +#include "connection/dbus/mpris.hpp" + +#include "gtkmm/box.h" + +class MediaWidget : public Gtk::Box { + public: + MediaWidget(); + + private: + Gtk::Box container; + + std::shared_ptr mprisController = MprisController::getInstance(); + void addPlayerWidget(const std::string &bus_name); + void removePlayerWidget(const std::string &bus_name); + + std::map> mediaWidgets; +}; \ No newline at end of file diff --git a/include/widgets/notification/baseNotification.hpp b/include/widgets/notification/baseNotification.hpp index 0d2f7f7..4734806 100644 --- a/include/widgets/notification/baseNotification.hpp +++ b/include/widgets/notification/baseNotification.hpp @@ -1,12 +1,10 @@ #pragma once #include -#include #include #include #include "gdkmm/monitor.h" -#include "gtkmm/scrolledwindow.h" #include "gtkmm/window.h" #define DEFAULT_NOTIFICATION_TIMEOUT 6700 @@ -19,11 +17,9 @@ class BaseNotification : public Gtk::Window { void resumeAutoClose(); void startAutoClose(int timeoutMs); - sigc::signal signal_close; + sigc::signal signal_close; sigc::signal signal_hover_changed; - virtual ~BaseNotification() = default; - uint64_t getNotificationId() const { return this->notificationId; } diff --git a/include/widgets/notification/copyNotification.hpp b/include/widgets/notification/copyNotification.hpp index 567e022..05fdd02 100644 --- a/include/widgets/notification/copyNotification.hpp +++ b/include/widgets/notification/copyNotification.hpp @@ -4,6 +4,7 @@ #include "connection/dbus/messages.hpp" #include "widgets/notification/baseNotification.hpp" + #include "gtkmm/box.h" class CopyNotification : public BaseNotification { diff --git a/include/widgets/notification/notificationWindow.hpp b/include/widgets/notification/notificationWindow.hpp index 1b23df0..d95b8de 100644 --- a/include/widgets/notification/notificationWindow.hpp +++ b/include/widgets/notification/notificationWindow.hpp @@ -2,6 +2,7 @@ #pragma once #include + #include "connection/dbus/messages.hpp" #include "widgets/notification/baseNotification.hpp" @@ -9,5 +10,4 @@ class NotificationWindow : public BaseNotification { public: NotificationWindow(uint64_t notificationId, std::shared_ptr monitor, NotifyMessage message); virtual ~NotificationWindow() = default; - }; diff --git a/include/widgets/tray.hpp b/include/widgets/tray.hpp index ebccd97..b17f6d8 100644 --- a/include/widgets/tray.hpp +++ b/include/widgets/tray.hpp @@ -39,12 +39,12 @@ class TrayIconWidget : public IconButton { Glib::RefPtr menuPopover; Glib::RefPtr menuActions; Glib::RefPtr menuModel; - bool menuPopupPending = false; + bool menuPopupPending = false; bool menuRequestInFlight = false; - bool hasRemoteMenu = false; + bool hasRemoteMenu = false; std::shared_ptr aliveFlag; - double pendingX = 0.0; - double pendingY = 0.0; + double pendingX = 0.0; + double pendingY = 0.0; void on_primary_released(int n_press, double x, double y); void on_middle_released(int n_press, double x, double y); diff --git a/resources/bar.css b/resources/bar.css index 9998ea0..3378a1f 100644 --- a/resources/bar.css +++ b/resources/bar.css @@ -7,18 +7,16 @@ :root { --icon-font-material: "Material Icons", sans-serif; --icon-font-awesome: "Font Awesome 7 Free", sans-serif; - --text-font: "Hack Nerd Font Mono", sans-serif; + --text-font: "Hack Nerd Font", sans-serif; --text-font-mono: "Hack Nerd Font Mono", monospace; } window { background-color: #191919c6; color: #ffffff; - padding-left: 4px; - padding-right: 4px; - padding-bottom: 2px; font-size: 14px; font-family: var(--text-font); + padding: 2px 6px; } .material-icons { @@ -26,20 +24,16 @@ window { } .icon-button { - font-size: 16px; + font-size: 20px; } .tab-icon { font-size: 20px; - margin-right: 6px; - border-radius: 4px; - border: 1px solid transparent; - padding: 2px 4px; } .control-center-tab-row { background-color: rgba(50, 50, 50, 0.8); - border-radius: 8px; + border-radius: 4px; padding: 2px 4px; margin-bottom: 4px; } @@ -54,25 +48,24 @@ window { popover { margin-top: 4px; font-family: var(--text-font); - border-radius: 8px; + border-radius: 4px; background: rgba(25, 25, 25, 0.8); box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); border: 1px solid rgba(57, 57, 57, 0.71); - font-size: 14px; } .control-center-popover { background-color: rgba(35, 35, 35, 0.95); padding: 12px; padding-top : 6px; - border-radius: 8px; + border-radius: 4px; box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3); border: 1px solid rgba(57, 57, 57, 0.8); } .control-center-player-container { - border-radius: 8px; + border-radius: 4px; background: rgba(35, 35, 35, 0.95); margin-bottom: 6px; } @@ -131,11 +124,10 @@ tooltip { font-size: 12px; } -button { +.button { font-size: 14px; padding: 4px 8px; border-radius: 4px; - color: #ffffff; transition: background-color 0.2s, color 0.2s, @@ -157,7 +149,7 @@ button { .workspace-pill { padding: 2px 5px; margin-right: 6px; - border-radius: 5px; + border-radius: 4px; text-shadow: 0 0 2px #646464; transition: background-color 0.2s, diff --git a/src/app.cpp b/src/app.cpp index aa548c9..c1ca47c 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -2,17 +2,18 @@ #include #include -#include "connection/dbus/notification.hpp" + #include "connection/dbus/mpris.hpp" +#include "connection/dbus/notification.hpp" #include "services/notificationController.hpp" #include "services/textureCache.hpp" App::App() { this->app = Gtk::Application::create("org.example.mybar"); this->setupServices(); - this->hyprlandService = HyprlandService::getInstance(); + this->hyprlandService = HyprlandService::getInstance(); this->notificationService = std::make_shared(); - this->mprisController = MprisController::getInstance(); + this->mprisController = MprisController::getInstance(); auto notificationController = NotificationController::getInstance(); this->mprisController->signal_mpris_updated().connect( @@ -26,8 +27,7 @@ App::App() { for (guint i = 0; i < monitors->get_n_items(); ++i) { auto monitor = std::dynamic_pointer_cast( - monitors->get_object(i) - ); + monitors->get_object(i)); if (monitor) { auto bar = std::make_shared(monitor->gobj()); @@ -44,7 +44,6 @@ App::App() { } }); - app->signal_shutdown().connect([&]() { this->trayService->stop(); }); diff --git a/src/bar/bar.cpp b/src/bar/bar.cpp index b5748fc..eb61508 100644 --- a/src/bar/bar.cpp +++ b/src/bar/bar.cpp @@ -33,8 +33,8 @@ Bar::Bar(GdkMonitor *monitor) { set_child(main_box); - this->volumeWidget = std::make_shared(); - this->trayWidget = std::make_shared(); + this->volumeWidget = std::make_shared(); + this->trayWidget = std::make_shared(); load_css(); setup_ui(); @@ -43,7 +43,7 @@ Bar::Bar(GdkMonitor *monitor) { date.onUpdate(); Glib::signal_timeout().connect(sigc::mem_fun(clock, &Clock::onUpdate), 1000); - Glib::signal_timeout().connect(sigc::mem_fun(date, &Date::onUpdate), 1000); +Glib::signal_timeout().connect(sigc::mem_fun(date, &Date::onUpdate), 1000); } void Bar::setup_ui() { diff --git a/src/widgets/controlCenter/mediaControl.cpp b/src/components/mediaControl.cpp similarity index 86% rename from src/widgets/controlCenter/mediaControl.cpp rename to src/components/mediaControl.cpp index 6b54442..194d760 100644 --- a/src/widgets/controlCenter/mediaControl.cpp +++ b/src/components/mediaControl.cpp @@ -1,4 +1,4 @@ -#include "widgets/controlCenter/mediaControl.hpp" +#include "components/mediaPlayer.hpp" #include "components/button/iconButton.hpp" #include "helpers/string.hpp" @@ -23,10 +23,8 @@ std::string formatTimeUs(int64_t time_us) { } } // namespace -MediaControlWidget::MediaControlWidget(std::shared_ptr controller) - : Gtk::Box(Gtk::Orientation::VERTICAL) { - - this->mprisController = std::move(controller); +MediaPlayer::MediaPlayer(std::shared_ptr controller) + : Gtk::Box(Gtk::Orientation::VERTICAL), mprisController(controller) { this->set_orientation(Gtk::Orientation::VERTICAL); this->set_hexpand(false); @@ -107,9 +105,9 @@ MediaControlWidget::MediaControlWidget(std::shared_ptr controll } }); - this->previousButton = std::make_unique(Icon::SKIP_PREVIOUS); + this->previousButton = std::make_unique(Icon::SKIP_PREVIOUS); this->playPauseButton = std::make_unique(Icon::PLAY_ARROW); - this->nextButton = std::make_unique(Icon::SKIP_NEXT); + this->nextButton = std::make_unique(Icon::SKIP_NEXT); this->bottomContainer.set_orientation(Gtk::Orientation::HORIZONTAL); this->bottomContainer.set_vexpand(false); @@ -133,7 +131,7 @@ MediaControlWidget::MediaControlWidget(std::shared_ptr controll }); this->mprisController->signal_mpris_updated().connect( - sigc::mem_fun(*this, &MediaControlWidget::onSpotifyMprisUpdated)); + sigc::mem_fun(*this, &MediaPlayer::onSpotifyMprisUpdated)); this->mprisController->signal_playback_status_changed().connect( [this](MprisController::PlaybackStatus status) { @@ -158,12 +156,12 @@ MediaControlWidget::MediaControlWidget(std::shared_ptr controll this->resetSeekTimer(0); } -void MediaControlWidget::setCanSeek(bool can_seek) { +void MediaPlayer::setCanSeek(bool can_seek) { this->canSeek = can_seek; this->seekBarContainer.set_visible(can_seek); } -void MediaControlWidget::onSpotifyMprisUpdated(const MprisPlayer2Message &message) { +void MediaPlayer::onSpotifyMprisUpdated(const MprisPlayer2Message &message) { std::string artistText = "Unknown Artist"; if (!message.artist.empty()) { artistText = StringHelper::trimToSize(message.artist[0], 30); @@ -171,7 +169,7 @@ void MediaControlWidget::onSpotifyMprisUpdated(const MprisPlayer2Message &messag this->artistLabel.set_text(artistText); this->titleLabel.set_text(message.title); const bool trackChanged = !this->currentTrackId.empty() && this->currentTrackId != message.track_id; - this->currentTrackId = message.track_id; + this->currentTrackId = message.track_id; if (trackChanged) { this->currentPositionUs = 0; @@ -191,23 +189,23 @@ void MediaControlWidget::onSpotifyMprisUpdated(const MprisPlayer2Message &messag } } -void MediaControlWidget::setCurrentPosition(int64_t position_us) { +void MediaPlayer::setCurrentPosition(int64_t position_us) { this->currentPositionUs = position_us; this->currentTimeLabel.set_text(formatTimeUs(position_us)); if (totalLengthUs > 0) { - double fraction = static_cast(currentPositionUs) / static_cast(totalLengthUs); + double fraction = static_cast(currentPositionUs) / static_cast(totalLengthUs); this->suppressSeekSignal = true; this->seekBar.set_value(fraction * 100); this->suppressSeekSignal = false; } } -void MediaControlWidget::setTotalLength(int64_t length_us) { +void MediaPlayer::setTotalLength(int64_t length_us) { this->totalLengthUs = length_us; this->totalTimeLabel.set_text(formatTimeUs(length_us)); } -void MediaControlWidget::resetSeekTimer(int64_t start_position_us) { +void MediaPlayer::resetSeekTimer(int64_t start_position_us) { if (seekTimerConnection.connected()) { seekTimerConnection.disconnect(); } @@ -215,19 +213,20 @@ void MediaControlWidget::resetSeekTimer(int64_t start_position_us) { setCurrentPosition(start_position_us); seekTimerConnection = Glib::signal_timeout().connect( - sigc::mem_fun(*this, &MediaControlWidget::onSeekTick), + sigc::mem_fun(*this, &MediaPlayer::onSeekTick), 1000); } -void MediaControlWidget::schedulePauseAfterSeek() { +void MediaPlayer::schedulePauseAfterSeek() { Glib::signal_timeout().connect_once([this]() { if (this->playbackStatus != MprisController::PlaybackStatus::Playing) { this->mprisController->pause(); } - }, 100); + }, + 100); } -bool MediaControlWidget::onSeekTick() { +bool MediaPlayer::onSeekTick() { if (totalLengthUs <= 0) { return true; } @@ -240,7 +239,7 @@ bool MediaControlWidget::onSeekTick() { return true; } -void MediaControlWidget::onRunningStateChanged(MprisController::PlaybackStatus status) { +void MediaPlayer::onRunningStateChanged(MprisController::PlaybackStatus status) { this->playbackStatus = status; switch (status) { case MprisController::PlaybackStatus::Playing: @@ -255,20 +254,20 @@ void MediaControlWidget::onRunningStateChanged(MprisController::PlaybackStatus s } } -void MediaControlWidget::onPlay() { +void MediaPlayer::onPlay() { this->playPauseButton->setIcon(Icon::PAUSE); this->resetSeekTimer(currentPositionUs); } -void MediaControlWidget::onPause() { +void MediaPlayer::onPause() { this->playPauseButton->setIcon(Icon::PLAY_ARROW); - + if (seekTimerConnection.connected()) { seekTimerConnection.disconnect(); } } -void MediaControlWidget::onStop() { +void MediaPlayer::onStop() { this->playPauseButton->setIcon(Icon::PLAY_ARROW); if (seekTimerConnection.connected()) { diff --git a/src/components/mediaPlayer.cpp b/src/components/mediaPlayer.cpp new file mode 100644 index 0000000..194d760 --- /dev/null +++ b/src/components/mediaPlayer.cpp @@ -0,0 +1,277 @@ +#include "components/mediaPlayer.hpp" + +#include "components/button/iconButton.hpp" +#include "helpers/string.hpp" +#include "services/textureCache.hpp" + +namespace { +std::string formatTimeUs(int64_t time_us) { + if (time_us < 0) { + time_us = 0; + } + int64_t totalSeconds = time_us / 1000000; + int64_t hours = totalSeconds / 3600; + int64_t minutes = (totalSeconds / 60) % 60; + int64_t seconds = totalSeconds % 60; + + if (hours > 0) { + return std::to_string(hours) + ":" + + (minutes < 10 ? "0" : "") + std::to_string(minutes) + ":" + + (seconds < 10 ? "0" : "") + std::to_string(seconds); + } + return std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + std::to_string(seconds); +} +} // namespace + +MediaPlayer::MediaPlayer(std::shared_ptr controller) + : Gtk::Box(Gtk::Orientation::VERTICAL), mprisController(controller) { + + this->set_orientation(Gtk::Orientation::VERTICAL); + this->set_hexpand(false); + this->set_vexpand(false); + this->add_css_class("control-center-player-container"); + + 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_size_request(-1, 120); + this->topContainer.set_vexpand(false); + this->topContainer.set_hexpand(true); + + this->infoContainer.set_orientation(Gtk::Orientation::VERTICAL); + this->infoContainer.set_valign(Gtk::Align::END); + this->infoContainer.set_halign(Gtk::Align::START); + this->infoContainer.append(this->artistLabel); + this->infoContainer.append(this->titleLabel); + this->topContainer.add_overlay(this->infoContainer); + + this->artistLabel.set_halign(Gtk::Align::START); + this->titleLabel.set_halign(Gtk::Align::START); + + this->seekBarContainer.set_orientation(Gtk::Orientation::HORIZONTAL); + this->seekBarContainer.set_vexpand(false); + this->seekBarContainer.set_hexpand(true); + this->seekBarContainer.set_halign(Gtk::Align::CENTER); + this->seekBarContainer.append(this->currentTimeLabel); + this->seekBarContainer.append(this->seekBar); + this->seekBarContainer.append(this->totalTimeLabel); + this->seekBarContainer.set_visible(true); + + this->currentTimeLabel.set_text("0:00"); + this->currentTimeLabel.set_halign(Gtk::Align::START); + this->totalTimeLabel.set_text("0:00"); + this->totalTimeLabel.set_halign(Gtk::Align::END); + this->seekBar.set_range(0, 100); + this->seekBar.set_value(0); + this->seekBar.set_orientation(Gtk::Orientation::HORIZONTAL); + this->seekBar.set_draw_value(false); + this->seekBar.set_hexpand(true); + this->seekBar.set_halign(Gtk::Align::CENTER); + this->seekBar.add_css_class("control-center-seek-bar"); + + this->seekBar.signal_value_changed().connect([this]() { + if (this->suppressSeekSignal || this->totalLengthUs <= 0) { + return; + } + double fraction = this->seekBar.get_value() / 100.0; + int64_t new_position_us = + static_cast(fraction * static_cast(this->totalLengthUs)); + if (new_position_us == this->currentPositionUs) { + return; + } + if (!this->currentTrackId.empty()) { + this->mprisController->set_position(this->currentTrackId, new_position_us); + if (this->playbackStatus != MprisController::PlaybackStatus::Playing) { + this->schedulePauseAfterSeek(); + } + } else if (this->playbackStatus == MprisController::PlaybackStatus::Playing) { + this->mprisController->emit_seeked(new_position_us - this->currentPositionUs); // in us + } else { + return; + } + if (this->playbackStatus == MprisController::PlaybackStatus::Playing) { + this->resetSeekTimer(new_position_us); + } else { + this->setCurrentPosition(new_position_us); + } + }); + + this->previousButton = std::make_unique(Icon::SKIP_PREVIOUS); + this->playPauseButton = std::make_unique(Icon::PLAY_ARROW); + this->nextButton = std::make_unique(Icon::SKIP_NEXT); + + this->bottomContainer.set_orientation(Gtk::Orientation::HORIZONTAL); + this->bottomContainer.set_vexpand(false); + this->bottomContainer.set_hexpand(false); + this->bottomContainer.set_valign(Gtk::Align::START); + this->bottomContainer.set_homogeneous(true); + this->topContainer.set_vexpand(false); + this->topContainer.set_hexpand(true); + this->bottomContainer.append(*this->previousButton); + this->bottomContainer.append(*this->playPauseButton); + this->bottomContainer.append(*this->nextButton); + + this->previousButton->signal_clicked().connect([this]() { + this->mprisController->previous_song(); + }); + this->playPauseButton->signal_clicked().connect([this]() { + this->mprisController->toggle_play(); + }); + this->nextButton->signal_clicked().connect([this]() { + this->mprisController->next_song(); + }); + + this->mprisController->signal_mpris_updated().connect( + sigc::mem_fun(*this, &MediaPlayer::onSpotifyMprisUpdated)); + + this->mprisController->signal_playback_status_changed().connect( + [this](MprisController::PlaybackStatus status) { + this->onRunningStateChanged(status); + }); + + this->mprisController->signal_playback_position_changed().connect( + [this](int64_t position_us) { + this->setCurrentPosition(position_us); + }); + + this->mprisController->signal_can_seek_changed().connect( + [this](bool can_seek) { + this->setCanSeek(can_seek); + }); + + this->artistLabel.set_text("Artist Name"); + this->artistLabel.add_css_class("control-center-player-artist-label"); + this->titleLabel.set_text("Song Title"); + this->titleLabel.add_css_class("control-center-player-title-label"); + + this->resetSeekTimer(0); +} + +void MediaPlayer::setCanSeek(bool can_seek) { + this->canSeek = can_seek; + this->seekBarContainer.set_visible(can_seek); +} + +void MediaPlayer::onSpotifyMprisUpdated(const MprisPlayer2Message &message) { + std::string artistText = "Unknown Artist"; + if (!message.artist.empty()) { + artistText = StringHelper::trimToSize(message.artist[0], 30); + } + this->artistLabel.set_text(artistText); + this->titleLabel.set_text(message.title); + const bool trackChanged = !this->currentTrackId.empty() && this->currentTrackId != message.track_id; + this->currentTrackId = message.track_id; + + if (trackChanged) { + this->currentPositionUs = 0; + } + + if (auto texture = TextureCacheService::getInstance()->getTexture(message.artwork_url)) { + this->backgroundImage.set_paintable(texture); + } + + this->setTotalLength(message.length_ms * 1000); + this->setCurrentPosition(this->currentPositionUs); + + if (this->playbackStatus == MprisController::PlaybackStatus::Playing) { + this->resetSeekTimer(this->currentPositionUs); + } else if (seekTimerConnection.connected()) { + seekTimerConnection.disconnect(); + } +} + +void MediaPlayer::setCurrentPosition(int64_t position_us) { + this->currentPositionUs = position_us; + this->currentTimeLabel.set_text(formatTimeUs(position_us)); + if (totalLengthUs > 0) { + double fraction = static_cast(currentPositionUs) / static_cast(totalLengthUs); + this->suppressSeekSignal = true; + this->seekBar.set_value(fraction * 100); + this->suppressSeekSignal = false; + } +} + +void MediaPlayer::setTotalLength(int64_t length_us) { + this->totalLengthUs = length_us; + this->totalTimeLabel.set_text(formatTimeUs(length_us)); +} + +void MediaPlayer::resetSeekTimer(int64_t start_position_us) { + if (seekTimerConnection.connected()) { + seekTimerConnection.disconnect(); + } + + setCurrentPosition(start_position_us); + + seekTimerConnection = Glib::signal_timeout().connect( + sigc::mem_fun(*this, &MediaPlayer::onSeekTick), + 1000); +} + +void MediaPlayer::schedulePauseAfterSeek() { + Glib::signal_timeout().connect_once([this]() { + if (this->playbackStatus != MprisController::PlaybackStatus::Playing) { + this->mprisController->pause(); + } + }, + 100); +} + +bool MediaPlayer::onSeekTick() { + if (totalLengthUs <= 0) { + return true; + } + + int64_t nextPosition = currentPositionUs + 1000000; + if (nextPosition > totalLengthUs) { + nextPosition = totalLengthUs; + } + setCurrentPosition(nextPosition); + return true; +} + +void MediaPlayer::onRunningStateChanged(MprisController::PlaybackStatus status) { + this->playbackStatus = status; + switch (status) { + case MprisController::PlaybackStatus::Playing: + this->onPlay(); + break; + case MprisController::PlaybackStatus::Paused: + this->onPause(); + break; + case MprisController::PlaybackStatus::Stopped: + this->onStop(); + break; + } +} + +void MediaPlayer::onPlay() { + this->playPauseButton->setIcon(Icon::PAUSE); + this->resetSeekTimer(currentPositionUs); +} + +void MediaPlayer::onPause() { + this->playPauseButton->setIcon(Icon::PLAY_ARROW); + + if (seekTimerConnection.connected()) { + seekTimerConnection.disconnect(); + } +} + +void MediaPlayer::onStop() { + this->playPauseButton->setIcon(Icon::PLAY_ARROW); + + if (seekTimerConnection.connected()) { + seekTimerConnection.disconnect(); + } + this->setCurrentPosition(0); +} \ No newline at end of file diff --git a/src/components/popover.cpp b/src/components/popover.cpp index dffcdd7..81a1594 100644 --- a/src/components/popover.cpp +++ b/src/components/popover.cpp @@ -1,7 +1,8 @@ #include "components/popover.hpp" + #include "components/button/iconButton.hpp" -Popover::Popover(Icon::Type icon, std::string name): IconButton(icon) { +Popover::Popover(Icon::Type icon, std::string name) : IconButton(icon) { signal_clicked().connect(sigc::mem_fun(*this, &Popover::on_toggle_window)); set_name(name); diff --git a/src/components/tab.cpp b/src/components/tab.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/components/workspaceIndicator.cpp b/src/components/workspaceIndicator.cpp index 7797cef..4307f55 100644 --- a/src/components/workspaceIndicator.cpp +++ b/src/components/workspaceIndicator.cpp @@ -41,7 +41,7 @@ WorkspaceIndicator::WorkspaceIndicator(int id, std::string label, sigc::slotclearCssClass(); this->currentState = state; - auto cssClass = this->stateToCssClass[state]; + auto cssClass = this->stateToCssClass[state]; this->overlay->add_css_class(cssClass); } diff --git a/src/connection/dbus/mpris.cpp b/src/connection/dbus/mpris.cpp index 1f93d63..367b003 100644 --- a/src/connection/dbus/mpris.cpp +++ b/src/connection/dbus/mpris.cpp @@ -76,8 +76,8 @@ void MprisController::on_bus_connected(const Glib::RefPtr &res try { auto list_names_result = m_dbus_proxy->call_sync("ListNames"); - auto names_variant = Glib::VariantBase::cast_dynamic< - Glib::Variant>>(list_names_result.get_child(0)); + auto names_variant = Glib::VariantBase::cast_dynamic< + Glib::Variant>>(list_names_result.get_child(0)); for (const auto &name : names_variant.get()) { const std::string bus_name = name; @@ -103,7 +103,7 @@ void MprisController::on_dbus_signal(const Glib::ustring &, return; } - auto name_var = Glib::VariantBase::cast_dynamic>(parameters.get_child(0)); + auto name_var = Glib::VariantBase::cast_dynamic>(parameters.get_child(0)); auto old_owner_var = Glib::VariantBase::cast_dynamic>(parameters.get_child(1)); auto new_owner_var = Glib::VariantBase::cast_dynamic>(parameters.get_child(2)); @@ -270,7 +270,7 @@ void MprisController::emit_cached_playback_status() { return; } - auto status = Glib::VariantBase::cast_dynamic>(status_var).get(); + auto status = Glib::VariantBase::cast_dynamic>(status_var).get(); auto parsedStatusIt = playbackStatusMap.find(static_cast(status)); if (parsedStatusIt != playbackStatusMap.end()) { currentPlaybackStatus = parsedStatusIt->second; @@ -398,10 +398,8 @@ void MprisController::set_position(const std::string &track_id, int64_t position } try { - Glib::VariantContainerBase params = Glib::VariantContainerBase::create_tuple({ - Glib::Variant::create(track_id), - Glib::Variant::create(position_us) - }); + Glib::VariantContainerBase params = Glib::VariantContainerBase::create_tuple({Glib::Variant::create(track_id), + Glib::Variant::create(position_us)}); m_proxy->call("SetPosition", params); } catch (const Glib::Error &ex) { diff --git a/src/connection/dbus/notification.cpp b/src/connection/dbus/notification.cpp index 2b862c1..8b5f38c 100644 --- a/src/connection/dbus/notification.cpp +++ b/src/connection/dbus/notification.cpp @@ -160,11 +160,11 @@ void NotificationService::handle_notify(const Glib::VariantContainerBase ¶me notify.expire_timeout = expire_timeout; guint id = notificationIdCounter++; - if (app_name == "image-copy" ) { + if (app_name == "image-copy") { NotificationController::getInstance()->showCopyNotification(notify); invocation->return_value(Glib::VariantContainerBase::create_tuple( Glib::Variant::create(id))); - + return; } @@ -173,7 +173,7 @@ void NotificationService::handle_notify(const Glib::VariantContainerBase ¶me invocation->return_value(Glib::VariantContainerBase::create_tuple( Glib::Variant::create(id))); return; - } + } if (app_name == "Thunderbird") { notify.expire_timeout = 10000; // 10 seconds for email notifications diff --git a/src/connection/dbus/tray.cpp b/src/connection/dbus/tray.cpp index 7f8a6ad..65c71f0 100644 --- a/src/connection/dbus/tray.cpp +++ b/src/connection/dbus/tray.cpp @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include #include #include @@ -128,7 +128,7 @@ void on_simple_call_finished(GObject *source, GAsyncResult *res, std::unique_ptr data( static_cast(user_data)); - GError *error = nullptr; + GError *error = nullptr; GVariant *reply = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); @@ -358,8 +358,8 @@ void TrayService::activate(const std::string &id, int32_t x, int32_t y) { return; } - auto data = new SimpleCallData(); - data->debugLabel = "Activate(" + id + ")"; + auto data = new SimpleCallData(); + data->debugLabel = "Activate(" + id + ")"; data->ignoreUnknownMethod = false; g_dbus_connection_call( connection->gobj(), it->second->publicData.busName.c_str(), @@ -375,8 +375,8 @@ void TrayService::secondaryActivate(const std::string &id, int32_t x, return; } - auto data = new SimpleCallData(); - data->debugLabel = "SecondaryActivate(" + id + ")"; + auto data = new SimpleCallData(); + data->debugLabel = "SecondaryActivate(" + id + ")"; data->ignoreUnknownMethod = false; g_dbus_connection_call( connection->gobj(), it->second->publicData.busName.c_str(), @@ -473,7 +473,7 @@ void on_menu_layout_finished(GObject *source, GAsyncResult *res, return; } - GError *error = nullptr; + GError *error = nullptr; GVariant *reply = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); @@ -823,7 +823,7 @@ struct RefreshCallData { }; void TrayService::on_refresh_finished_static(GObject *source, GAsyncResult *res, - gpointer user_data) { + gpointer user_data) { std::unique_ptr data( static_cast(user_data)); if (!data || !data->self) { @@ -837,7 +837,7 @@ void TrayService::on_refresh_finished_static(GObject *source, GAsyncResult *res, auto &tracked = *it->second; - GError *error = nullptr; + GError *error = nullptr; GVariant *reply = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); if (!reply) { @@ -919,9 +919,9 @@ void TrayService::on_refresh_finished_static(GObject *source, GAsyncResult *res, g_variant_unref(dictVariant); - const bool menuPathChanged = (tracked.publicData.menuPath != menuPath); - tracked.publicData.title = title; - tracked.publicData.status = status; + const bool menuPathChanged = (tracked.publicData.menuPath != menuPath); + tracked.publicData.title = title; + tracked.publicData.status = status; tracked.publicData.menuPath = menuPath; tracked.publicData.menuAvailable = !menuPath.empty(); diff --git a/src/connection/httpConnection.cpp b/src/connection/httpConnection.cpp index f8b1a88..e3eb4b2 100644 --- a/src/connection/httpConnection.cpp +++ b/src/connection/httpConnection.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -17,20 +18,20 @@ size_t write_to_string(void *contents, size_t size, size_t nmemb, void *userp) { std::string trim(std::string value) { auto not_space = [](unsigned char c) { return std::isspace(c) == 0; }; value.erase(value.begin(), - std::find_if(value.begin(), value.end(), not_space)); - value.erase(std::find_if(value.rbegin(), value.rend(), not_space).base(), + std::ranges::find_if(value, not_space)); + value.erase(std::ranges::find_if(std::ranges::reverse_view(value), not_space).base(), value.end()); return value; } size_t header_to_map(char *buffer, size_t size, size_t nitems, void *userdata) { - size_t total = size * nitems; + size_t total = size * nitems; auto *header_map = static_cast *>(userdata); std::string line(buffer, total); auto colon = line.find(':'); if (colon != std::string::npos) { - auto key = trim(line.substr(0, colon)); + auto key = trim(line.substr(0, colon)); auto value = trim(line.substr(colon + 1)); if (!key.empty()) { header_map->insert_or_assign(std::move(key), std::move(value)); @@ -38,19 +39,19 @@ size_t header_to_map(char *buffer, size_t size, size_t nitems, void *userdata) { } return total; } -} +} // namespace HttpResponse HttpConnection::get(const std::string &url, - const std::map &headers, - long timeout_ms) { + const std::map &headers, + long timeout_ms) { return performRequest("GET", url, std::string(), headers, std::string(), timeout_ms); } HttpResponse HttpConnection::post(const std::string &url, - const std::string &body, - const std::map &headers, - const std::string &content_type, - long timeout_ms) { + const std::string &body, + const std::map &headers, + const std::string &content_type, + long timeout_ms) { return performRequest("POST", url, body, headers, content_type, timeout_ms); } @@ -91,12 +92,12 @@ HttpResponse HttpConnection::performRequest(const std::string &method, struct curl_slist *header_list = nullptr; for (const auto &pair : headers) { std::string header = pair.first + ": " + pair.second; - header_list = curl_slist_append(header_list, header.c_str()); + header_list = curl_slist_append(header_list, header.c_str()); } if (method == "POST" && !content_type.empty()) { std::string content_header = "Content-Type: " + content_type; - header_list = curl_slist_append(header_list, content_header.c_str()); + header_list = curl_slist_append(header_list, content_header.c_str()); } if (header_list) { diff --git a/src/services/hyprland.cpp b/src/services/hyprland.cpp index c877099..549c1b4 100644 --- a/src/services/hyprland.cpp +++ b/src/services/hyprland.cpp @@ -14,7 +14,6 @@ #include "helpers/string.hpp" #include "gtkmm/box.h" - #include "spdlog/spdlog.h" HyprlandService::HyprlandService() { diff --git a/src/services/textureCache.cpp b/src/services/textureCache.cpp index 6cec102..1a26595 100644 --- a/src/services/textureCache.cpp +++ b/src/services/textureCache.cpp @@ -18,7 +18,7 @@ namespace { #define CACHE_AGE 168 constexpr std::uint64_t kFnvOffsetBasis = 14695981039346656037ull; -constexpr std::uint64_t kFnvPrime = 1099511628211ull; +constexpr std::uint64_t kFnvPrime = 1099511628211ull; std::string to_hex(std::uint64_t value) { std::ostringstream stream; @@ -53,7 +53,7 @@ std::filesystem::path get_cache_path_for_url(const std::string &url) { auto filename = hash_url(url); auto last_slash = url.find_last_of('/'); - auto last_dot = url.find_last_of('.'); + auto last_dot = url.find_last_of('.'); if (last_dot != std::string::npos && (last_slash == std::string::npos || last_dot > last_slash)) { auto ext = url.substr(last_dot); if (ext.size() <= 10) { @@ -66,14 +66,13 @@ std::filesystem::path get_cache_path_for_url(const std::string &url) { std::chrono::system_clock::time_point to_system_clock(std::filesystem::file_time_type time) { return std::chrono::time_point_cast( - time - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now() - ); + time - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()); } size_t write_to_buffer(void *contents, size_t size, size_t nmemb, void *userp) { auto *buffer = static_cast *>(userp); - auto total = size * nmemb; - auto *bytes = static_cast(contents); + auto total = size * nmemb; + auto *bytes = static_cast(contents); buffer->insert(buffer->end(), bytes, bytes + total); return total; } @@ -144,7 +143,7 @@ Glib::RefPtr TextureCacheService::getTexture(const std::string &ur } auto cache_path = get_cache_path_for_url(url); - auto texture = load_texture_from_file(cache_path); + auto texture = load_texture_from_file(cache_path); if (!texture) { texture = download_texture_from_url(url, cache_path); } @@ -166,7 +165,7 @@ void TextureCacheService::pruneCache() { std::vector> files; std::uintmax_t total_size = 0; - auto now = std::chrono::system_clock::now(); + auto now = std::chrono::system_clock::now(); auto max_age = std::chrono::hours(CACHE_AGE); for (const auto &entry : std::filesystem::directory_iterator(cache_dir, error)) { @@ -174,7 +173,7 @@ void TextureCacheService::pruneCache() { continue; } - auto path = entry.path(); + auto path = entry.path(); auto last_write = entry.last_write_time(error); if (!error) { auto age = now - to_system_clock(last_write); @@ -199,7 +198,7 @@ void TextureCacheService::pruneCache() { std::sort(files.begin(), files.end(), [&](const auto &left, const auto &right) { std::error_code left_error; std::error_code right_error; - auto left_time = std::filesystem::last_write_time(left.first, left_error); + auto left_time = std::filesystem::last_write_time(left.first, left_error); auto right_time = std::filesystem::last_write_time(right.first, right_error); if (left_error || right_error) { return left.first.string() < right.first.string(); diff --git a/src/services/timeSignal.cpp b/src/services/timeSignal.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/widgets/controlCenter/controlCenter.cpp b/src/widgets/controlCenter/controlCenter.cpp index 646fa2d..b22dcfc 100644 --- a/src/widgets/controlCenter/controlCenter.cpp +++ b/src/widgets/controlCenter/controlCenter.cpp @@ -1,18 +1,16 @@ #include "widgets/controlCenter/controlCenter.hpp" + #include "components/button/iconButton.hpp" #include "components/button/tabButton.hpp" - +#include "components/mediaPlayer.hpp" ControlCenter::ControlCenter(Icon::Type 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(10); - + this->scrollview.set_child(this->container); - this->scrollview.set_min_content_width(220); - this->scrollview.set_max_content_width(220); - this->scrollview.set_size_request(220, -1); this->scrollview.set_policy( Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); this->scrollview.set_hexpand(false); @@ -26,84 +24,60 @@ ControlCenter::ControlCenter(Icon::Type icon, std::string name) this->tabRow.set_margin_bottom(4); this->tabRow.add_css_class("control-center-tab-row"); - this->mediaControl = std::make_unique(Icon::PLAY_CIRCLE); - this->testTabButton = std::make_unique(Icon::EMPTY_DASHBOARD); + this->mediaTabButton = std::make_unique(Icon::PLAY_CIRCLE); + this->infoTabButton = std::make_unique(Icon::EMPTY_DASHBOARD); + this->timerButton = std::make_unique(Icon::TOKEN); - this->tabRow.append(*this->mediaControl); - this->tabRow.append(*this->testTabButton); + this->tabRow.append(*this->mediaTabButton); + this->tabRow.append(*this->infoTabButton); + this->tabRow.append(*this->timerButton); this->container.append(this->tabRow); this->contentStack.set_hhomogeneous(true); this->contentStack.set_vhomogeneous(false); this->contentStack.set_transition_type(Gtk::StackTransitionType::CROSSFADE); - this->contentStack.set_transition_duration(150); - this->controlCenterContainer.set_orientation(Gtk::Orientation::VERTICAL); - this->controlCenterContainer.set_spacing(4); + this->mediaControlWidget = std::make_unique(); + + this->weatherWidget = std::make_unique(); + + this->contentStack.add(*this->mediaControlWidget, "controls", "Controls"); + this->contentStack.add(*this->weatherWidget, "info", "Info"); + this->contentStack.add(*Gtk::make_managed("Timer"), "timer", "Timer"); - this->contentStack.add(this->controlCenterContainer, "controls", "Controls"); - this->contentStack.add(this->weatherWidget, "test", "Test"); this->contentStack.set_visible_child("controls"); - this->setActiveTab("controls"); + this->setActiveTab("info"); this->container.append(this->contentStack); - this->mediaControl->signal_clicked().connect([this]() { + this->mediaTabButton->signal_clicked().connect([this]() { this->setActiveTab("controls"); }); - this->testTabButton->signal_clicked().connect([this]() { - this->setActiveTab("test"); + this->infoTabButton->signal_clicked().connect([this]() { + this->setActiveTab("info"); }); - this->mprisController->signal_player_registered().connect( - [this](const std::string &bus_name) { - this->addPlayerWidget(bus_name); - }); - - this->mprisController->signal_player_deregistered().connect( - [this](const std::string &bus_name) { - this->removePlayerWidget(bus_name); - }); - - for (const auto &bus_name : this->mprisController->get_registered_players()) { - this->addPlayerWidget(bus_name); - } + this->timerButton->signal_clicked().connect([this]() { + this->setActiveTab("timer"); + }); } void ControlCenter::setActiveTab(const std::string &tab_name) { this->contentStack.set_visible_child(tab_name); - this->mediaControl->setActive(false); - this->testTabButton->setActive(false); + this->mediaTabButton->setActive(false); + this->infoTabButton->setActive(false); + this->timerButton->setActive(false); if (tab_name == "controls") { - this->mediaControl->setActive(true); - } else if (tab_name == "test") { - this->testTabButton->setActive(true); + this->mediaTabButton->setActive(true); + } else if (tab_name == "info") { + this->infoTabButton->setActive(true); + } else if (tab_name == "timer") { + this->timerButton->setActive(true); } } -void ControlCenter::addPlayerWidget(const std::string &bus_name) { - if (this->mediaWidgets.find(bus_name) != this->mediaWidgets.end()) { - return; - } - - auto controller = MprisController::createForPlayer(bus_name); - auto widget = Gtk::make_managed(controller); - this->mediaWidgets.emplace(bus_name, widget); - this->controlCenterContainer.append(*widget); -} - -void ControlCenter::removePlayerWidget(const std::string &bus_name) { - auto it = this->mediaWidgets.find(bus_name); - if (it == this->mediaWidgets.end()) { - return; - } - - this->controlCenterContainer.remove(*it->second); - this->mediaWidgets.erase(it); -} - diff --git a/src/widgets/controlCenter/mediaWidget.cpp b/src/widgets/controlCenter/mediaWidget.cpp new file mode 100644 index 0000000..1af1fb4 --- /dev/null +++ b/src/widgets/controlCenter/mediaWidget.cpp @@ -0,0 +1,50 @@ +#include "widgets/controlCenter/mediaWidget.hpp" + +#include + +MediaWidget::MediaWidget() : Gtk::Box(Gtk::Orientation::VERTICAL) { + this->set_hexpand(true); + this->set_vexpand(false); + this->add_css_class("control-center-media-widget"); + this->container.set_orientation(Gtk::Orientation::VERTICAL); + this->container.set_hexpand(true); + this->container.set_vexpand(false); + this->append(this->container); + + this->mprisController->signal_player_registered().connect( + [this](const std::string &bus_name) { + this->addPlayerWidget(bus_name); + }); + + this->mprisController->signal_player_deregistered().connect( + [this](const std::string &bus_name) { + this->removePlayerWidget(bus_name); + }); + + for (const auto &bus_name : this->mprisController->get_registered_players()) { + this->addPlayerWidget(bus_name); + } +} + +void MediaWidget::addPlayerWidget(const std::string &bus_name) { + if (this->mediaWidgets.find(bus_name) != this->mediaWidgets.end()) { + return; + } + + auto controller = MprisController::createForPlayer(bus_name); + auto widget = std::make_unique(controller); + + this->mediaWidgets.emplace(bus_name, std::move(widget)); + this->container.append(*this->mediaWidgets[bus_name]); +} + +void MediaWidget::removePlayerWidget(const std::string &bus_name) { + auto it = this->mediaWidgets.find(bus_name); + + if (it == this->mediaWidgets.end()) { + return; + } + + this->container.remove(*it->second); + this->mediaWidgets.erase(it); +} \ No newline at end of file diff --git a/src/widgets/notification/copyNotification.cpp b/src/widgets/notification/copyNotification.cpp index 8b14a4b..e812b5e 100644 --- a/src/widgets/notification/copyNotification.cpp +++ b/src/widgets/notification/copyNotification.cpp @@ -2,6 +2,7 @@ #include #include + #include "components/button/iconButton.hpp" #include "glibmm/datetime.h" @@ -86,7 +87,6 @@ void CopyNotification::createImageNotification(NotifyMessage notify) { auto buttonBox = Gtk::make_managed(); buttonBox->set_spacing(10); - auto saveToClipboardButton = Gtk::make_managed(Icon::CONTENT_COPY); saveToClipboardButton->signal_clicked().connect([this]() { copyToClipboard(this->copiedImage); @@ -103,8 +103,8 @@ void CopyNotification::createImageNotification(NotifyMessage notify) { // xdg-pic/screenshot // use env auto xdgPicturesDir = Glib::get_user_special_dir(Glib::UserDirectory::PICTURES); auto dateStamp = Glib::DateTime::create_now_local().format("%Y%m%d_%H%M%S"); - auto filepath = xdgPicturesDir + "/screenshot" ; - auto filename = dateStamp + ".png"; + auto filepath = xdgPicturesDir + "/screenshot"; + auto filename = dateStamp + ".png"; saveImageToFile(this->copiedImage, filepath, filename); spdlog::info("Saved image to {}", filepath.c_str()); @@ -119,7 +119,6 @@ void CopyNotification::createImageNotification(NotifyMessage notify) { contentBox->append(*buttonBox); this->mainBox.append(*contentBox); - } void CopyNotification::createTextNotification(NotifyMessage notify) { diff --git a/src/widgets/notification/notificationWindow.cpp b/src/widgets/notification/notificationWindow.cpp index c8ebacc..c44fb8c 100644 --- a/src/widgets/notification/notificationWindow.cpp +++ b/src/widgets/notification/notificationWindow.cpp @@ -1,33 +1,32 @@ #include "widgets/notification/notificationWindow.hpp" + #include #include -#include "components/button/iconButton.hpp" + #include "components/button/textButton.hpp" #include "helpers/string.hpp" #include "gtkmm/box.h" -#include "gtkmm/button.h" #include "gtkmm/image.h" #include "gtkmm/label.h" NotificationWindow::NotificationWindow(uint64_t notificationId, std::shared_ptr monitor, NotifyMessage notify) : BaseNotification(notificationId, monitor) { set_title(notify.summary); - // Main vertical box auto vbox = Gtk::make_managed(Gtk::Orientation::VERTICAL, 8); vbox->set_halign(Gtk::Align::FILL); switch (notify.urgency) { - case NotificationUrgency::CRITICAL: - add_css_class("notification-critical"); - break; - case NotificationUrgency::NORMAL: - add_css_class("notification-normal"); - break; - case NotificationUrgency::LOW: - add_css_class("notification-low"); - break; + case NotificationUrgency::CRITICAL: + add_css_class("notification-critical"); + break; + case NotificationUrgency::NORMAL: + add_css_class("notification-normal"); + break; + case NotificationUrgency::LOW: + add_css_class("notification-low"); + break; } auto header_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); @@ -77,16 +76,16 @@ NotificationWindow::NotificationWindow(uint64_t notificationId, std::shared_ptr< btn->add_css_class("notification-button"); switch (notify.urgency) { - case NotificationUrgency::CRITICAL: - btn->add_css_class("notification-critical"); - break; - case NotificationUrgency::NORMAL: - btn->add_css_class("notification-normal"); - break; - case NotificationUrgency::LOW: - btn->add_css_class("notification-low"); - break; - } + case NotificationUrgency::CRITICAL: + btn->add_css_class("notification-critical"); + break; + case NotificationUrgency::NORMAL: + btn->add_css_class("notification-normal"); + break; + case NotificationUrgency::LOW: + btn->add_css_class("notification-low"); + break; + } btn->signal_clicked().connect([this, action_id, cb = notify.on_action, guard = notify.actionInvoked]() { if (cb && guard && !*guard) { diff --git a/src/widgets/notification/spotifyNotification.cpp b/src/widgets/notification/spotifyNotification.cpp index 2751073..84724ae 100644 --- a/src/widgets/notification/spotifyNotification.cpp +++ b/src/widgets/notification/spotifyNotification.cpp @@ -1,4 +1,5 @@ #include "widgets/notification/spotifyNotification.hpp" + #include #include "components/button/iconButton.hpp" diff --git a/src/widgets/tray.cpp b/src/widgets/tray.cpp index 9a49780..4565d12 100644 --- a/src/widgets/tray.cpp +++ b/src/widgets/tray.cpp @@ -1,12 +1,12 @@ #include "widgets/tray.hpp" +#include #include #include -#include -#include #include -#include +#include #include +#include namespace { bool is_wayland_display(GtkWidget *widget) { @@ -182,7 +182,7 @@ void log_menu_tree(const std::vector &nodes, } // namespace TrayIconWidget::TrayIconWidget(Icon::Type icon, std::string id) : IconButton(icon), id(std::move(id)), - container(Gtk::Orientation::HORIZONTAL) { + container(Gtk::Orientation::HORIZONTAL) { aliveFlag = std::make_shared(true); set_has_frame(false); set_focusable(false); @@ -243,8 +243,8 @@ TrayIconWidget::~TrayIconWidget() { } void TrayIconWidget::update(const TrayService::Item &item) { - hasRemoteMenu = item.menuAvailable; - menuPopupPending = false; + hasRemoteMenu = item.menuAvailable; + menuPopupPending = false; menuRequestInFlight = false; if (!item.menuAvailable) { @@ -350,7 +350,7 @@ void TrayIconWidget::on_secondary_released(int /*n_press*/, double x, } menuRequestInFlight = true; - auto weak = std::weak_ptr(aliveFlag); + auto weak = std::weak_ptr(aliveFlag); service.request_menu_layout( id, [weak, this](std::optional layout) { if (auto locked = weak.lock()) { @@ -402,8 +402,8 @@ void TrayIconWidget::on_menu_layout_ready( const auto &layout = *layoutOpt; log_menu_tree(layout.children, 0); - auto menu = Gio::Menu::create(); - auto actions = Gio::SimpleActionGroup::create(); + auto menu = Gio::Menu::create(); + auto actions = Gio::SimpleActionGroup::create(); populate_menu_items(layout.children, menu, actions); @@ -529,8 +529,8 @@ void TrayIconWidget::on_menu_action(const Glib::VariantBase & /*parameter*/, } bool TrayIconWidget::try_get_pending_coords(int32_t &outX, int32_t &outY) const { - outX = -1; - outY = -1; + outX = -1; + outY = -1; int32_t sendX = static_cast(std::lround(pendingX)); int32_t sendY = static_cast(std::lround(pendingY)); if (!try_get_global_click_coords(GTK_WIDGET(gobj()), pendingX, pendingY, diff --git a/src/widgets/volumeWidget.cpp b/src/widgets/volumeWidget.cpp index bddac16..ba3e1d0 100644 --- a/src/widgets/volumeWidget.cpp +++ b/src/widgets/volumeWidget.cpp @@ -1,9 +1,9 @@ #include "widgets/volumeWidget.hpp" #include -#include #include #include +#include #include "helpers/command.hpp" diff --git a/src/widgets/webWidget.cpp b/src/widgets/webWidget.cpp index c160921..7153700 100644 --- a/src/widgets/webWidget.cpp +++ b/src/widgets/webWidget.cpp @@ -2,6 +2,7 @@ #include #include + #include "components/button/iconButton.hpp" WebWidget::WebWidget(Icon::Type icon, std::string name, std::string url) : Popover(icon, name) { diff --git a/weather.json b/weather.json deleted file mode 100644 index 7ed9d37..0000000 --- a/weather.json +++ /dev/null @@ -1,580 +0,0 @@ -{ - "latitude": 49.0, - "longitude": 8.4, - "generationtime_ms": 0.23317337036132812, - "utc_offset_seconds": 3600, - "timezone": "Europe/Berlin", - "timezone_abbreviation": "GMT+1", - "elevation": 122.0, - "current_units": { - "time": "iso8601", - "interval": "seconds", - "temperature_2m": "°C" - }, - "current": { - "time": "2026-02-03T23:00", - "interval": 900, - "temperature_2m": -0.7 - }, - "hourly_units": { - "time": "iso8601", - "temperature_2m": "°C", - "rain": "mm" - }, - "hourly": { - "time": [ - "2026-02-03T00:00", - "2026-02-03T01:00", - "2026-02-03T02:00", - "2026-02-03T03:00", - "2026-02-03T04:00", - "2026-02-03T05:00", - "2026-02-03T06:00", - "2026-02-03T07:00", - "2026-02-03T08:00", - "2026-02-03T09:00", - "2026-02-03T10:00", - "2026-02-03T11:00", - "2026-02-03T12:00", - "2026-02-03T13:00", - "2026-02-03T14:00", - "2026-02-03T15:00", - "2026-02-03T16:00", - "2026-02-03T17:00", - "2026-02-03T18:00", - "2026-02-03T19:00", - "2026-02-03T20:00", - "2026-02-03T21:00", - "2026-02-03T22:00", - "2026-02-03T23:00", - "2026-02-04T00:00", - "2026-02-04T01:00", - "2026-02-04T02:00", - "2026-02-04T03:00", - "2026-02-04T04:00", - "2026-02-04T05:00", - "2026-02-04T06:00", - "2026-02-04T07:00", - "2026-02-04T08:00", - "2026-02-04T09:00", - "2026-02-04T10:00", - "2026-02-04T11:00", - "2026-02-04T12:00", - "2026-02-04T13:00", - "2026-02-04T14:00", - "2026-02-04T15:00", - "2026-02-04T16:00", - "2026-02-04T17:00", - "2026-02-04T18:00", - "2026-02-04T19:00", - "2026-02-04T20:00", - "2026-02-04T21:00", - "2026-02-04T22:00", - "2026-02-04T23:00", - "2026-02-05T00:00", - "2026-02-05T01:00", - "2026-02-05T02:00", - "2026-02-05T03:00", - "2026-02-05T04:00", - "2026-02-05T05:00", - "2026-02-05T06:00", - "2026-02-05T07:00", - "2026-02-05T08:00", - "2026-02-05T09:00", - "2026-02-05T10:00", - "2026-02-05T11:00", - "2026-02-05T12:00", - "2026-02-05T13:00", - "2026-02-05T14:00", - "2026-02-05T15:00", - "2026-02-05T16:00", - "2026-02-05T17:00", - "2026-02-05T18:00", - "2026-02-05T19:00", - "2026-02-05T20:00", - "2026-02-05T21:00", - "2026-02-05T22:00", - "2026-02-05T23:00", - "2026-02-06T00:00", - "2026-02-06T01:00", - "2026-02-06T02:00", - "2026-02-06T03:00", - "2026-02-06T04:00", - "2026-02-06T05:00", - "2026-02-06T06:00", - "2026-02-06T07:00", - "2026-02-06T08:00", - "2026-02-06T09:00", - "2026-02-06T10:00", - "2026-02-06T11:00", - "2026-02-06T12:00", - "2026-02-06T13:00", - "2026-02-06T14:00", - "2026-02-06T15:00", - "2026-02-06T16:00", - "2026-02-06T17:00", - "2026-02-06T18:00", - "2026-02-06T19:00", - "2026-02-06T20:00", - "2026-02-06T21:00", - "2026-02-06T22:00", - "2026-02-06T23:00", - "2026-02-07T00:00", - "2026-02-07T01:00", - "2026-02-07T02:00", - "2026-02-07T03:00", - "2026-02-07T04:00", - "2026-02-07T05:00", - "2026-02-07T06:00", - "2026-02-07T07:00", - "2026-02-07T08:00", - "2026-02-07T09:00", - "2026-02-07T10:00", - "2026-02-07T11:00", - "2026-02-07T12:00", - "2026-02-07T13:00", - "2026-02-07T14:00", - "2026-02-07T15:00", - "2026-02-07T16:00", - "2026-02-07T17:00", - "2026-02-07T18:00", - "2026-02-07T19:00", - "2026-02-07T20:00", - "2026-02-07T21:00", - "2026-02-07T22:00", - "2026-02-07T23:00", - "2026-02-08T00:00", - "2026-02-08T01:00", - "2026-02-08T02:00", - "2026-02-08T03:00", - "2026-02-08T04:00", - "2026-02-08T05:00", - "2026-02-08T06:00", - "2026-02-08T07:00", - "2026-02-08T08:00", - "2026-02-08T09:00", - "2026-02-08T10:00", - "2026-02-08T11:00", - "2026-02-08T12:00", - "2026-02-08T13:00", - "2026-02-08T14:00", - "2026-02-08T15:00", - "2026-02-08T16:00", - "2026-02-08T17:00", - "2026-02-08T18:00", - "2026-02-08T19:00", - "2026-02-08T20:00", - "2026-02-08T21:00", - "2026-02-08T22:00", - "2026-02-08T23:00", - "2026-02-09T00:00", - "2026-02-09T01:00", - "2026-02-09T02:00", - "2026-02-09T03:00", - "2026-02-09T04:00", - "2026-02-09T05:00", - "2026-02-09T06:00", - "2026-02-09T07:00", - "2026-02-09T08:00", - "2026-02-09T09:00", - "2026-02-09T10:00", - "2026-02-09T11:00", - "2026-02-09T12:00", - "2026-02-09T13:00", - "2026-02-09T14:00", - "2026-02-09T15:00", - "2026-02-09T16:00", - "2026-02-09T17:00", - "2026-02-09T18:00", - "2026-02-09T19:00", - "2026-02-09T20:00", - "2026-02-09T21:00", - "2026-02-09T22:00", - "2026-02-09T23:00" - ], - "temperature_2m": [ - 2.2, - 2.2, - 2.2, - 2.2, - 2.0, - 1.9, - 1.8, - 1.5, - 1.2, - 1.4, - 1.8, - 2.2, - 2.5, - 1.9, - 2.6, - 3.0, - 2.7, - 3.0, - 3.0, - 2.4, - 1.7, - 1.3, - 0.9, - -0.7, - -0.2, - -0.1, - -0.5, - -0.7, - -1.4, - -1.1, - -1.0, - -1.0, - -1.1, - -0.9, - 1.7, - 3.7, - 5.0, - 5.8, - 6.5, - 6.9, - 6.6, - 5.9, - 4.8, - 3.6, - 2.7, - 2.0, - 1.5, - 1.1, - 0.5, - 0.2, - 0.4, - 0.9, - 1.2, - 1.3, - 1.5, - 1.7, - 1.8, - 2.0, - 2.2, - 2.7, - 3.4, - 4.2, - 5.2, - 6.1, - 6.4, - 5.9, - 4.5, - 3.2, - 2.4, - 1.9, - 1.4, - 1.1, - 1.0, - 0.8, - 0.5, - 0.3, - 0.1, - -0.1, - -0.2, - -0.3, - -0.3, - -0.1, - 0.7, - 1.7, - 2.9, - 4.1, - 5.0, - 5.2, - 5.1, - 4.8, - 4.3, - 4.1, - 4.2, - 4.1, - 3.8, - 3.5, - 3.3, - 3.4, - 3.4, - 3.5, - 3.5, - 3.5, - 3.4, - 3.4, - 3.5, - 3.6, - 4.1, - 5.2, - 6.6, - 7.8, - 8.4, - 8.8, - 8.8, - 8.1, - 6.9, - 5.9, - 5.3, - 4.9, - 4.6, - 4.3, - 4.2, - 4.0, - 3.9, - 3.9, - 3.8, - 3.8, - 3.8, - 3.9, - 4.3, - 4.7, - 5.3, - 6.0, - 6.9, - 7.5, - 7.6, - 7.5, - 7.2, - 6.6, - 5.8, - 5.0, - 4.4, - 3.9, - 3.5, - 3.1, - 2.9, - 2.8, - 3.0, - 3.3, - 3.6, - 3.5, - 3.4, - 3.4, - 3.7, - 4.2, - 4.7, - 5.3, - 6.0, - 6.5, - 6.8, - 6.8, - 6.6, - 5.9, - 4.8, - 3.9, - 3.5, - 3.3, - 3.1, - 2.9 - ], - "rain": [ - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.10, - 0.40, - 0.00, - 0.00, - 0.20, - 0.60, - 0.30, - 1.30, - 0.00, - 0.00, - 0.40, - 0.10, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.10, - 0.20, - 0.00, - 0.00, - 0.00, - 0.10, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00, - 0.00 - ] - }, - "daily_units": { - "time": "iso8601", - "temperature_2m_max": "°C", - "temperature_2m_min": "°C", - "weather_code": "wmo code" - }, - "daily": { - "time": [ - "2026-02-03", - "2026-02-04", - "2026-02-05", - "2026-02-06", - "2026-02-07", - "2026-02-08", - "2026-02-09" - ], - "temperature_2m_max": [ - 3.0, - 6.9, - 6.4, - 5.2, - 8.8, - 7.6, - 6.8 - ], - "temperature_2m_min": [ - -0.7, - -1.4, - 0.2, - -0.3, - 3.3, - 3.1, - 2.8 - ], - "weather_code": [ - 61, - 45, - 45, - 61, - 61, - 3, - 3 - ] - } -} \ No newline at end of file