Linux named notifications (#12192)
* Set name & desktop-entry on Linux notifications * DBusMock now honors verbose mode flag * Disable DBus Notification tests on ia32
This commit is contained in:
parent
9d090e00f2
commit
86af20ded0
9 changed files with 163 additions and 14 deletions
|
@ -108,14 +108,13 @@ void Browser::SetVersion(const std::string& version) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Browser::GetName() const {
|
std::string Browser::GetName() const {
|
||||||
std::string ret = name_override_;
|
std::string ret = brightray::GetOverriddenApplicationName();
|
||||||
if (ret.empty())
|
if (ret.empty())
|
||||||
ret = GetExecutableFileProductName();
|
ret = GetExecutableFileProductName();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Browser::SetName(const std::string& name) {
|
void Browser::SetName(const std::string& name) {
|
||||||
name_override_ = name;
|
|
||||||
brightray::OverrideApplicationName(name);
|
brightray::OverrideApplicationName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -273,8 +273,6 @@ class Browser : public WindowListObserver {
|
||||||
// The browser is being shutdown.
|
// The browser is being shutdown.
|
||||||
bool is_shutdown_;
|
bool is_shutdown_;
|
||||||
|
|
||||||
std::string name_override_;
|
|
||||||
|
|
||||||
int badge_count_ = 0;
|
int badge_count_ = 0;
|
||||||
|
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
|
|
|
@ -18,10 +18,15 @@
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
GDesktopAppInfo* get_desktop_app_info() {
|
GDesktopAppInfo* get_desktop_app_info() {
|
||||||
|
GDesktopAppInfo * ret = nullptr;
|
||||||
|
|
||||||
std::unique_ptr<base::Environment> env(base::Environment::Create());
|
std::unique_ptr<base::Environment> env(base::Environment::Create());
|
||||||
const std::string desktop_id = libgtkui::GetDesktopName(env.get());
|
const std::string desktop_id = libgtkui::GetDesktopName(env.get());
|
||||||
return desktop_id.empty() ? nullptr
|
const char * libcc_default_id = "chromium-browser.desktop";
|
||||||
: g_desktop_app_info_new(desktop_id.c_str());
|
if (!desktop_id.empty() && (desktop_id != libcc_default_id))
|
||||||
|
ret = g_desktop_app_info_new(desktop_id.c_str());
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -8,12 +8,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "base/environment.h"
|
||||||
#include "base/files/file_enumerator.h"
|
#include "base/files/file_enumerator.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/strings/string_util.h"
|
#include "base/strings/string_util.h"
|
||||||
#include "base/strings/utf_string_conversions.h"
|
#include "base/strings/utf_string_conversions.h"
|
||||||
#include "brightray/browser/notification_delegate.h"
|
#include "brightray/browser/notification_delegate.h"
|
||||||
#include "brightray/common/application_info.h"
|
#include "brightray/common/application_info.h"
|
||||||
|
#include "chrome/browser/ui/libgtkui/gtk_util.h"
|
||||||
#include "chrome/browser/ui/libgtkui/skia_utils_gtk.h"
|
#include "chrome/browser/ui/libgtkui/skia_utils_gtk.h"
|
||||||
#include "third_party/skia/include/core/SkBitmap.h"
|
#include "third_party/skia/include/core/SkBitmap.h"
|
||||||
|
|
||||||
|
@ -126,6 +128,20 @@ void LibnotifyNotification::Show(const NotificationOptions& options) {
|
||||||
notification_, "x-canonical-append", "true");
|
notification_, "x-canonical-append", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the desktop name to identify the application
|
||||||
|
// The desktop-entry is the part before the .desktop
|
||||||
|
std::unique_ptr<base::Environment> env(base::Environment::Create());
|
||||||
|
std::string desktop_id = libgtkui::GetDesktopName(env.get());
|
||||||
|
if (!desktop_id.empty()) {
|
||||||
|
const std::string suffix{".desktop"};
|
||||||
|
if (base::EndsWith(desktop_id, suffix,
|
||||||
|
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||||
|
desktop_id.resize(desktop_id.size() - suffix.size());
|
||||||
|
}
|
||||||
|
libnotify_loader_.notify_notification_set_hint_string(
|
||||||
|
notification_, "desktop-entry", desktop_id.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
GError* error = nullptr;
|
GError* error = nullptr;
|
||||||
libnotify_loader_.notify_notification_show(notification_, &error);
|
libnotify_loader_.notify_notification_show(notification_, &error);
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
|
@ -7,8 +7,9 @@ namespace {
|
||||||
std::string g_overridden_application_name;
|
std::string g_overridden_application_name;
|
||||||
std::string g_overridden_application_version;
|
std::string g_overridden_application_version;
|
||||||
|
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
|
// name
|
||||||
void OverrideApplicationName(const std::string& name) {
|
void OverrideApplicationName(const std::string& name) {
|
||||||
g_overridden_application_name = name;
|
g_overridden_application_name = name;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +17,7 @@ std::string GetOverriddenApplicationName() {
|
||||||
return g_overridden_application_name;
|
return g_overridden_application_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// version
|
||||||
void OverrideApplicationVersion(const std::string& version) {
|
void OverrideApplicationVersion(const std::string& version) {
|
||||||
g_overridden_application_version = version;
|
g_overridden_application_version = version;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,10 +47,7 @@ PCWSTR GetRawAppUserModelID() {
|
||||||
if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(¤t_app_id))) {
|
if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(¤t_app_id))) {
|
||||||
g_app_user_model_id = current_app_id;
|
g_app_user_model_id = current_app_id;
|
||||||
} else {
|
} else {
|
||||||
std::string name = GetOverriddenApplicationName();
|
std::string name = GetApplicationName();
|
||||||
if (name.empty()) {
|
|
||||||
name = GetApplicationName();
|
|
||||||
}
|
|
||||||
base::string16 generated_app_id = base::ReplaceStringPlaceholders(
|
base::string16 generated_app_id = base::ReplaceStringPlaceholders(
|
||||||
kAppUserModelIDFormat, base::UTF8ToUTF16(name), nullptr);
|
kAppUserModelIDFormat, base::UTF8ToUTF16(name), nullptr);
|
||||||
SetAppUserModelID(generated_app_id);
|
SetAppUserModelID(generated_app_id);
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
|
from config import is_verbose_mode
|
||||||
from dbusmock import DBusTestCase
|
from dbusmock import DBusTestCase
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def cleanup():
|
def cleanup():
|
||||||
DBusTestCase.stop_dbus(DBusTestCase.system_bus_pid)
|
DBusTestCase.stop_dbus(DBusTestCase.system_bus_pid)
|
||||||
|
DBusTestCase.stop_dbus(DBusTestCase.session_bus_pid)
|
||||||
|
|
||||||
|
|
||||||
atexit.register(cleanup)
|
atexit.register(cleanup)
|
||||||
|
|
||||||
|
dbusmock_log = sys.stdout if is_verbose_mode() else open(os.devnull, 'w')
|
||||||
|
|
||||||
DBusTestCase.start_system_bus()
|
DBusTestCase.start_system_bus()
|
||||||
# create a mock for "org.freedesktop.login1" using python-dbusmock
|
DBusTestCase.spawn_server_template('logind', None, dbusmock_log)
|
||||||
# preconfigured template
|
|
||||||
(logind_mock, logind) = DBusTestCase.spawn_server_template('logind')
|
DBusTestCase.start_session_bus()
|
||||||
|
DBusTestCase.spawn_server_template('notification_daemon', None, dbusmock_log)
|
||||||
|
|
|
@ -38,6 +38,7 @@ def main():
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
enable_verbose_mode()
|
enable_verbose_mode()
|
||||||
|
os.environ['ELECTRON_ENABLE_LOGGING'] = '1'
|
||||||
|
|
||||||
spec_modules = os.path.join(SOURCE_ROOT, 'spec', 'node_modules')
|
spec_modules = os.path.join(SOURCE_ROOT, 'spec', 'node_modules')
|
||||||
if args.rebuild_native_modules or not os.path.isdir(spec_modules):
|
if args.rebuild_native_modules or not os.path.isdir(spec_modules):
|
||||||
|
|
122
spec/api-notification-dbus-spec.js
Normal file
122
spec/api-notification-dbus-spec.js
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// For these tests we use a fake DBus daemon to verify libnotify interaction
|
||||||
|
// with the session bus. This requires python-dbusmock to be installed and
|
||||||
|
// running at $DBUS_SESSION_BUS_ADDRESS.
|
||||||
|
//
|
||||||
|
// script/test.py spawns dbusmock, which sets DBUS_SESSION_BUS_ADDRESS.
|
||||||
|
//
|
||||||
|
// See https://pypi.python.org/pypi/python-dbusmock to read about dbusmock.
|
||||||
|
|
||||||
|
const assert = require('assert')
|
||||||
|
const dbus = require('dbus-native')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
const {remote} = require('electron')
|
||||||
|
const {app} = remote.require('electron')
|
||||||
|
|
||||||
|
const skip = process.platform !== 'linux' ||
|
||||||
|
process.arch === 'ia32' ||
|
||||||
|
!process.env.DBUS_SESSION_BUS_ADDRESS;
|
||||||
|
|
||||||
|
(skip ? describe.skip : describe)('Notification module (dbus)', () => {
|
||||||
|
let mock, Notification, getCalls, reset
|
||||||
|
const realAppName = app.getName()
|
||||||
|
const realAppVersion = app.getVersion()
|
||||||
|
const appName = 'api-notification-dbus-spec'
|
||||||
|
const serviceName = 'org.freedesktop.Notifications'
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
// init app
|
||||||
|
app.setName(appName)
|
||||||
|
app.setDesktopName(appName + '.desktop')
|
||||||
|
// init dbus
|
||||||
|
const path = '/org/freedesktop/Notifications'
|
||||||
|
const iface = 'org.freedesktop.DBus.Mock'
|
||||||
|
const bus = dbus.sessionBus()
|
||||||
|
console.log('session bus: ' + process.env.DBUS_SESSION_BUS_ADDRESS)
|
||||||
|
const service = bus.getService(serviceName)
|
||||||
|
const getInterface = Promise.promisify(service.getInterface, {context: service})
|
||||||
|
mock = await getInterface(path, iface)
|
||||||
|
getCalls = Promise.promisify(mock.GetCalls, {context: mock})
|
||||||
|
reset = Promise.promisify(mock.Reset, {context: mock})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
// cleanup dbus
|
||||||
|
await reset()
|
||||||
|
// cleanup app
|
||||||
|
app.setName(realAppName)
|
||||||
|
app.setVersion(realAppVersion)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Notification module using ' + serviceName, () => {
|
||||||
|
function onMethodCalled (done) {
|
||||||
|
function cb (name) {
|
||||||
|
console.log('onMethodCalled: ' + name)
|
||||||
|
if (name === 'Notify') {
|
||||||
|
mock.removeListener('MethodCalled', cb)
|
||||||
|
console.log('done')
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cb
|
||||||
|
}
|
||||||
|
|
||||||
|
function unmarshalDBusNotifyHints (dbusHints) {
|
||||||
|
let o = {}
|
||||||
|
for (let hint of dbusHints) {
|
||||||
|
let key = hint[0]
|
||||||
|
let value = hint[1][1][0]
|
||||||
|
o[key] = value
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
function unmarshalDBusNotifyArgs (dbusArgs) {
|
||||||
|
return {
|
||||||
|
app_name: dbusArgs[0][1][0],
|
||||||
|
replaces_id: dbusArgs[1][1][0],
|
||||||
|
app_icon: dbusArgs[2][1][0],
|
||||||
|
title: dbusArgs[3][1][0],
|
||||||
|
body: dbusArgs[4][1][0],
|
||||||
|
actions: dbusArgs[5][1][0],
|
||||||
|
hints: unmarshalDBusNotifyHints(dbusArgs[6][1][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
mock.on('MethodCalled', onMethodCalled(done))
|
||||||
|
// lazy load Notification after we listen to MethodCalled mock signal
|
||||||
|
Notification = require('electron').remote.Notification
|
||||||
|
const n = new Notification({
|
||||||
|
title: 'title',
|
||||||
|
subtitle: 'subtitle',
|
||||||
|
body: 'body',
|
||||||
|
replyPlaceholder: 'replyPlaceholder',
|
||||||
|
sound: 'sound',
|
||||||
|
closeButtonText: 'closeButtonText'
|
||||||
|
})
|
||||||
|
n.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call ' + serviceName + ' to show notifications', async () => {
|
||||||
|
const calls = await getCalls()
|
||||||
|
assert(calls.length >= 1)
|
||||||
|
let lastCall = calls[calls.length - 1]
|
||||||
|
let methodName = lastCall[1]
|
||||||
|
assert.equal(methodName, 'Notify')
|
||||||
|
let args = unmarshalDBusNotifyArgs(lastCall[2])
|
||||||
|
assert.deepEqual(args, {
|
||||||
|
app_name: appName,
|
||||||
|
replaces_id: 0,
|
||||||
|
app_icon: '',
|
||||||
|
title: 'title',
|
||||||
|
body: 'body',
|
||||||
|
actions: [],
|
||||||
|
hints: {
|
||||||
|
'append': 'true',
|
||||||
|
'desktop-entry': appName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue