[chrome-gnome-shell/feature/connector-update-check: 17/18] notifications: implemented native Gio.Notification support



commit 4fc72424371c61292981ddb35445fcf07b12376d
Author: Yuri Konotopov <ykonotopov gnome org>
Date:   Mon Dec 19 00:59:05 2016 +0400

    notifications: implemented native Gio.Notification support

 connector/chrome-gnome-shell.py                 |  317 +++++++++++++++++------
 connector/org.gnome.ChromeGnomeShell.desktop    |    6 +
 connector/org.gnome.ChromeGnomeShell.service.in |    3 +
 extension/extension.js                          |  134 ++++++----
 extension/include/constants.js                  |    2 +
 extension/include/gsc.js                        |  121 ++++++----
 extension/include/notifications.js              |  122 +++++++--
 extension/include/sync.js                       |   38 +++-
 extension/include/update.js                     |   64 ++++-
 9 files changed, 588 insertions(+), 219 deletions(-)
---
diff --git a/connector/chrome-gnome-shell.py b/connector/chrome-gnome-shell.py
index 9e7815e..6d1924c 100755
--- a/connector/chrome-gnome-shell.py
+++ b/connector/chrome-gnome-shell.py
@@ -1,4 +1,5 @@
 #!/usr/bin/env python
+# -*- coding: UTF-8 -*-
 
 '''
     GNOME Shell integration for Chrome
@@ -12,7 +13,7 @@
 
 from __future__ import unicode_literals
 from __future__ import print_function
-from gi.repository import GLib, Gio
+from gi.repository import GLib, Gio, GObject
 import json
 import os
 import re
@@ -47,68 +48,147 @@ def logError(message):
 
 
 class ChromeGNOMEShell(Gio.Application):
-    def __init__(self):
+    def __init__(self, run_as_service):
         Gio.Application.__init__(self,
-                                    application_id='org.gnome.chrome-gnome-shell-%s' % os.getppid(),
-                                    flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
+                                 application_id='org.gnome.ChromeGnomeShell',
+                                 flags=Gio.ApplicationFlags.IS_SERVICE if run_as_service
+                                    else Gio.ApplicationFlags.IS_LAUNCHER | Gio.ApplicationFlags.HANDLES_OPEN
+                                 )
 
         self.shellAppearedId = None
         self.shellSignalId = None
-        self.proxy = Gio.DBusProxy.new_for_bus_sync(Gio.BusType.SESSION,
-                                       Gio.DBusProxyFlags.NONE,
-                                       None,
-                                       'org.gnome.Shell',
-                                       '/org/gnome/Shell',
-                                       'org.gnome.Shell.Extensions',
-                                       None)
 
         # Set custom exception hook
         sys.excepthook = self.default_exception_hook
 
+        self.register()
+
+        if not run_as_service:
+            self.shell_proxy = Gio.DBusProxy.new_sync(self.get_dbus_connection(),
+                                                      Gio.DBusProxyFlags.NONE,
+                                                      None,
+                                                      'org.gnome.Shell',
+                                                      '/org/gnome/Shell',
+                                                      'org.gnome.Shell.Extensions',
+                                                      None)
+
+            self.get_dbus_connection().signal_subscribe(
+                self.get_application_id(),
+                self.get_application_id(),
+                None,
+                "/org/gnome/ChromeGnomeShell",
+                None,
+                Gio.DBusSignalFlags.NONE,
+                self.on_dbus_signal,
+                None
+            )
 
-    def default_exception_hook(self, type, value, tb):
-        logError("Uncaught exception of type %s occured" % type)
-        traceback.print_tb(tb)
-        logError("Exception: %s" % value)
+            stdin = GLib.IOChannel.unix_new(sys.stdin.fileno())
+            stdin.set_encoding(None)
+            stdin.set_buffered(False)
 
-        self.release()
+            GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.IN, self.on_input, None)
+            GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.HUP, self.on_hup, None)
+            GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.ERR, self.on_hup, None)
+        else:
+            self.add_simple_action("create-notification", self.on_create_notification, 'a{sv}')
+            self.add_simple_action("on-notification-clicked", self.on_notification_clicked, 's')
+            self.add_simple_action("on-notification-action", self.on_notification_action, '(si)')
+
+            GLib.timeout_add_seconds(5 * 60, self.on_service_timeout, None)
 
-    def do_startup(self):
-        debug('Startup')
-        Gio.Application.do_startup(self)
+        GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.on_sigint, None)
 
+        if not run_as_service or not self.get_is_remote():
+            self.hold()
 
-    def do_shutdown(self):
-        debug('Shutdown')
-        Gio.Application.do_shutdown(self)
+    # Is there any way to hook this to shutdown?
+    def cleanup(self):
+        debug('Cleanup')
 
         if self.shellAppearedId:
             Gio.bus_unwatch_name(self.shellAppearedId)
 
         if self.shellSignalId:
-            self.proxy.disconnect(self.shellSignalId)
+            self.get_dbus_connection().signal_unsubscribe(self.shellSignalId)
 
+    def default_exception_hook(self, exception_type, value, tb):
+        logError("Uncaught exception of type %s occured" % exception_type)
+        traceback.print_tb(tb)
+        logError("Exception: %s" % value)
 
-    def do_activate(self, app):
-        debug('Activate')
-        Gio.Application.do_activate(self)
+        self.release()
 
+    def add_simple_action(self, name, callback, parameter_type):
+        action = Gio.SimpleAction.new(
+            name,
+            GLib.VariantType.new(parameter_type) if parameter_type is not None else None
+        )
+        action.connect('activate', callback)
+        self.add_action(action)
 
     def do_local_command_line(self, arguments):
-        stdin = GLib.IOChannel.unix_new(sys.stdin.fileno())
-        stdin.set_encoding(None)
-        stdin.set_buffered(False)
-
-        GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.on_sigint, None)
-        GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.IN, self.on_input, None)
-        GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.HUP, self.on_hup, None)
-        GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.ERR, self.on_hup, None)
-
-        self.hold()
-
-        return (True, None, 0)
+        if '--gapplication-service' in arguments:
+            arguments.remove('--gapplication-service')
+
+        return Gio.Application.do_local_command_line(self, arguments)
+
+    # Service events
+    def on_create_notification(self, object, request):
+        debug('On create notification')
+
+        request = request.unpack()
+
+        notification = Gio.Notification.new(request['title'])
+        notification.set_body(request['message'])
+        notification.set_priority(Gio.NotificationPriority.NORMAL)
+        notification.set_default_action_and_target(
+            "app.on-notification-clicked",
+            GLib.Variant.new_string(request['name'])
+        )
+
+        if 'buttons' in request:
+            for button_id, button in enumerate(request['buttons']):
+                notification.add_button_with_target(
+                    button['title'],
+                    "app.on-notification-action",
+                    GLib.Variant.new_tuple(
+                        GLib.Variant.new_string(request['name']),
+                        GLib.Variant.new_int32(button_id)
+                    )
+                )
+
+        self.send_notification(request['name'], notification)
+
+    def on_notification_action(self, notification, parameters):
+        debug('Notification %s action: %s' % parameters.unpack())
+
+        self.get_dbus_connection().emit_signal(
+            None,
+            self.get_dbus_object_path(),
+            self.get_application_id(),
+            "NotificationAction",
+            parameters
+        )
+
+    def on_notification_clicked(self, notification, notification_name):
+        debug('Notification %s clicked' % (notification_name))
+
+        self.get_dbus_connection().emit_signal(
+            None,
+            self.get_dbus_object_path(),
+            self.get_application_id(),
+            "NotificationClicked",
+            GLib.Variant.new_tuple(notification_name)
+        )
+
+    def on_service_timeout(self, data):
+        debug('On service timeout')
+        self.release()
 
+        return False
 
+    # Native messaging events
     def on_input(self, source, condition, data):
         debug('On input')
         text_length_bytes = source.read(MESSAGE_LENGTH_SIZE)
@@ -132,29 +212,63 @@ class ChromeGNOMEShell(Gio.Application):
 
             self.process_request(request)
 
+        return True
+
+    def on_dbus_signal(self, connection, sender_name, object_path, interface_name, signal_name, parameters, 
user_data):
+        debug('Signal %s from %s' % (signal_name, interface_name))
 
-    def on_shell_signal(self, d_bus_proxy, sender_name, signal_name, parameters):
-        if signal_name == 'ExtensionStatusChanged':
-            debug('Signal: to %s' % signal_name)
+        if interface_name == "org.gnome.Shell.Extensions" and signal_name == 'ExtensionStatusChanged':
             self.send_message({'signal': signal_name, 'parameters': parameters.unpack()})
-            debug('Signal: from %s' % signal_name)
+        elif interface_name == self.get_application_id():
+            if signal_name == 'NotificationAction':
+                notification_name, button_id = parameters.unpack()
 
+                self.send_message({
+                    'signal': "NotificationAction",
+                    'name': notification_name,
+                    'button_id': button_id
+                })
+            elif signal_name == 'NotificationClicked':
+                (notification_name,) = parameters.unpack()
+
+                self.send_message({
+                    'signal': "NotificationClicked",
+                    'name': notification_name
+                })
 
     def on_shell_appeared(self, connection, name, name_owner):
         debug('Signal: to %s' % name)
         self.send_message({'signal': name})
         debug('Signal: from %s' % name)
 
-
+    # General events
     def on_hup(self, source, condition, data):
         debug('On hup: %s' % str(condition))
         self.release()
 
+        return False
 
     def on_sigint(self, data):
         debug('On sigint')
         self.release()
 
+        return False
+
+    # Helpers
+    def dbus_call_response(self, method, parameters, resultProperty):
+        try:
+            result = self.shell_proxy.call_sync(method,
+                                                parameters,
+                                                Gio.DBusCallFlags.NONE,
+                                                -1,
+                                                None)
+
+            self.send_message({'success': True, resultProperty: result.unpack()[0]})
+        except GLib.GError as e:
+            self.send_error(e.message)
+
+    def send_error(self, message):
+        self.send_message({'success': False, 'message': message})
 
     # Helper function that sends a message to the webapp.
     def send_message(self, response):
@@ -178,30 +292,51 @@ class ChromeGNOMEShell(Gio.Application):
             logError('IOError occured: %s' % e.strerror)
             sys.exit(1)
 
+    def get_variant(self, data, basic_type = False):
+        if isinstance(data, ("".__class__, u"".__class__)) or type(data) is int or basic_type:
+            if isinstance(data, ("".__class__, u"".__class__)):
+                return GLib.Variant.new_string(data)
+            elif type(data) is int:
+                return GLib.Variant.new_int32(data)
+            else:
+                raise Exception("Unknown basic data type: %s, %s" % (type(data), str(data)))
+        elif type(data) is list:
+            variant_builder = GLib.VariantBuilder.new(GLib.VariantType.new('av'))
 
-    def send_error(self, message):
-        self.send_message({'success': False, 'message': message})
+            for value in data:
+                variant_builder.add_value(GLib.Variant.new_variant(self.get_variant(value)))
 
+            return variant_builder.end()
 
-    def dbus_call_response(self, method, parameters, resultProperty):
-        try:
-            result = self.proxy.call_sync(method,
-                                     parameters,
-                                     Gio.DBusCallFlags.NONE,
-                                     -1,
-                                     None)
+        elif type(data) is dict:
+            variant_builder = GLib.VariantBuilder.new(GLib.VariantType.new('a{sv}'))
 
-            self.send_message({'success': True, resultProperty: result.unpack()[0]})
-        except GLib.GError as e:
-            self.send_error(e.message)
+            for key in data:
+                if data[key] is None:
+                    continue
+
+                if sys.version < '3':
+                    # noinspection PyUnresolvedReferences
+                    key_string = unicode(key)
+                else:
+                    key_string = str(key)
 
+                variant_builder.add_value(
+                    GLib.Variant.new_dict_entry(
+                        self.get_variant(key_string, True), 
GLib.Variant.new_variant(self.get_variant(data[key]))
+                    )
+                )
+
+            return variant_builder.end()
+        else:
+            raise Exception("Unknown data type: %s" % type(data))
 
     def process_request(self, request):
         debug('Execute: to %s' % request['execute'])
 
         if request['execute'] == 'initialize':
             settings = Gio.Settings.new(SHELL_SCHEMA)
-            shellVersion = self.proxy.get_cached_property("ShellVersion")
+            shellVersion = self.shell_proxy.get_cached_property("ShellVersion")
             if EXTENSION_DISABLE_VERSION_CHECK_KEY in settings.keys():
                 disableVersionCheck = settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY)
             else:
@@ -214,25 +349,42 @@ class ChromeGNOMEShell(Gio.Application):
                         'connectorVersion': CONNECTOR_VERSION,
                         'shellVersion': shellVersion.unpack(),
                         'versionValidationEnabled': not disableVersionCheck
-                    }
+                    },
+                    'supports': [
+                        'notifications',
+                        'update-check'
+                    ]
                 }
             )
 
         elif request['execute'] == 'subscribeSignals':
             if not self.shellAppearedId:
-                self.shellAppearedId = Gio.bus_watch_name(Gio.BusType.SESSION,
-                                                     'org.gnome.Shell',
-                                                     Gio.BusNameWatcherFlags.NONE,
-                                                     self.on_shell_appeared,
-                                                     None)
+                self.shellAppearedId = Gio.bus_watch_name_on_connection(
+                    self.get_dbus_connection(),
+                    'org.gnome.Shell',
+                    Gio.BusNameWatcherFlags.NONE,
+                    self.on_shell_appeared,
+                    None
+                )
 
             if not self.shellSignalId:
-                self.shellSignalId = self.proxy.connect('g-signal', self.on_shell_signal)
+                self.shellSignalId = self.get_dbus_connection().signal_subscribe(
+                    "org.gnome.Shell",
+                    "org.gnome.Shell.Extensions",
+                    "ExtensionStatusChanged",
+                    "/org/gnome/Shell",
+                    None,
+                    Gio.DBusSignalFlags.NONE,
+                    self.on_dbus_signal,
+                    None
+                )
 
         elif request['execute'] == 'installExtension':
-            self.dbus_call_response("InstallRemoteExtension",
-                               GLib.Variant.new_tuple(GLib.Variant.new_string(request['uuid'])),
-                               "status")
+            self.dbus_call_response(
+                "InstallRemoteExtension",
+                GLib.Variant.new_tuple(GLib.Variant.new_string(request['uuid'])),
+                "status"
+            )
 
         elif request['execute'] == 'listExtensions':
             self.dbus_call_response("ListExtensions", None, "extensions")
@@ -291,8 +443,20 @@ class ChromeGNOMEShell(Gio.Application):
 
             self.check_update(update_url)
 
-
-
+        elif request['execute'] == 'createNotification':
+            Gio.DBusActionGroup.get(
+                app.get_dbus_connection(),
+                app.get_application_id(),
+                app.get_dbus_object_path()
+            ).activate_action('create-notification', self.get_variant({
+                'name': request['name'],
+                'title': request['options']['title'],
+                'message': request['options']['message'],
+                'buttons' : request['options']['buttons']
+            }))
+
+        elif request['execute'] == 'removeNotification':
+            self.withdraw_notification(request['name'])
 
         debug('Execute: from %s' % request['execute'])
 
@@ -307,7 +471,7 @@ class ChromeGNOMEShell(Gio.Application):
 
         if extensions:
             http_request = {
-                'shell_version': self.proxy.get_cached_property("ShellVersion").unpack(),
+                'shell_version': self.shell_proxy.get_cached_property("ShellVersion").unpack(),
                 'installed': {}
             }
 
@@ -342,15 +506,22 @@ class ChromeGNOMEShell(Gio.Application):
                     requests.ConnectionError, requests.HTTPError, requests.Timeout,
                     requests.TooManyRedirects, requests.RequestException, ValueError
                     ) as ex:
-                logError('Unable to check extensions updates: %s' % (str(ex.message) if ('message' in ex) 
else str(ex)))
-                self.send_message({'success': False, 'message': str(ex.message) if ('message' in ex) else 
str(ex)})
+                error_message = str(ex.message) if ('message' in ex) else str(ex)
+                logError('Unable to check extensions updates: %s' % error_message)
+
+                request_url = ex.response.url if ex.response is not None else ex.request.url
+                if request_url:
+                    url_parameters = request_url.replace(update_url, "")
+                    error_message = error_message.replace(url_parameters, "…")
+
+                self.send_message({'success': False, 'message': error_message})
 
 
 if __name__ == '__main__':
     debug('Main. Use CTRL+D to quit.')
+    app = ChromeGNOMEShell('--gapplication-service' in sys.argv)
 
-    app = ChromeGNOMEShell()
-    app.register()
     app.run(sys.argv)
+    app.cleanup()
 
     debug('Quit')
diff --git a/connector/org.gnome.ChromeGnomeShell.desktop b/connector/org.gnome.ChromeGnomeShell.desktop
new file mode 100644
index 0000000..658505f
--- /dev/null
+++ b/connector/org.gnome.ChromeGnomeShell.desktop
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Type=Application
+Encoding=UTF-8
+Name=GNOME Shell integration
+Comment=Provides integration with GNOME Shell and the corresponding extensions repository 
https://extensions.gnome.org.
+DBusActivatable=true
diff --git a/connector/org.gnome.ChromeGnomeShell.service.in b/connector/org.gnome.ChromeGnomeShell.service.in
new file mode 100644
index 0000000..f22fec4
--- /dev/null
+++ b/connector/org.gnome.ChromeGnomeShell.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.ChromeGnomeShell
+Exec=${CMAKE_INSTALL_FULL_BINDIR}/chrome-gnome-shell --gapplication-service
diff --git a/extension/extension.js b/extension/extension.js
index 5c8d87b..ca781cc 100644
--- a/extension/extension.js
+++ b/extension/extension.js
@@ -74,78 +74,104 @@ var port = chrome.runtime.connectNative(NATIVE_HOST);
  * Native host messaging events handler.
  */
 port.onMessage.addListener(function (message) {
-       if (message && message.signal && [SIGNAL_EXTENSION_CHANGED, 
SIGNAL_SHELL_APPEARED].indexOf(message.signal) !== -1)
+       if (message && message.signal)
        {
-               /*
-                * Skip duplicate events. This is happens eg when extension is installed.
-                */
-               if((new Date().getTime()) - lastPortMessage.date < 1000 && GSC.isSignalsEqual(message, 
lastPortMessage.message))
+               if([SIGNAL_EXTENSION_CHANGED, SIGNAL_SHELL_APPEARED].indexOf(message.signal) !== -1)
                {
-                       lastPortMessage.date = new Date().getTime();
-                       return;
-               }
-
-               /*
-                * Send events to opened extensions.gnome.org tabs
-                */
-               chrome.tabs.query({
-                       url: EXTENSIONS_WEBSITE + '*'
-               },
-               function (tabs) {
-                       for (k in tabs)
+                       /*
+                        * Skip duplicate events. This is happens eg when extension is installed.
+                        */
+                       if ((new Date().getTime()) - lastPortMessage.date < 1000 && 
GSC.isSignalsEqual(message, lastPortMessage.message))
                        {
-                               chrome.tabs.sendMessage(tabs[k].id, message);
+                               lastPortMessage.date = new Date().getTime();
+                               return;
                        }
-               });
 
-               /*
-                * Route message to Options page.
-                */
-               chrome.runtime.sendMessage(GS_CHROME_ID, message);
-               if(message.signal === SIGNAL_EXTENSION_CHANGED)
-               {
                        /*
-                        * GNOME Shell sends 2 events when extension is uninstalled:
-                        * "disabled" event and then "uninstalled" event.
-                        * Let's delay any "disabled" event and drop it if
-                        * "uninstalled" event received within 1,5 secs.
+                        * Send events to opened extensions.gnome.org tabs
                         */
-                       if(message.parameters[EXTENSION_CHANGED_STATE] === EXTENSION_STATE.DISABLED)
+                       chrome.tabs.query({
+                                       url: EXTENSIONS_WEBSITE + '*'
+                               },
+                               function (tabs) {
+                                       for (k in tabs)
+                                       {
+                                               chrome.tabs.sendMessage(tabs[k].id, message);
+                                       }
+                               });
+
+                       /*
+                        * Route message to Options page.
+                        */
+                       chrome.runtime.sendMessage(GS_CHROME_ID, message);
+                       if (message.signal === SIGNAL_EXTENSION_CHANGED)
                        {
-                               disabledExtensionTimeout = setTimeout(function() {
+                               /*
+                                * GNOME Shell sends 2 events when extension is uninstalled:
+                                * "disabled" event and then "uninstalled" event.
+                                * Let's delay any "disabled" event and drop it if
+                                * "uninstalled" event received within 1,5 secs.
+                                */
+                               if (message.parameters[EXTENSION_CHANGED_STATE] === EXTENSION_STATE.DISABLED)
+                               {
+                                       disabledExtensionTimeout = setTimeout(function () {
+                                               disabledExtensionTimeout = null;
+                                               GSC.sync.onExtensionChanged(message);
+                                       }, 1500);
+                               }
+                               else if (
+                                       disabledExtensionTimeout &&
+                                       message.parameters[EXTENSION_CHANGED_STATE] === 
EXTENSION_STATE.UNINSTALLED &&
+                                       lastPortMessage.message.signal === SIGNAL_EXTENSION_CHANGED &&
+                                       lastPortMessage.message.parameters[EXTENSION_CHANGED_UUID] === 
message.parameters[EXTENSION_CHANGED_UUID] &&
+                                       lastPortMessage.message.parameters[EXTENSION_CHANGED_STATE] === 
EXTENSION_STATE.DISABLED
+                               )
+                               {
+                                       clearTimeout(disabledExtensionTimeout);
                                        disabledExtensionTimeout = null;
                                        GSC.sync.onExtensionChanged(message);
-                               }, 1500);
-                       }
-                       else if(
-                               disabledExtensionTimeout &&
-                               message.parameters[EXTENSION_CHANGED_STATE] === EXTENSION_STATE.UNINSTALLED &&
-                               lastPortMessage.message.signal === SIGNAL_EXTENSION_CHANGED &&
-                               lastPortMessage.message.parameters[EXTENSION_CHANGED_UUID] === 
message.parameters[EXTENSION_CHANGED_UUID] &&
-                               lastPortMessage.message.parameters[EXTENSION_CHANGED_STATE] === 
EXTENSION_STATE.DISABLED
-                       )
-                       {
-                               clearTimeout(disabledExtensionTimeout);
-                               disabledExtensionTimeout = null;
-                               GSC.sync.onExtensionChanged(message);
-                       }
-                       else
-                       {
-                               GSC.sync.onExtensionChanged(message);
+                               }
+                               else
+                               {
+                                       GSC.sync.onExtensionChanged(message);
+                               }
                        }
-               }
 
-               lastPortMessage = {
-                       message: message,
-                       date: new Date().getTime()
-               };
+                       lastPortMessage = {
+                               message: message,
+                               date: new Date().getTime()
+                       };
+               }
+               else if([SIGNAL_NOTIFICATION_ACTION, SIGNAL_NOTIFICATION_CLICKED].indexOf(message.signal) != 
-1)
+               {
+                       chrome.runtime.sendMessage(message);
+               }
        }
 });
 /*
- * Subsctibe to GNOME Shell signals
+ * Subscribe to GNOME Shell signals
  */
 port.postMessage({execute: 'subscribeSignals'});
 
+chrome.runtime.onMessage.addListener(
+       function (request, sender, sendResponse) {
+               if(
+                       sender.id && sender.id === GS_CHROME_ID &&
+                       request && request.execute)
+               {
+                       switch (request.execute)
+                       {
+                               case 'createNotification':
+                                       port.postMessage(request);
+                                       break;
+                               case 'removeNotification':
+                                       port.postMessage(request);
+                                       break;
+                       }
+               }
+       }
+);
+
 chrome.runtime.getPlatformInfo(function(info) {
        if (PLATFORMS_WHITELIST.indexOf(info.os) !== -1)
        {
diff --git a/extension/include/constants.js b/extension/include/constants.js
index 96ae2ab..1b14dd6 100644
--- a/extension/include/constants.js
+++ b/extension/include/constants.js
@@ -22,6 +22,8 @@ MESSAGE_NEXT_UPDATE_CHANGED           = 'gs-next-update-changed';
 MESSAGE_SYNC_FROM_REMOTE               = 'gs-sync-from-remote';
 
 SIGNAL_EXTENSION_CHANGED               = 'ExtensionStatusChanged';
+SIGNAL_NOTIFICATION_ACTION             = 'NotificationAction';
+SIGNAL_NOTIFICATION_CLICKED            = 'NotificationClicked';
 SIGNAL_SHELL_APPEARED                  = 'org.gnome.Shell';
 
 EXTENSION_CHANGED_UUID                 = 0;
diff --git a/extension/include/gsc.js b/extension/include/gsc.js
index 8f71ae0..b5d64d7 100644
--- a/extension/include/gsc.js
+++ b/extension/include/gsc.js
@@ -23,59 +23,78 @@ GSC = (function() {
        });
        ready.catch(function() {});
 
+       var onInitialize = new Promise((resolve, reject) => {
+               sendNativeRequest({ execute: "initialize" }, (response) => {
+                       if(response && response.success)
+                       {
+                               resolve(response);
+                       }
+                       else
+                       {
+                               reject(response);
+                       }
+               });
+       });
+
+       function sendNativeRequest(request, sendResponse) {
+               ready.then(function () {
+                       if (sendResponse)
+                       {
+                               chrome.runtime.sendNativeMessage(
+                                       NATIVE_HOST,
+                                       request,
+                                       function (response) {
+                                               if (response)
+                                               {
+                                                       sendResponse(response);
+                                               }
+                                               else
+                                               {
+                                                       var message = m('no_host_connector');
+                                                       if (
+                                                               chrome.runtime.lastError &&
+                                                               chrome.runtime.lastError.message &&
+                                                               
chrome.runtime.lastError.message.indexOf("host not found") === -1
+                                                       )
+                                                       {
+                                                               // Some error occured. Show to user
+                                                               message = chrome.runtime.lastError.message;
+                                                       }
+
+                                                       sendResponse({
+                                                               success: false,
+                                                               message: message
+                                                       });
+                                               }
+                                       }
+                               );
+                       }
+                       else
+                       {
+                               chrome.runtime.sendNativeMessage(NATIVE_HOST, request);
+                       }
+               }, function () {
+                       if (sendResponse)
+                       {
+                               sendResponse({
+                                       success: false,
+                                       message: m('platform_not_supported')
+                               });
+                       }
+               });
+       }
+
+       function isSupported(feature, response) {
+               return response.supports && response.supports.indexOf(feature) !== -1;
+       }
+
        return {
                // https://wiki.gnome.org/Projects/GnomeShell/Extensions/UUIDGuidelines
                isUUID: function(uuid) {
                        return uuid && uuid.match('^[-a-zA-Z0-9@._]+$');
                },
 
-               sendNativeRequest: function(request, sendResponse) {
-                       ready.then(function() {
-                               if(sendResponse)
-                               {
-                                       chrome.runtime.sendNativeMessage(
-                                               NATIVE_HOST,
-                                               request,
-                                               function (response) {
-                                                       if (response)
-                                                       {
-                                                               sendResponse(response);
-                                                       }
-                                                       else
-                                                       {
-                                                               var message = m('no_host_connector');
-                                                               if(
-                                                                       chrome.runtime.lastError &&
-                                                                       chrome.runtime.lastError.message &&
-                                                                       
chrome.runtime.lastError.message.indexOf("host not found") === -1
-                                                               )
-                                                               {
-                                                                       // Some error occured. Show to user
-                                                                       message = 
chrome.runtime.lastError.message;
-                                                               }
-
-                                                               sendResponse({
-                                                                       success: false,
-                                                                       message: message
-                                                               });
-                                                       }
-                                               }
-                                       );
-                               }
-                               else
-                               {
-                                       chrome.runtime.sendNativeMessage(NATIVE_HOST, request);
-                               }
-                       }, function() {
-                               if(sendResponse)
-                               {
-                                       sendResponse({
-                                               success: false,
-                                               message: m('platform_not_supported')
-                                       });
-                               }
-                       });
-               },
+               sendNativeRequest: sendNativeRequest,
 
                isSignalsEqual: function(newSignal, oldSignal) {
                        if(!oldSignal || !newSignal)
@@ -106,6 +125,14 @@ GSC = (function() {
                        }
 
                        return true;
+               },
+
+               onInitialize: function() {
+                       return onInitialize;
+               },
+
+               nativeNotificationsSupported: function (response) {
+                       return isSupported('notifications', response);
                }
        };
 })();
diff --git a/extension/include/notifications.js b/extension/include/notifications.js
index 2c12141..7913300 100644
--- a/extension/include/notifications.js
+++ b/extension/include/notifications.js
@@ -9,6 +9,47 @@
  */
 
 GSC.notifications = (function($) {
+       var DEFAULT_NOTIFICATION_OPTIONS = {
+               type: chrome.notifications.TemplateType.BASIC,
+               iconUrl: 'icons/GnomeLogo-128.png',
+               title: m('gs_chrome'),
+               buttons: [
+                       {title: m('close')}
+               ],
+               priority: 2,
+               isClickable: true,
+               requireInteraction: true
+       };
+
+       function remove_list(options) {
+               if(options.items)
+               {
+                       var items = [];
+                       for (k in options.items)
+                       {
+                               if (options.items.hasOwnProperty(k))
+                               {
+                                       items.push(options.items[k].title + ' ' + options.items[k].message);
+                               }
+                       }
+
+                       if(options.message && items)
+                       {
+                               options.message += "\n";
+                       }
+
+                       options.message += items.join("\n");
+
+                       options.type = chrome.notifications.TemplateType.BASIC;
+                       delete options.items;
+               }
+
+               return options;
+       }
+
+       /*
+               @Deprecated: remove browser notifications in version 9
+        */
        var browser = (function() {
                function init() {
                        chrome.runtime.onStartup.addListener(function() {
@@ -39,16 +80,7 @@ GSC.notifications = (function($) {
                        }, function (items) {
                                var notifications = items.notifications;
 
-                               notifications[name] = $.extend({
-                                       type: chrome.notifications.TemplateType.BASIC,
-                                       iconUrl: 'icons/GnomeLogo-128.png',
-                                       title: 'GNOME Shell integration',
-                                       buttons: [
-                                               {title: m('close')}
-                                       ],
-                                       priority: 2,
-                                       isClickable: true
-                               }, options);
+                               notifications[name] = $.extend(DEFAULT_NOTIFICATION_OPTIONS, options);
 
                                _create(name, notifications[name], function (notificationId) {
                                        chrome.storage.local.set({
@@ -67,18 +99,7 @@ GSC.notifications = (function($) {
                                delete options.buttons;
                                if(options.type === chrome.notifications.TemplateType.LIST)
                                {
-                                       var items = [];
-                                       for(k in options.items)
-                                       {
-                                               if(options.items.hasOwnProperty(k))
-                                               {
-                                                       items.push(options.items[k].title + ' ' + 
options.items[k].message);
-                                               }
-                                       }
-                                       options.message += "\n" + items.join("\n");
-
-                                       options.type = chrome.notifications.TemplateType.BASIC;
-                                       delete options.items;
+                                       options = remove_list(options);
                                }
                        }
 
@@ -136,7 +157,30 @@ GSC.notifications = (function($) {
                        });
                }
 
-               init();
+               return {
+                       create: create,
+                       remove: remove,
+                       init: init
+               };
+       })();
+
+       var native = (function() {
+               function create(name, options) {
+                       options = remove_list(options);
+
+                       chrome.runtime.sendMessage({
+                               execute: 'createNotification',
+                               name: name,
+                               options: $.extend(DEFAULT_NOTIFICATION_OPTIONS, options)
+                       });
+               }
+
+               function remove(notificationId) {
+                       chrome.runtime.sendMessage({
+                               execute: 'removeNotification',
+                               name: notificationId
+                       });
+               }
 
                return {
                        create: create,
@@ -144,9 +188,37 @@ GSC.notifications = (function($) {
                };
        })();
 
+       GSC.onInitialize().then(response => {
+               if (!GSC.nativeNotificationsSupported(response))
+               {
+                       browser.init();
+               }
+       });
 
        return {
-               create: browser.create,
-               remove: browser.remove
+               create: function() {
+                       GSC.onInitialize().then(response => {
+                               if(GSC.nativeNotificationsSupported(response))
+                               {
+                                       native.create.apply(this, arguments);
+                               }
+                               else
+                               {
+                                       browser.create.apply(this, arguments);
+                               }
+                       });
+               },
+               remove: function() {
+                       GSC.onInitialize().then(response => {
+                               if(GSC.nativeNotificationsSupported(response))
+                               {
+                                       native.remove.apply(this, arguments);
+                               }
+                               else
+                               {
+                                       browser.remove.apply(this, arguments);
+                               }
+                       });
+               }
        };
 })(jQuery);
diff --git a/extension/include/sync.js b/extension/include/sync.js
index 80be629..95a1e1f 100644
--- a/extension/include/sync.js
+++ b/extension/include/sync.js
@@ -22,6 +22,15 @@ GSC.sync = (function($) {
                        return;
                }
 
+               function onNotificationAction(notificationId, buttonIndex) {
+                       if (notificationId !== NOTIFICATION_SYNC_FAILED)
+                       {
+                               return;
+                       }
+
+                       GSC.notifications.remove(notificationId);
+               }
+
                onSyncFromRemote();
                chrome.storage.onChanged.addListener(function(changes, areaName) {
                        if(areaName === 'sync' && changes.extensions)
@@ -42,11 +51,30 @@ GSC.sync = (function($) {
                        }
                );
 
-               chrome.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
-                       if (notificationId !== NOTIFICATION_SYNC_FAILED)
-                               return;
-
-                       GSC.notifications.remove(notificationId);
+               GSC.onInitialize().then(response => {
+                       /*
+                               @Deprecated: remove browser notifications in version 9
+                        */
+                       if (!GSC.nativeNotificationsSupported(response))
+                       {
+                               chrome.notifications.onButtonClicked.addListener(onNotificationAction);
+                       }
+                       else
+                       {
+                               chrome.runtime.onMessage.addListener(
+                                       function (request, sender, sendResponse) {
+                                               if(
+                                                       sender.id && sender.id === GS_CHROME_ID &&
+                                                       request && request.signal)
+                                               {
+                                                       if(request.signal == SIGNAL_NOTIFICATION_ACTION)
+                                                       {
+                                                               onNotificationAction(request.name, 
request.button_id);
+                                                       }
+                                               }
+                                       }
+                               );
+                       }
                });
        }
 
diff --git a/extension/include/update.js b/extension/include/update.js
index 5ac37c4..922cf74 100644
--- a/extension/include/update.js
+++ b/extension/include/update.js
@@ -180,6 +180,28 @@ GSC.update = (function($) {
        }
 
        function init() {
+               function onNotificationAction(notificationId, buttonIndex) {
+                       if ($.inArray(notificationId, [NOTIFICATION_UPDATE_AVAILABLE, 
NOTIFICATION_UPDATE_CHECK_FAILED]) === -1)
+                               return;
+
+                       if (notificationId === NOTIFICATION_UPDATE_CHECK_FAILED && buttonIndex == 0)
+                       {
+                               check();
+                       }
+
+                       GSC.notifications.remove(notificationId);
+               }
+
+               function onNotificationClicked(notificationId) {
+                       if (notificationId === NOTIFICATION_UPDATE_AVAILABLE)
+                       {
+                               chrome.tabs.create({
+                                       url: EXTENSIONS_WEBSITE + 'local/',
+                                       active: true
+                               });
+                       }
+               }
+
                chrome.alarms.onAlarm.addListener(function (alarm) {
                        if (alarm.name === ALARM_UPDATE_CHECK)
                        {
@@ -198,26 +220,38 @@ GSC.update = (function($) {
                        }
                });
 
-               chrome.notifications.onClicked.addListener(function (notificationId) {
-                       if (notificationId === NOTIFICATION_UPDATE_AVAILABLE)
+               GSC.onInitialize().then(response => {
+                       /*
+                               @Deprecated: remove browser notifications in version 9
+                        */
+                       if (!GSC.nativeNotificationsSupported(response))
                        {
-                               chrome.tabs.create({
-                                       url: EXTENSIONS_WEBSITE + 'local/',
-                                       active: true
+                               chrome.notifications.onClicked.addListener(function (notificationId) {
+                                       onNotificationClicked(notificationId);
                                });
-                       }
-               });
 
-               chrome.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
-                       if ($.inArray(notificationId, [NOTIFICATION_UPDATE_AVAILABLE, 
NOTIFICATION_UPDATE_CHECK_FAILED]) === -1)
-                               return;
-
-                       if (notificationId === NOTIFICATION_UPDATE_CHECK_FAILED && buttonIndex === 0)
+                               chrome.notifications.onButtonClicked.addListener(onNotificationAction);
+                       }
+                       else
                        {
-                               check();
+                               chrome.runtime.onMessage.addListener(
+                                       function (request, sender, sendResponse) {
+                                               if(
+                                                       sender.id && sender.id === GS_CHROME_ID &&
+                                                       request && request.signal)
+                                               {
+                                                       if(request.signal == SIGNAL_NOTIFICATION_ACTION)
+                                                       {
+                                                               onNotificationAction(request.name, 
request.button_id);
+                                                       }
+                                                       else if(request.signal == SIGNAL_NOTIFICATION_CLICKED)
+                                                       {
+                                                               onNotificationClicked(request.name);
+                                                       }
+                                               }
+                                       }
+                               );
                        }
-
-                       GSC.notifications.remove(notificationId);
                });
 
                chrome.storage.onChanged.addListener(function (changes, areaName) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]