From 9186081a545ca6446d3c45421e74fcdab6f06fec Mon Sep 17 00:00:00 2001 From: Dru Still Date: Tue, 12 Aug 2025 20:47:34 -0400 Subject: [PATCH] macOS: Add status bar menu with DL/UL display Adds a macOS status bar icon with minimal menu: - Live download/upload rates menu item --- src/gui/CMakeLists.txt | 10 ++- src/gui/macosstatusitem/itemview.h | 33 ++++++++ src/gui/macosstatusitem/itemview.mm | 111 ++++++++++++++++++++++++++ src/gui/macosstatusitem/statusitem.h | 47 +++++++++++ src/gui/macosstatusitem/statusitem.mm | 52 ++++++++++++ src/gui/mainwindow.cpp | 3 + src/gui/mainwindow.h | 2 + 7 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 src/gui/macosstatusitem/itemview.h create mode 100644 src/gui/macosstatusitem/itemview.mm create mode 100644 src/gui/macosstatusitem/statusitem.h create mode 100644 src/gui/macosstatusitem/statusitem.mm diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 18a42d15e..94a6f065a 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -287,14 +287,18 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") macosdockbadge/badger.mm macosdockbadge/badgeview.h macosdockbadge/badgeview.mm - macosshiftclickhandler.h macosshiftclickhandler.cpp + macosshiftclickhandler.h + macosstatusitem/itemview.h + macosstatusitem/itemview.mm + macosstatusitem/statusitem.h + macosstatusitem/statusitem.mm macutilities.h macutilities.mm - powermanagement/inhibitormacos.h powermanagement/inhibitormacos.cpp - programupdater.h + powermanagement/inhibitormacos.h programupdater.cpp + programupdater.h ) target_link_libraries(qbt_gui PRIVATE objc diff --git a/src/gui/macosstatusitem/itemview.h b/src/gui/macosstatusitem/itemview.h new file mode 100644 index 000000000..4e757e1ce --- /dev/null +++ b/src/gui/macosstatusitem/itemview.h @@ -0,0 +1,33 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2025 Dru Still + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#import + +@interface ItemView: NSView + +@end diff --git a/src/gui/macosstatusitem/itemview.mm b/src/gui/macosstatusitem/itemview.mm new file mode 100644 index 000000000..c5fcf7c8f --- /dev/null +++ b/src/gui/macosstatusitem/itemview.mm @@ -0,0 +1,111 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2025 Dru Still + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#import "itemview.h" + +#include +#include "base/utils/misc.h" + +namespace +{ + static NSString * const kUploadArrow = @"\u2191"; + static NSString * const kDownloadArrow = @"\u2193"; +} + +@interface ItemView () + +@property(nonatomic) int64_t fDownloadRate; +@property(nonatomic) int64_t fUploadRate; + +@property(strong) NSStatusItem *statusItem; + +@property(strong) NSMenu *statusMenu; +@property(strong) NSMenuItem *statusStatsItem; +@end + +@implementation ItemView + +- (instancetype)init +{ + if ((self = [super init])){ + _fDownloadRate = 0.0; + _fUploadRate = 0.0; + + NSImage *icon = [NSImage imageNamed:@"qbittorrent_mac"]; + [icon setSize:NSMakeSize(16, 16)]; + + self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; + self.statusItem.button.image = icon; + self.statusItem.button.imagePosition = NSImageOnly; + self.statusItem.button.imageScaling = NSImageScaleProportionallyDown; + self.statusItem.button.toolTip = @"qBittorrent"; + self.statusItem.button.title = @""; + + self.statusMenu = [[NSMenu alloc] init]; + + QString uploadString = Utils::Misc::friendlyUnit(self.fUploadRate, true); + QString downloadString = Utils::Misc::friendlyUnit(self.fDownloadRate, true); + + NSString *uploadNSString = [NSString stringWithUTF8String:uploadString.toUtf8().constData()]; + NSString *downloadNSString = [NSString stringWithUTF8String:downloadString.toUtf8().constData()]; + + self.statusStatsItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"%@ %@ · %@ %@", + kDownloadArrow, downloadNSString, + kUploadArrow, uploadNSString] action:nil keyEquivalent:@""]; + self.statusStatsItem.action = nil; + self.statusStatsItem.target = nil; + [self.statusMenu addItem:self.statusStatsItem]; + [self.statusMenu addItem:[NSMenuItem separatorItem]]; + + self.statusItem.menu = self.statusMenu; + } + return self; +} + +- (BOOL)setRatesWithDownload:(int64_t)downloadRate upload:(int64_t)uploadRate +{ + if ((downloadRate == self.fDownloadRate) && (uploadRate == self.fUploadRate)) + return NO; + + self.fDownloadRate = downloadRate; + self.fUploadRate = uploadRate; + + QString uploadString = Utils::Misc::friendlyUnit(self.fUploadRate, true); + QString downloadString = Utils::Misc::friendlyUnit(self.fDownloadRate, true); + + NSString *uploadNSString = [NSString stringWithUTF8String:uploadString.toUtf8().constData()]; + NSString *downloadNSString = [NSString stringWithUTF8String:downloadString.toUtf8().constData()]; + + [self.statusStatsItem setTitle:[NSString stringWithFormat:@"%@ %@ · %@ %@", + kDownloadArrow, downloadNSString, + kUploadArrow, uploadNSString]]; + + return YES; +} + +@end diff --git a/src/gui/macosstatusitem/statusitem.h b/src/gui/macosstatusitem/statusitem.h new file mode 100644 index 000000000..f7e112947 --- /dev/null +++ b/src/gui/macosstatusitem/statusitem.h @@ -0,0 +1,47 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2025 Dru Still + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include + +namespace MacUtils +{ + class StatusItem final + { + public: + StatusItem(); + ~StatusItem(); + + void updateSpeed(int64_t downloadRate, int64_t uploadRate); + + private: + struct Impl; + std::unique_ptr m_impl; + }; +} diff --git a/src/gui/macosstatusitem/statusitem.mm b/src/gui/macosstatusitem/statusitem.mm new file mode 100644 index 000000000..c64a8ca67 --- /dev/null +++ b/src/gui/macosstatusitem/statusitem.mm @@ -0,0 +1,52 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2025 Dru Still + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "statusitem.h" + +#import "itemview.h" + +namespace MacUtils +{ + struct StatusItem::Impl + { + ItemView *itemView = nullptr; + }; + + StatusItem::StatusItem() + : m_impl(std::make_unique()) + { + m_impl->itemView = [[ItemView alloc] init]; + } + + StatusItem::~StatusItem() = default; + + void StatusItem::updateSpeed(const int64_t downloadRate, const int64_t uploadRate) + { + [m_impl->itemView setRatesWithDownload:downloadRate upload:uploadRate]; + } +} diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 68221c981..f29ac971e 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -105,6 +105,7 @@ #ifdef Q_OS_MACOS #include "macosdockbadge/badger.h" +#include "macosstatusitem/statusitem.h" #endif #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #include "programupdater.h" @@ -138,6 +139,7 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con , m_storeExecutionLogTypes {EXECUTIONLOG_SETTINGS_KEY(u"Types"_s), Log::MsgType::ALL} #ifdef Q_OS_MACOS , m_badger {std::make_unique()} + , m_statusItem {std::make_unique()} #endif // Q_OS_MACOS { m_ui->setupUi(this); @@ -1465,6 +1467,7 @@ void MainWindow::loadSessionStats() // update global information #ifdef Q_OS_MACOS m_badger->updateSpeed(status.payloadDownloadRate, status.payloadUploadRate); + m_statusItem->updateSpeed(status.payloadDownloadRate, status.payloadUploadRate); #else refreshTrayIconTooltip(); #endif // Q_OS_MACOS diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index 99b0989a5..ee4a620fa 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -66,6 +66,7 @@ class TransferListWidget; namespace MacUtils { class Badger; + class StatusItem; } #endif @@ -261,5 +262,6 @@ private: #endif #ifdef Q_OS_MACOS std::unique_ptr m_badger; + std::unique_ptr m_statusItem; #endif };