Use sound in native notifications on macOS.

This commit is contained in:
John Preston 2025-01-21 14:24:43 +04:00
parent 37c7b0c6d1
commit aa0c56876c
6 changed files with 70 additions and 35 deletions

View file

@ -314,30 +314,20 @@ LocalCache::~LocalCache() {
}
}
QString LocalCache::path(
LocalSound LocalCache::sound(
DocumentId id,
Fn<QByteArray()> resolveBytes,
Fn<QByteArray()> fallbackBytes) {
Fn<QByteArray()> resolveOriginalBytes,
Fn<QByteArray()> fallbackOriginalBytes) {
auto &result = _cache[id];
if (!result.isEmpty()) {
return result;
return { id, result };
}
const auto bytes = resolveBytes();
if (bytes.isEmpty()) {
return fallbackBytes ? path(0, fallbackBytes, nullptr) : QString();
}
const auto prefix = cWorkingDir() + u"tdata/audio_cache"_q;
QDir().mkpath(prefix);
const auto name = QString::number(id, 16).toUpper();
result = u"%1/%2.wav"_q.arg(prefix, name);
auto file = QFile(result);
if (!file.open(QIODevice::WriteOnly)) {
return fallbackBytes ? path(0, fallbackBytes, nullptr) : QString();
}
file.write(ConvertAndCut(bytes));
file.close();
return result;
result = resolveOriginalBytes();
return !result.isEmpty()
? LocalSound{ id, result }
: fallbackOriginalBytes
? sound(0, fallbackOriginalBytes, nullptr)
: LocalSound();
}
} // namespace Media::Audio

View file

@ -9,18 +9,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Media::Audio {
struct LocalSound {
DocumentId id = 0;
QByteArray wav;
explicit operator bool() const {
return !wav.isEmpty();
}
};
class LocalCache final {
public:
LocalCache() = default;
~LocalCache();
[[nodiscard]] QString path(
[[nodiscard]] LocalSound sound(
DocumentId id,
Fn<QByteArray()> resolveBytes,
Fn<QByteArray()> fallbackBytes);
Fn<QByteArray()> resolveOriginalBytes,
Fn<QByteArray()> fallbackOriginalBytes);
private:
base::flat_map<DocumentId, QString> _cache;
base::flat_map<DocumentId, QByteArray> _cache;
};

View file

@ -232,7 +232,7 @@ NotificationData::NotificationData(
bool NotificationData::init(const Info &info) {
const auto &title = info.title;
const auto &subtitle = info.subtitle;
//const auto sound = info.soundPath ? info.soundPath() : QString();
//const auto sound = info.sound ? info.sound() : QString();
if (_application) {
_notification = Gio::Notification::new_(
subtitle.isEmpty()

View file

@ -229,6 +229,8 @@ private:
void clearingThreadLoop();
void checkFocusState();
[[nodiscard]] QString cacheSound(const Media::Audio::LocalSound &sound);
const uint64 _managerId = 0;
QString _managerIdString;
@ -266,6 +268,7 @@ private:
QProcess _dnd;
QProcess _focus;
std::vector<Fn<void()>> _focusedCallbacks;
base::flat_map<DocumentId, QString> _cachedSounds;
bool _waitingDnd = false;
bool _waitingFocus = false;
bool _focused = false;
@ -289,6 +292,37 @@ Manager::Private::Private(Manager *manager)
}, _lifetime);
}
QString Manager::Private::cacheSound(const Media::Audio::LocalSound &sound) {
const auto i = _cachedSounds.find(sound.id);
if (i != end(_cachedSounds)) {
return i->second;
}
auto result = u"TDesktop-%1"_q.arg(sound.id
? QString::number(sound.id, 16).toUpper()
: u"Default"_q);
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSLibraryDirectory,
NSUserDomainMask,
YES);
NSString *library = [paths firstObject];
NSString *sounds = [library stringByAppendingPathComponent:@"Sounds"];
const auto folder = NS2QString(sounds);
const auto path = folder + u"/%1.wav"_q.arg(result);
QDir().mkpath(folder);
auto f = QFile(path);
if (f.open(QIODevice::WriteOnly)) {
f.write(sound.wav);
f.close();
}
_cachedSounds.emplace(sound.id, result);
return result;
}
void Manager::Private::showNotification(
NotificationInfo &&info,
Ui::PeerUserpicView &userpicView) {
@ -334,9 +368,9 @@ void Manager::Private::showNotification(
[notification setHasReplyButton:YES];
}
const auto sound = info.soundPath ? info.soundPath() : QString();
if (!sound.isEmpty()) {
[notification setSoundName:Q2NSString(sound)];
const auto sound = info.sound ? info.sound() : Media::Audio::LocalSound();
if (sound) {
[notification setSoundName:Q2NSString(cacheSound(sound))];
} else {
[notification setSoundName:nil];
}

View file

@ -118,8 +118,9 @@ constexpr auto kSystemAlertDuration = crl::time(0);
|| notifySettings->muteUnknown(from));
const auto fromAlert = !fromUnknown
&& !notifySettings->isMuted(from);
return (threadAlert || fromAlert)
? notifySettings->sound(thread).id
const auto &sound = notifySettings->sound(thread);
return ((threadAlert || fromAlert) && !sound.none)
? sound.id
: std::optional<DocumentId>();
}
@ -1252,13 +1253,13 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
// #TODO optimize
auto userpicView = item->history()->peer->createUserpicView();
const auto owner = &item->history()->owner();
const auto soundPath = fields.soundId ? [=, id = *fields.soundId] {
return _localSoundCache.path(id, [=] {
const auto sound = fields.soundId ? [=, id = *fields.soundId] {
return _localSoundCache.sound(id, [=] {
return Core::App().notifications().lookupSoundBytes(owner, id);
}, [=] {
return Core::App().notifications().lookupSoundBytes(owner, 0);
});
} : Fn<QString()>();
} : Fn<NotificationSound()>();
doShowNativeNotification({
.peer = item->history()->peer,
.topicRootId = item->topicRootId(),
@ -1266,7 +1267,7 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
.title = scheduled ? WrapFromScheduled(fullTitle) : fullTitle,
.subtitle = subtitle,
.message = text,
.soundPath = soundPath,
.sound = sound,
.options = options,
}, userpicView);
}

View file

@ -399,6 +399,7 @@ public:
return ManagerType::Native;
}
using NotificationSound = Media::Audio::LocalSound;
struct NotificationInfo {
not_null<PeerData*> peer;
MsgId topicRootId = 0;
@ -406,7 +407,7 @@ public:
QString title;
QString subtitle;
QString message;
Fn<QString()> soundPath;
Fn<NotificationSound()> sound;
DisplayOptions options;
};