New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Native notifications on Linux without libnotify #6825
Native notifications on Linux without libnotify #6825
Conversation
95ab1e3
to
fef8e63
Compare
@ilya-fedin edit 1st post and add a note "closes #1026" plz :) |
@Aokromes ok |
🤔 travis-ci failed with -Werror=enum-compare
But Qt documentation recommends comparsion with QMetaType https://doc.qt.io/QT-5/qvariant.html#type Should I add -Wno-enum-compare to build scripts? |
You can just replace
|
Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp
Outdated
Show resolved
Hide resolved
4f2cbe4
to
63c4cc9
Compare
I remembered that this PR also fixes #3750, because now there is no call to GetServerName in Supported(), instead the interface is checking for validity. I don’t know if I need to add this to the first post, because issue is auto-closed. |
// } | ||
// Libs::notify_notification_clear_actions(_data); | ||
Libs::g_object_unref(Libs::g_object_cast(_data)); | ||
if (std::find(capabilities.begin(), capabilities.end(), qsl("body-markup")) != capabilities.end()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All such lines can be replaced with the range-v3.
Something like this.
if (ranges::find(capabilities, qsl("body-markup")) != capabilities.end()) {
Also to shorten the code we can move the capabilities.end()
to a new variable. For example.
const auto endCap = end(capabilities);
if (ranges::find(capabilities, qsl("body-markup")) != endCap) {
...
if (ranges::find(capabilities, qsl("actions")) != endCap) {
...
etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
QString NotificationData::escapeHtml(const QString &text) { | ||
auto result = QString(); | ||
auto copyFrom = 0, textSize = text.size(); | ||
auto data = text.constData(); | ||
for (auto i = 0; i != textSize; ++i) { | ||
auto ch = data[i]; | ||
if (ch == '<' || ch == '>' || ch == '&') { | ||
if (!copyFrom) { | ||
result.reserve(textSize * 5); | ||
} | ||
if (i > copyFrom) { | ||
result.append(data + copyFrom, i - copyFrom); | ||
} | ||
switch (ch.unicode()) { | ||
case '<': result.append(qstr("<")); break; | ||
case '>': result.append(qstr(">")); break; | ||
case '&': result.append(qstr("&")); break; | ||
} | ||
copyFrom = i + 1; | ||
} | ||
} | ||
if (copyFrom > 0) { | ||
result.append(data + copyFrom, textSize - copyFrom); | ||
return result; | ||
} | ||
return text; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, but why we can't just use toHtmlEscaped()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this code is from the old implementation, I just reused it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
if (!subtitle.isEmpty()) { | ||
_body = qstr("<b>") + escapeHtml(subtitle) + qstr("</b>") + '\n' + escapeHtml(msg); | ||
} else { | ||
_body = escapeHtml(msg); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified.
_body = subtitle.isEmpty()
? escapeHtml(msg)
: QString("<b>%1</b>\n%2")
.arg(escapeHtml(subtitle))
.arg(escapeHtml(msg));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
return _actionsSupported; | ||
} | ||
bool NotificationData::show() { | ||
QStringList actionsStringList(QList<QString>::fromVector(QVector<QString>::fromStdVector(_actions))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, this is insane.
Perhaps I was wrong and it will be better to store _actions
as QStringList
in this particular case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
63c4cc9
to
5bca9c4
Compare
if (_specificationVersion.majorVersion() == 1 && _specificationVersion.minorVersion() >= 2 || _specificationVersion.majorVersion() > 1) { | ||
imageKey = "image-data"; | ||
} else if (_specificationVersion.majorVersion() == 1 && _specificationVersion.minorVersion() == 1) { | ||
imageKey = "image_data"; | ||
} else if (_specificationVersion.majorVersion() == 1 && _specificationVersion.minorVersion() < 1 || _specificationVersion.majorVersion() < 1) { | ||
imageKey = "icon_data"; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These huge conditions can be easily shortened with a new variables.
const auto major = _specificationVersion.majorVersion();
const auto minor = _specificationVersion.minorVersion();
if (major == 1 && minor >= 2) {
...
It would be better to have a clearly if ((a && b) || c)
condition instead of unclearly if (a && b || c)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
for (int i = 0; i < serverInformationReply.arguments().size(); ++i) { | ||
if (static_cast<QMetaType::Type>(serverInformationReply.arguments()[i].type()) == QMetaType::QString) { | ||
serverInformation.push_back(serverInformationReply.arguments()[i].toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be simplified with the for ( a : b )
loop.
For example.
for (const auto &arg : serverInformationReply.arguments()) {
if (static_cast<QMetaType::Type>(arg.type()) == QMetaType::QString) {
serverInformation.push_back(arg.toString());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
return text; | ||
} | ||
NotificationData::NotificationData(const std::shared_ptr<Manager*> &guarded, const QString &title, const QString &subtitle, const QString &msg, PeerId peerId, MsgId msgId) | ||
: _notificationInterface("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest you to move the repeated constant values to the separate constants.
Something like this.
namespace {
const auto kService = QString("org.freedesktop.Notifications");
const auto kPath = QString("/org/freedesktop/Notifications");
} // namespace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving such consts into an anonymous namespace is welcome.
An example from the project code.
tdesktop/Telegram/SourceFiles/window/themes/window_theme.cpp
Lines 36 to 42 in 0c0c8f3
namespace { | |
constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024; | |
constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024; | |
constexpr auto kNightThemeFile = str_const(":/gui/night.tdesktop-theme"); | |
constexpr auto kMinimumTiledSize = 512; | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
5bca9c4
to
3a0c597
Compare
if (!capabilities.empty()) { | ||
QString capabilitiesString; | ||
|
||
for (const auto &capability : capabilities) { | ||
if (!capabilitiesString.isEmpty()) { | ||
capabilitiesString += qstr(", "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a useless job for the Release build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it is better to check if debug logs are enabled than this is a debug build? This should be very helpful if there will be issues with notifications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be very helpful if there will be issues with notifications.
Well, if this is useful for problems with notifications, we will not get this information from the users, as no one uses the Debug version. I think for a one-time log entry this will be fine to use LOG()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
Libs::g_list_free_full(capabilities, g_free); | ||
const QDBusArgument &operator>>(const QDBusArgument &argument, NotificationData::ImageData &imageData) { | ||
argument.beginStructure(); | ||
argument >> imageData.width >> imageData.height >> imageData.rowStride >> imageData.hasAlpha >> imageData.bitsPerSample >> imageData.channels >> imageData.data;; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argument >> imageData.width >> imageData.height >> imageData.rowStride >> imageData.hasAlpha >> imageData.bitsPerSample >> imageData.channels >> imageData.data;; | |
argument >> imageData.width >> imageData.height >> imageData.rowStride >> imageData.hasAlpha >> imageData.bitsPerSample >> imageData.channels >> imageData.data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
bool hideNameAndPhoto = false; | ||
}; | ||
if (capabilitiesReply.isValid()) { | ||
return capabilitiesReply.value().toVector().toStdVector(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, we can remove the blank elements from the vector right away.
return capabilitiesReply.value().toVector().toStdVector(); | |
auto capabilities = capabilitiesReply.value(); | |
capabilities.removeAll(QString()); | |
return capabilities | ranges::to_vector; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which blank elements? o_O
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have the following lines in your code.
tdesktop/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp
Lines 43 to 45 in 3a0c597
if (!capabilitiesString.isEmpty()) { | |
capabilitiesString += qstr(", "); | |
} |
So you are probably assuming that there may be blank elements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it is a list of daemon capabilities for debug purposes.
It is also a port of
LOG(("LibNotify capabilities: %1").arg(_capabilities.join(qstr(", ")))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I misread the condition, sorry.
QString capabilitiesString; | ||
|
||
for (const auto &capability : capabilities) { | ||
if (!capabilitiesString.isEmpty()) { | ||
capabilitiesString += qstr(", "); | ||
} | ||
|
||
logError(error); | ||
capabilitiesString += capability; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop can be replaced with std::accumulate
.
For example.
const auto capabilitiesString = std::accumulate(
capabilities.begin(),
capabilities.end(),
QString{},
[](auto &s, auto &p) { return s += (p + qstr(", ")); }).chopped(2);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
By the way, all new project files follow the style with the column limit of 78. |
auto NotificationDaemonRunning = false; | ||
bool Supported() { | ||
static auto Checked = false; | ||
if (!Checked) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason to keep the naked NotificationDaemonRunning
variable here? You can move it to the Supported()
like you did with the static auto Checked
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand, the point here is that the check is performed only once - at startup.
static auto Checked
was in Supported()
already:
static auto Checked = false; |
Windows version has similar code:
tdesktop/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp
Lines 306 to 312 in 0c713a9
bool Supported() { | |
if (!Checked) { | |
Checked = true; | |
Check(); | |
} | |
return InitSucceeded; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and you can see that these variables are at least in the anonymous namespace.
tdesktop/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp
Lines 296 to 313 in 0c713a9
auto Checked = false; | |
auto InitSucceeded = false; | |
void Check() { | |
InitSucceeded = init(); | |
} | |
} // namespace | |
bool Supported() { | |
if (!Checked) { | |
Checked = true; | |
Check(); | |
} | |
return InitSucceeded; | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll put it in an anonymous namespace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
QVersionNumber NotificationData::parseSpecificationVersion(const std::vector<QString> &serverInformation) { | ||
if (serverInformation.size() >= 4) { | ||
return QVersionNumber::fromString(serverInformation[3]); | ||
} else { | ||
LOG(("Native notification error: server information should have 4 elements")); | ||
} | ||
|
||
using Notifications = QMap<PeerId, QMap<MsgId, Notification>>; | ||
Notifications _notifications; | ||
return QVersionNumber(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: There is no reason to keep this function as a class method and it can be safely moved to anonymous namespace.
namespace {
auto ParseSpecificationVersion(const std::vector<QString> &serverInformation) {
if (serverInformation.size() >= 4) {
return QVersionNumber::fromString(serverInformation[3]);
} else {
LOG(("Native notification error: server information should have 4 elements"));
}
return QVersionNumber();
}
} // namespace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
|
||
if (!_specificationVersion.isNull()) { | ||
const auto majorVersion = _specificationVersion.majorVersion(); | ||
const auto minorVersion = _specificationVersion.majorVersion(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const auto minorVersion = _specificationVersion.majorVersion(); | |
const auto minorVersion = _specificationVersion.minorVersion(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -23,16 +28,62 @@ inline bool SkipToast() { | |||
inline void FlashBounce() { | |||
} | |||
|
|||
void Finish(); | |||
class NotificationData : public QObject { | |||
Q_OBJECT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can safely remove Q_OBJECT
to avoid an unnecessary extra MOC.
Q_OBJECT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we can't
connect(&_notificationInterface, SIGNAL(ActionInvoked(uint, QString)), this, SLOT(notificationClicked(uint))); |
connect(&_notificationInterface, SIGNAL(NotificationClosed(uint, uint)), this, SLOT(notificationClosed(uint))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you try?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I added Q_OBJECT only after a lot of tries without it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay.
Hmm, do we really need to create the |
…ication implementation
3a0c597
to
db38f5a
Compare
fixed
I checked - it works without that. But it is still necessary to request capabilities and specification version every time, because user can replace notification daemon (or it can be replaced by system upgrade, because notification daemon doesn't works all the time and only starts upon notification request by dbus activation). |
Thanks a lot! |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
This change removes the need of gtk for native notifications on Linux
Closes #1026