From e6ec3d0c2bbdda2d3228558907353f725a4d45b7 Mon Sep 17 00:00:00 2001 From: Nick Korotysh Date: Mon, 16 Oct 2023 08:45:24 +0200 Subject: [PATCH] Display download/upload speed in dock icon Implementation is based on Transmission sources. Closes #2761 Closes #3671 Closes #7098 Closes #11350 Closes #18527 PR #19595 --- src/base/utils/misc.cpp | 22 ++++- src/base/utils/misc.h | 1 + src/gui/CMakeLists.txt | 4 + src/gui/macosdockbadge/badger.h | 48 ++++++++++ src/gui/macosdockbadge/badger.mm | 55 +++++++++++ src/gui/macosdockbadge/badgeview.h | 36 ++++++++ src/gui/macosdockbadge/badgeview.mm | 138 ++++++++++++++++++++++++++++ src/gui/mainwindow.cpp | 15 +-- src/gui/mainwindow.h | 10 ++ 9 files changed, 317 insertions(+), 12 deletions(-) create mode 100644 src/gui/macosdockbadge/badger.h create mode 100644 src/gui/macosdockbadge/badger.mm create mode 100644 src/gui/macosdockbadge/badgeview.h create mode 100644 src/gui/macosdockbadge/badgeview.mm diff --git a/src/base/utils/misc.cpp b/src/base/utils/misc.cpp index d75c963a3..6b99d8d52 100644 --- a/src/base/utils/misc.cpp +++ b/src/base/utils/misc.cpp @@ -94,7 +94,7 @@ namespace Utils::Misc::SizeUnit unit; }; - std::optional splitToFriendlyUnit(const qint64 bytes) + std::optional splitToFriendlyUnit(const qint64 bytes, const int unitThreshold = 1024) { if (bytes < 0) return std::nullopt; @@ -102,7 +102,7 @@ namespace int i = 0; auto value = static_cast(bytes); - while ((value >= 1024) && (i < static_cast(Utils::Misc::SizeUnit::ExbiByte))) + while ((value >= unitThreshold) && (i < static_cast(Utils::Misc::SizeUnit::ExbiByte))) { value /= 1024; ++i; @@ -270,6 +270,24 @@ QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed, const + QChar::Nbsp + unitString(result->unit, isSpeed); } +QString Utils::Misc::friendlyUnitCompact(const qint64 bytes) +{ + // avoid 1000-1023 values, use next larger unit instead + const std::optional result = splitToFriendlyUnit(bytes, 1000); + if (!result) + return QCoreApplication::translate("misc", "Unknown", "Unknown (size)"); + + int precision = 0; // >= 100 + if (result->value < 10) + precision = 2; // 0 - 9.99 + if (result->value < 100) + precision = 1; // 10 - 99.9 + + return Utils::String::fromDouble(result->value, precision) + // use only one character for unit representation + + QChar::Nbsp + unitString(result->unit, false)[0]; +} + int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit) { // friendlyUnit's number of digits after the decimal point diff --git a/src/base/utils/misc.h b/src/base/utils/misc.h index 1d6a43989..17758d67f 100644 --- a/src/base/utils/misc.h +++ b/src/base/utils/misc.h @@ -81,6 +81,7 @@ namespace Utils::Misc // return the best user friendly storage unit (B, KiB, MiB, GiB, TiB) // value must be given in bytes QString friendlyUnit(qint64 bytes, bool isSpeed = false, int precision = -1); + QString friendlyUnitCompact(qint64 bytes); int friendlyUnitPrecision(SizeUnit unit); qint64 sizeInBytes(qreal size, SizeUnit unit); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index e8146f4d9..b939b1fa8 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -280,6 +280,10 @@ endif() if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") target_sources(qbt_gui PRIVATE + macosdockbadge/badger.h + macosdockbadge/badger.mm + macosdockbadge/badgeview.h + macosdockbadge/badgeview.mm macutilities.h macutilities.mm ) diff --git a/src/gui/macosdockbadge/badger.h b/src/gui/macosdockbadge/badger.h new file mode 100644 index 000000000..deeb95b41 --- /dev/null +++ b/src/gui/macosdockbadge/badger.h @@ -0,0 +1,48 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Nick Korotysh + * + * 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 +#include + +namespace MacUtils +{ + class Badger final + { + public: + Badger(); + ~Badger(); + + void updateSpeed(int64_t dlRate, int64_t ulRate); + + private: + struct Impl; + std::unique_ptr m_impl; + }; +} diff --git a/src/gui/macosdockbadge/badger.mm b/src/gui/macosdockbadge/badger.mm new file mode 100644 index 000000000..04357c849 --- /dev/null +++ b/src/gui/macosdockbadge/badger.mm @@ -0,0 +1,55 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Nick Korotysh + * + * 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 "badger.h" + +#import "badgeview.h" + +namespace MacUtils +{ + struct Badger::Impl + { + BadgeView *view = nullptr; + }; + + Badger::Badger() + : m_impl(std::make_unique()) + { + m_impl->view = [[BadgeView alloc] init]; + NSApp.dockTile.contentView = m_impl->view; + } + + Badger::~Badger() = default; + + void Badger::updateSpeed(const int64_t dlRate, const int64_t ulRate) + { + // only update if the badged values change + if ([m_impl->view setRatesWithDownload:dlRate upload:ulRate]) + [NSApp.dockTile display]; + } +} diff --git a/src/gui/macosdockbadge/badgeview.h b/src/gui/macosdockbadge/badgeview.h new file mode 100644 index 000000000..e9281cdee --- /dev/null +++ b/src/gui/macosdockbadge/badgeview.h @@ -0,0 +1,36 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Nick Korotysh + * Copyright (C) 2007-2023 Transmission authors and contributors. + * + * 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 BadgeView : NSView + +- (BOOL)setRatesWithDownload:(int64_t)downloadRate upload:(int64_t)uploadRate; + +@end diff --git a/src/gui/macosdockbadge/badgeview.mm b/src/gui/macosdockbadge/badgeview.mm new file mode 100644 index 000000000..a40a04d48 --- /dev/null +++ b/src/gui/macosdockbadge/badgeview.mm @@ -0,0 +1,138 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Nick Korotysh + * Copyright (C) 2007-2023 Transmission authors and contributors. + * + * 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 "badgeview.h" +#include "base/utils/misc.h" + +static const CGFloat kBetweenPadding = 2.0; +static const NSColor *const kUploadBadgeColor = [NSColor colorWithRed:0.094 green:0.243 blue:0.835 alpha:0.9]; // #183ed5 +static const NSColor *const kDownloadBadgeColor = [NSColor colorWithRed:0.216 green:0.482 blue:0.173 alpha:0.9]; // #377b2c +static const NSSize kBadgeSize = {128.0, 30.0}; +static const NSString *const kUploadArrow = @"\u2191"; // or U+2b61 +static const NSString *const kDownloadArrow = @"\u2193"; // or U+2b63 +static CGSize kArrowInset; +static CGSize kArrowSize; + +@interface BadgeView () + +@property(nonatomic) NSMutableDictionary *fAttributes; + +@property(nonatomic) int64_t fDownloadRate; +@property(nonatomic) int64_t fUploadRate; + +@end + +@implementation BadgeView + +- (instancetype)init +{ + if ((self = [super init])) + { + _fDownloadRate = 0.0; + _fUploadRate = 0.0; + + NSShadow *stringShadow = [[NSShadow alloc] init]; + stringShadow.shadowOffset = NSMakeSize(2.0, -2.0); + stringShadow.shadowBlurRadius = 4.0; + + _fAttributes = [[NSMutableDictionary alloc] initWithCapacity:3]; + _fAttributes[NSForegroundColorAttributeName] = NSColor.whiteColor; + _fAttributes[NSShadowAttributeName] = stringShadow; + _fAttributes[NSFontAttributeName] = [NSFont boldSystemFontOfSize:26.0]; + + // DownloadBadge and UploadBadge should have the same size + // DownArrowTemplate and UpArrowTemplate should have the same size + kArrowInset = { kBadgeSize.height * 0.2, kBadgeSize.height * 0.1 }; + kArrowSize = [kDownloadArrow sizeWithAttributes:self.fAttributes]; + } + return self; +} + +- (BOOL)setRatesWithDownload:(int64_t)downloadRate upload:(int64_t)uploadRate +{ + // only needs update if the badges were displayed or are displayed now + if ((self.fDownloadRate == downloadRate) && (self.fUploadRate == uploadRate)) + { + return NO; + } + + self.fDownloadRate = downloadRate; + self.fUploadRate = uploadRate; + + return YES; +} + +- (void)drawRect:(NSRect)rect +{ + [NSApp.applicationIconImage drawInRect:rect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0]; + + const BOOL upload = self.fUploadRate >= 0.1; + const BOOL download = self.fDownloadRate >= 0.1; + CGFloat bottom = 0.0; + if (download) + { + [self badge:kDownloadBadgeColor arrow:kDownloadArrow + string:Utils::Misc::friendlyUnitCompact(self.fDownloadRate).toNSString() + atHeight:bottom]; + + if (upload) + { + bottom += kBadgeSize.height + kBetweenPadding; // upload rate above download rate + } + } + if (upload) + { + [self badge:kUploadBadgeColor arrow:kUploadArrow + string:Utils::Misc::friendlyUnitCompact(self.fUploadRate).toNSString() + atHeight:bottom]; + } +} + +- (void)badge:(const NSColor*)badgeColor arrow:(const NSString*)arrowSymbol string:(const NSString*)string atHeight:(CGFloat)height +{ + // background + const NSRect badgeRect = { { 0.0, height }, kBadgeSize }; + [badgeColor setFill]; + const CGFloat r = kBadgeSize.height / 2.0; + [[NSBezierPath bezierPathWithRoundedRect:badgeRect xRadius:r yRadius:r] fill]; + + // string is in center of image + const NSSize stringSize = [string sizeWithAttributes:self.fAttributes]; + NSRect stringRect; + stringRect.origin.x = NSMidX(badgeRect) - stringSize.width * 0.5 + kArrowInset.width; // adjust for arrow + stringRect.origin.y = NSMidY(badgeRect) - stringSize.height * 0.5 + 1.0; // adjust for shadow + stringRect.size = stringSize; + [string drawInRect:stringRect withAttributes:self.fAttributes]; + + // arrow + const NSRect arrowRect = { { kArrowInset.width, stringRect.origin.y }, kArrowSize }; + [arrowSymbol drawInRect:arrowRect withAttributes:self.fAttributes]; +} + +@end diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 79520012e..0df257f3b 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -102,7 +102,7 @@ #include "utils.h" #ifdef Q_OS_MACOS -#include "macutilities.h" +#include "macosdockbadge/badger.h" #endif #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #include "programupdater.h" @@ -132,6 +132,9 @@ MainWindow::MainWindow(IGUIApplication *app, WindowState initialState) , m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_s)) , m_storeDownloadTrackerFavicon(SETTINGS_KEY(u"DownloadTrackerFavicon"_s)) , m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY(u"Types"_s), Log::MsgType::ALL) +#ifdef Q_OS_MACOS + , m_badger(std::make_unique()) +#endif // Q_OS_MACOS { m_ui->setupUi(this); @@ -1481,15 +1484,7 @@ void MainWindow::reloadSessionStats() // update global information #ifdef Q_OS_MACOS - if (status.payloadDownloadRate > 0) - { - MacUtils::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds") - .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate))); - } - else if (!MacUtils::badgeLabelText().isEmpty()) - { - MacUtils::setBadgeLabelText({}); - } + m_badger->updateSpeed(status.payloadDownloadRate, status.payloadUploadRate); #else const auto toolTip = u"%1\n%2"_s.arg( tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)) diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index 17f90b19e..65d1088c8 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -61,6 +61,13 @@ class TorrentCreatorDialog; class TransferListFiltersWidget; class TransferListWidget; +#ifdef Q_OS_MACOS +namespace MacUtils +{ + class Badger; +} +#endif + namespace Net { struct DownloadResult; @@ -245,4 +252,7 @@ private: QTimer *m_programUpdateTimer = nullptr; #endif +#ifdef Q_OS_MACOS + std::unique_ptr m_badger; +#endif };