One more attempt to fix DND on macOS.

This commit is contained in:
John Preston 2023-03-15 15:00:20 +04:00
parent 1eff68813d
commit 32e650548f
12 changed files with 228 additions and 102 deletions

View File

@ -794,16 +794,16 @@ void NotificationData::notificationReplied(
} // namespace
bool SkipAudioForCustom() {
return false;
}
bool SkipToastForCustom() {
return false;
}
bool SkipFlashBounceForCustom() {
return false;
void MaybePlaySoundForCustom(Fn<void()> playSound) {
playSound();
}
void MaybeFlashBounceForCustom(Fn<void()> flashBounce) {
flashBounce();
}
bool WaitForInputForCustom() {
@ -917,10 +917,7 @@ public:
void clearFromHistory(not_null<History*> history);
void clearFromSession(not_null<Main::Session*> session);
void clearNotification(NotificationId id);
[[nodiscard]] bool inhibited() const {
return _inhibited;
}
void invokeIfNotInhibited(Fn<void()> callback);
~Private();
@ -1154,6 +1151,12 @@ void Manager::Private::clearNotification(NotificationId id) {
}
}
void Manager::Private::invokeIfNotInhibited(Fn<void()> callback) {
if (!_inhibited) {
callback();
}
}
Manager::Private::~Private() {
clearAll();
@ -1215,16 +1218,16 @@ void Manager::doClearFromSession(not_null<Main::Session*> session) {
_private->clearFromSession(session);
}
bool Manager::doSkipAudio() const {
return _private->inhibited();
}
bool Manager::doSkipToast() const {
return false;
}
bool Manager::doSkipFlashBounce() const {
return _private->inhibited();
void Manager::doMaybePlaySound(Fn<void()> playSound) {
_private->invokeIfNotInhibited(std::move(playSound));
}
void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
_private->invokeIfNotInhibited(std::move(flashBounce));
}
} // namespace Notifications

View File

@ -33,9 +33,9 @@ protected:
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
bool doSkipAudio() const override;
bool doSkipToast() const override;
bool doSkipFlashBounce() const override;
void doMaybePlaySound(Fn<void()> playSound) override;
void doMaybeFlashBounce(Fn<void()> flashBounce) override;
private:
class Private;

View File

@ -13,16 +13,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Platform {
namespace Notifications {
bool SkipAudioForCustom() {
return false;
}
bool SkipToastForCustom() {
return false;
}
bool SkipFlashBounceForCustom() {
return false;
void MaybePlaySoundForCustom(Fn<void()> playSound) {
playSound();
}
void MaybeFlashBounceForCustom(Fn<void()> flashBounce) {
flashBounce();
}
bool WaitForInputForCustom() {

View File

@ -34,9 +34,9 @@ protected:
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
QString accountNameSeparator() override;
bool doSkipAudio() const override;
bool doSkipToast() const override;
bool doSkipFlashBounce() const override;
void doMaybePlaySound(Fn<void()> playSound) override;
void doMaybeFlashBounce(Fn<void()> flashBounce) override;
private:
class Private;

View File

@ -27,17 +27,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
static constexpr auto kQuerySettingsEachMs = 1000;
auto DoNotDisturbEnabled = false;
auto LastSettingsQueryMs = 0;
constexpr auto kQuerySettingsEachMs = crl::time(1000);
crl::time LastSettingsQueryMs/* = 0*/;
bool DoNotDisturbEnabled/* = false*/;
[[nodiscard]] bool ShouldQuerySettings() {
const auto now = crl::now();
if (LastSettingsQueryMs > 0 && now <= LastSettingsQueryMs + kQuerySettingsEachMs) {
return false;
}
LastSettingsQueryMs = now;
return true;
}
[[nodiscard]] QString LibraryPath() {
static const auto result = [] {
NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSLibraryDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return url
? QString::fromUtf8([[url path] fileSystemRepresentation])
: QString();
}();
return result;
}
void queryDoNotDisturbState() {
auto ms = crl::now();
if (LastSettingsQueryMs > 0 && ms <= LastSettingsQueryMs + kQuerySettingsEachMs) {
if (!ShouldQuerySettings()) {
return;
}
LastSettingsQueryMs = ms;
Boolean isKeyValid;
const auto doNotDisturb = CFPreferencesGetAppBooleanValue(
CFSTR("doNotDisturb"),
@ -151,16 +168,16 @@ using Manager = Platform::Notifications::Manager;
namespace Platform {
namespace Notifications {
bool SkipAudioForCustom() {
return false;
}
bool SkipToastForCustom() {
return false;
}
bool SkipFlashBounceForCustom() {
return false;
void MaybePlaySoundForCustom(Fn<void()> playSound) {
playSound();
}
void MaybeFlashBounceForCustom(Fn<void()> flashBounce) {
flashBounce();
}
bool WaitForInputForCustom() {
@ -207,6 +224,8 @@ public:
void clearFromSession(not_null<Main::Session*> session);
void updateDelegate();
void invokeIfNotFocused(Fn<void()> callback);
~Private();
private:
@ -214,6 +233,7 @@ private:
void putClearTask(Task task);
void clearingThreadLoop();
void checkFocusState();
const uint64 _managerId = 0;
QString _managerIdString;
@ -249,6 +269,14 @@ private:
ClearFinish>;
std::vector<ClearTask> _clearingTasks;
QProcess _dnd;
QProcess _focus;
std::vector<Fn<void()>> _focusedCallbacks;
bool _waitingDnd = false;
bool _waitingFocus = false;
bool _focused = false;
bool _processesInited = false;
rpl::lifetime _lifetime;
};
@ -457,6 +485,70 @@ void Manager::Private::updateDelegate() {
[center setDelegate:_delegate];
}
void Manager::Private::invokeIfNotFocused(Fn<void()> callback) {
if (!Platform::IsMac11_0OrGreater()) {
queryDoNotDisturbState();
if (!DoNotDisturbEnabled) {
callback();
}
} else if (Platform::IsMacStoreBuild() || LibraryPath().isEmpty()) {
callback();
} else if (!_focusedCallbacks.empty()) {
_focusedCallbacks.push_back(std::move(callback));
} else if (!ShouldQuerySettings()) {
if (!_focused) {
callback();
}
} else {
if (!_processesInited) {
_processesInited = true;
QObject::connect(&_dnd, &QProcess::finished, [=] {
_waitingDnd = false;
checkFocusState();
});
QObject::connect(&_focus, &QProcess::finished, [=] {
_waitingFocus = false;
checkFocusState();
});
}
const auto start = [](QProcess &process, QString keys) {
auto arguments = QStringList()
<< "-extract"
<< keys
<< "raw"
<< "-o"
<< "-"
<< "--"
<< (LibraryPath() + "/Preferences/com.apple.controlcenter.plist");
DEBUG_LOG(("Focus Check: Started %1.").arg(u"plutil"_q + arguments.join(' ')));
process.start(u"plutil"_q, arguments);
};
_focusedCallbacks.push_back(std::move(callback));
_waitingFocus = _waitingDnd = true;
start(_focus, u"NSStatusItem Visible FocusModes"_q);
start(_dnd, u"NSStatusItem Visible DoNotDisturb"_q);
}
}
void Manager::Private::checkFocusState() {
if (_waitingFocus || _waitingDnd) {
return;
}
const auto istrue = [](QProcess &process) {
const auto output = process.readAllStandardOutput();
DEBUG_LOG(("Focus Check: %1").arg(output));
const auto result = (output.trimmed() == u"true"_q);
return result;
};
_focused = istrue(_focus) || istrue(_dnd);
auto callbacks = base::take(_focusedCallbacks);
if (!_focused) {
for (const auto &callback : callbacks) {
callback();
}
}
}
Manager::Private::~Private() {
if (_clearingThread.joinable()) {
putClearTask(ClearFinish());
@ -517,17 +609,16 @@ QString Manager::accountNameSeparator() {
return QString::fromUtf8(" \xE2\x86\x92 ");
}
bool Manager::doSkipAudio() const {
queryDoNotDisturbState();
return DoNotDisturbEnabled;
}
bool Manager::doSkipToast() const {
return false;
}
bool Manager::doSkipFlashBounce() const {
return doSkipAudio();
void Manager::doMaybePlaySound(Fn<void()> playSound) {
_private->invokeIfNotFocused(std::move(playSound));
}
void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
_private->invokeIfNotFocused(std::move(flashBounce));
}
} // namespace Notifications

View File

@ -12,9 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Platform {
namespace Notifications {
[[nodiscard]] bool SkipAudioForCustom();
[[nodiscard]] bool SkipToastForCustom();
[[nodiscard]] bool SkipFlashBounceForCustom();
void MaybePlaySoundForCustom(Fn<void()> playSound);
void MaybeFlashBounceForCustom(Fn<void()> flashBounce);
[[nodiscard]] bool WaitForInputForCustom();
[[nodiscard]] bool Supported();

View File

@ -53,6 +53,19 @@ namespace Notifications {
#ifndef __MINGW32__
namespace {
constexpr auto kQuerySettingsEachMs = 1000;
crl::time LastSettingsQueryMs/* = 0*/;
[[nodiscard]] bool ShouldQuerySettings() {
const auto now = crl::now();
if (LastSettingsQueryMs > 0 && now <= LastSettingsQueryMs + kQuerySettingsEachMs) {
return false;
}
LastSettingsQueryMs = now;
return true;
}
[[nodiscard]] std::wstring NotificationTemplate(
QString id,
Window::Notifications::Manager::DisplayOptions options) {
@ -333,24 +346,16 @@ void QueryUserNotificationState() {
}
}
static constexpr auto kQuerySettingsEachMs = 1000;
crl::time LastSettingsQueryMs = 0;
void QuerySystemNotificationSettings() {
auto ms = crl::now();
if (LastSettingsQueryMs > 0 && ms <= LastSettingsQueryMs + kQuerySettingsEachMs) {
if (!ShouldQuerySettings()) {
return;
}
LastSettingsQueryMs = ms;
QueryQuietHours();
QueryFocusAssist();
QueryUserNotificationState();
}
} // namespace
#endif // !__MINGW32__
bool SkipAudioForCustom() {
bool SkipSoundForCustom() {
QuerySystemNotificationSettings();
return (UserNotificationState == QUNS_NOT_PRESENT)
@ -358,6 +363,19 @@ bool SkipAudioForCustom() {
|| Core::App().screenIsLocked();
}
bool SkipFlashBounceForCustom() {
return SkipToastForCustom();
}
} // namespace
#endif // !__MINGW32__
void MaybePlaySoundForCustom(Fn<void()> playSound) {
if (!SkipSoundForCustom()) {
playSound();
}
}
bool SkipToastForCustom() {
QuerySystemNotificationSettings();
@ -365,8 +383,10 @@ bool SkipToastForCustom() {
|| (UserNotificationState == QUNS_RUNNING_D3D_FULL_SCREEN);
}
bool SkipFlashBounceForCustom() {
return SkipToastForCustom();
void MaybeFlashBounceForCustom(Fn<void()> flashBounce) {
if (!SkipFlashBounceForCustom()) {
flashBounce();
}
}
bool WaitForInputForCustom() {
@ -942,20 +962,26 @@ void Manager::onAfterNotificationActivated(
_private->afterNotificationActivated(id, window);
}
bool Manager::doSkipAudio() const {
return SkipAudioForCustom()
|| QuietHoursEnabled
|| FocusAssistBlocks;
}
bool Manager::doSkipToast() const {
return false;
}
bool Manager::doSkipFlashBounce() const {
return SkipFlashBounceForCustom()
void Manager::doMaybePlaySound(Fn<void()> playSound) {
const auto skip = SkipSoundForCustom()
|| QuietHoursEnabled
|| FocusAssistBlocks;
if (!skip) {
playSound();
}
}
void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
const auto skip = SkipFlashBounceForCustom()
|| QuietHoursEnabled
|| FocusAssistBlocks;
if (!skip) {
flashBounce();
}
}
#endif // !__MINGW32__

View File

@ -45,9 +45,9 @@ protected:
void onAfterNotificationActivated(
NotificationId id,
not_null<Window::SessionController*> window) override;
bool doSkipAudio() const override;
bool doSkipToast() const override;
bool doSkipFlashBounce() const override;
void doMaybePlaySound(Fn<void()> playSound) override;
void doMaybeFlashBounce(Fn<void()> flashBounce) override;
private:
class Private;

View File

@ -574,22 +574,28 @@ void System::showNext() {
}
const auto &settings = Core::App().settings();
if (alertThread) {
if (settings.flashBounceNotify() && !_manager->skipFlashBounce()) {
if (settings.flashBounceNotify()) {
const auto peer = alertThread->peer();
if (const auto window = Core::App().windowFor(peer)) {
if (const auto handle = window->widget()->windowHandle()) {
handle->alert(kSystemAlertDuration);
// (handle, SLOT(_q_clearAlert())); in the future.
if (const auto controller = window->sessionController()) {
_manager->maybeFlashBounce(crl::guard(controller, [=] {
if (const auto handle = window->widget()->windowHandle()) {
handle->alert(kSystemAlertDuration);
// (handle, SLOT(_q_clearAlert())); in the future.
}
}));
}
}
}
if (settings.soundNotify() && !_manager->skipAudio()) {
const auto track = lookupSound(
&alertThread->owner(),
alertThread->owner().notifySettings().sound(alertThread).id);
track->playOnce();
Media::Player::mixer()->suppressAll(track->getLengthMs());
Media::Player::mixer()->scheduleFaderCallback();
if (settings.soundNotify()) {
const auto owner = &alertThread->owner();
const auto id = owner->notifySettings().sound(alertThread).id;
_manager->maybePlaySound(crl::guard(&owner->session(), [=] {
const auto track = lookupSound(owner, id);
track->playOnce();
Media::Player::mixer()->suppressAll(track->getLengthMs());
Media::Player::mixer()->scheduleFaderCallback();
}));
}
}

View File

@ -337,14 +337,14 @@ public:
[[nodiscard]] virtual ManagerType type() const = 0;
[[nodiscard]] bool skipAudio() const {
return doSkipAudio();
}
[[nodiscard]] bool skipToast() const {
return doSkipToast();
}
[[nodiscard]] bool skipFlashBounce() const {
return doSkipFlashBounce();
void maybePlaySound(Fn<void()> playSound) {
doMaybePlaySound(std::move(playSound));
}
void maybeFlashBounce(Fn<void()> flashBounce) {
doMaybeFlashBounce(std::move(flashBounce));
}
virtual ~Manager() = default;
@ -362,9 +362,9 @@ protected:
virtual void doClearFromTopic(not_null<Data::ForumTopic*> topic) = 0;
virtual void doClearFromHistory(not_null<History*> history) = 0;
virtual void doClearFromSession(not_null<Main::Session*> session) = 0;
virtual bool doSkipAudio() const = 0;
virtual bool doSkipToast() const = 0;
virtual bool doSkipFlashBounce() const = 0;
[[nodiscard]] virtual bool doSkipToast() const = 0;
virtual void doMaybePlaySound(Fn<void()> playSound) = 0;
virtual void doMaybeFlashBounce(Fn<void()> flashBounce) = 0;
[[nodiscard]] virtual bool forceHideDetails() const {
return false;
}
@ -445,14 +445,14 @@ protected:
}
void doClearFromSession(not_null<Main::Session*> session) override {
}
bool doSkipAudio() const override {
return false;
}
bool doSkipToast() const override {
return false;
}
bool doSkipFlashBounce() const override {
return false;
void doMaybePlaySound(Fn<void()> playSound) override {
playSound();
}
void doMaybeFlashBounce(Fn<void()> flashBounce) override {
flashBounce();
}
};

View File

@ -445,16 +445,16 @@ void Manager::doClearFromItem(not_null<HistoryItem*> item) {
}
}
bool Manager::doSkipAudio() const {
return Platform::Notifications::SkipAudioForCustom();
}
bool Manager::doSkipToast() const {
return Platform::Notifications::SkipToastForCustom();
}
bool Manager::doSkipFlashBounce() const {
return Platform::Notifications::SkipFlashBounceForCustom();
void Manager::doMaybePlaySound(Fn<void()> playSound) {
Platform::Notifications::MaybePlaySoundForCustom(std::move(playSound));
}
void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
Platform::Notifications::MaybeFlashBounceForCustom(std::move(flashBounce));
}
void Manager::doUpdateAll() {

View File

@ -73,9 +73,9 @@ private:
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
void doClearFromItem(not_null<HistoryItem*> item) override;
bool doSkipAudio() const override;
bool doSkipToast() const override;
bool doSkipFlashBounce() const override;
void doMaybePlaySound(Fn<void()> playSound) override;
void doMaybeFlashBounce(Fn<void()> flashBounce) override;
void showNextFromQueue();
void unlinkFromShown(Notification *remove);