From 7d0514bdbf536a1b803b76f6b372546fdd727691 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Wed, 24 Apr 2019 10:05:39 -0700 Subject: [PATCH 1/3] Update the shell extension Make it cooperate better with the app --- shell-extension/extension.js | 59 +++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/shell-extension/extension.js b/shell-extension/extension.js index 6962f8b..db88bb0 100644 --- a/shell-extension/extension.js +++ b/shell-extension/extension.js @@ -46,7 +46,7 @@ const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; -const { AssistantModel, Direction, MessageType } = Me.imports.common.chatmodel; +const { AssistantModel, Direction, MessageType, Message } = Me.imports.common.chatmodel; const Config = Me.imports.common.config; const { Service } = Me.imports.common.serviceproxy; @@ -70,12 +70,8 @@ class AssistantNotification extends MessageTray.Notification { } activate() { - // override the default behavior: close the calendar and open Almond - Main.panel.closeCalendar(); - this.source.notify(this); - - // do not chain up: the default implementation emits "activated" - // which triggers a number of undesirable behaviors + this.source.open(); + super.activate(); } destroy(reason) { @@ -119,7 +115,7 @@ const MessageConstructors = { let textureCache = St.TextureCache.get_default(); let file = Gio.File.new_for_uri(msg.picture_url); let scaleFactor = St.ThemeContext.get_for_stage(window.global.stage).scale_factor; - let texture = textureCache.load_file_async(file, -1, 300, scaleFactor); + let texture = textureCache.load_file_async(file, -1, 300, scaleFactor, 1.0); return new St.Bin({ child: texture }); }, @@ -311,17 +307,6 @@ class AssistantNotificationBanner extends MessageTray.NotificationBanner { lineBox.add(body); msgObject.actor = lineBox; this._contentArea.insert_child_at_index(lineBox, position); - - if (message.direction === Direction.FROM_ALMOND) { - if (position === this._contentArea.get_n_children()-1) - this.notification.source.setMessageIcon(message.icon); - let msgBody = message.toNotification(); - if (msgBody) { - this.notification.update(this.notification.source.title, msgBody, { - secondaryGIcon: this.notification.source.getSecondaryIcon() - }); - } - } } _onEntryActivated() { @@ -397,15 +382,41 @@ class AssistantSource extends MessageTray.Source { this.service.HandleCommandRemote(text, onerror); } + _activateIfAlmondUnfocused() { + const focus_app = Shell.WindowTracker.get_default().focus_app; + if (focus_app && focus_app.get_id() === 'edu.stanford.Almond.desktop') + return; + + this.notify(this._notification); + } + _continueInit() { // Add ourselves as a source. Main.messageTray.add(this); - this.service.connectSignal('NewMessage', () => { - this.notify(this._notification); + this.service.connectSignal('NewMessage', (signal, sender, [id, type, direction, msg]) => { + if (direction !== Direction.FROM_ALMOND) + return; + + msg.message_id = id; + msg.message_type = type; + msg.direction = direction; + const message = new Message(msg); + + this.setMessageIcon(message.icon); + let msgBody = message.toNotification(); + log('msgBody: ' + msgBody); + + if (msgBody && this._notification) { + this._notification.update(this.title, msgBody, { + secondaryGIcon: this.getSecondaryIcon() + }); + } + + this._activateIfAlmondUnfocused(); }); this.service.connectSignal('Activate', () => { - this.notify(this._notification); + this._activateIfAlmondUnfocused(); }); this.service.connectSignal('VoiceHypothesis', (signal, sender, [hyp]) => { if (!this._banner) @@ -460,8 +471,8 @@ class AssistantSource extends MessageTray.Source { Main.overview.hide(); Main.panel.closeCalendar(); - let app = Shell.AppSystem.get_default().lookup_app('edu.stanford.Almond'); - app.launch(); + let app = Shell.AppSystem.get_default().lookup_app('edu.stanford.Almond.desktop'); + app.activate(); } } From 8b6525cbc466c4574f3cebafcaec195b37c1fefe Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Wed, 24 Apr 2019 10:30:06 -0700 Subject: [PATCH 2/3] shell-extension: improve the styling of buttons and links --- shell-extension/extension.js | 7 +++++-- shell-extension/meson.build | 1 + shell-extension/stylesheet.css | 4 ++++ src/common/chatmodel.js | 4 ++-- 4 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 shell-extension/stylesheet.css diff --git a/shell-extension/extension.js b/shell-extension/extension.js index db88bb0..ec04579 100644 --- a/shell-extension/extension.js +++ b/shell-extension/extension.js @@ -139,6 +139,8 @@ const MessageConstructors = { [MessageType.BUTTON](msg, service) { let button = new St.Button(); + button.add_style_class_name('button'); + button.add_style_class_name('almond-button'); msg.bind_property('text', button, 'label', GObject.BindingFlags.SYNC_CREATE); button.connect('clicked', () => { service.HandleParsedCommandRemote(msg.text, msg.json, (error) => { @@ -184,6 +186,7 @@ const MessageConstructors = { }); msg.bind_property('text', button, 'label', GObject.BindingFlags.SYNC_CREATE); button.add_style_class_name('almond-rdl-title'); + button.add_style_class_name('shell-link'); box.add_actor(button); let description = new St.Label(); @@ -218,7 +221,7 @@ class AssistantNotificationBanner extends MessageTray.NotificationBanner { vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, hscrollbar_policy: Gtk.PolicyType.NEVER, visible: this.expanded }); - this._contentArea = new St.BoxLayout({ style_class: 'chat-body', + this._contentArea = new St.BoxLayout({ style_class: 'chat-body almond-chatview', vertical: true }); this._scrollArea.add_actor(this._contentArea); @@ -304,7 +307,7 @@ class AssistantNotificationBanner extends MessageTray.NotificationBanner { } let lineBox = new AssistantLineBox(); - lineBox.add(body); + lineBox.add(body, { expand: true, x_fill: true, y_fill: true }); msgObject.actor = lineBox; this._contentArea.insert_child_at_index(lineBox, position); } diff --git a/shell-extension/meson.build b/shell-extension/meson.build index 770af67..52a614f 100644 --- a/shell-extension/meson.build +++ b/shell-extension/meson.build @@ -4,6 +4,7 @@ install_data( files('extension.js'), files('convenience.js'), files('metadata.json'), + files('stylesheet.css'), install_dir: shell_extension_dir ) diff --git a/shell-extension/stylesheet.css b/shell-extension/stylesheet.css new file mode 100644 index 0000000..bf389a4 --- /dev/null +++ b/shell-extension/stylesheet.css @@ -0,0 +1,4 @@ +.almond-button { + text-align: center; + margin: 0 24px; +} diff --git a/src/common/chatmodel.js b/src/common/chatmodel.js index 8f344df..ca1e7f4 100644 --- a/src/common/chatmodel.js +++ b/src/common/chatmodel.js @@ -28,7 +28,7 @@ var MessageType = { MAX: 6 }; -const Message = GObject.registerClass({ +var Message = GObject.registerClass({ Properties: { message_id: GObject.ParamSpec.int('message-id', '', '', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 0, GLib.MAXINT32, 0), message_type: GObject.ParamSpec.int('message-type', '','', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, 0, MessageType.MAX, 0), @@ -137,4 +137,4 @@ var AssistantModel = class AssistantModel { } } }; -Signals.addSignalMethods(AssistantModel.prototype); \ No newline at end of file +Signals.addSignalMethods(AssistantModel.prototype); From e7fc9ff878aac3df9cb1e629268c1046b95ae7ce Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Wed, 24 Apr 2019 10:55:53 -0700 Subject: [PATCH 3/3] shell-extension: implement Link buttons So we can configure devices from the extension, if needed --- shell-extension/extension.js | 64 ++++++++++++++++++++++++++++++++++-- src/app/chatview.js | 2 +- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/shell-extension/extension.js b/shell-extension/extension.js index ec04579..8bdb1fb 100644 --- a/shell-extension/extension.js +++ b/shell-extension/extension.js @@ -31,12 +31,13 @@ const MessageTray = imports.ui.messageTray; //const Params = imports.misc.params; //const Util = imports.misc.util; const Gio = imports.gi.Gio; -//const GLib = imports.gi.GLib; +const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const St = imports.gi.St; const Shell = imports.gi.Shell; const Pango = imports.gi.Pango; +const Soup = imports.gi.Soup; //const Clutter = imports.gi.Clutter; const Gettext = imports.gettext.domain('edu.stanford.Almond'); @@ -102,6 +103,29 @@ function handleSpecial(service, title, special) { }); } +function activateGtkAction(actionName, actionParameter) { + const app = Shell.AppSystem.get_default().lookup_app('edu.stanford.Almond.desktop'); + + let attempts = 0; + function _continue() { + attempts ++; + if (attempts >= 10) + return GLib.SOURCE_REMOVE; + const actionGroup = app.action_group; + if (actionGroup === null) + return GLib.SOURCE_CONTINUE; + actionGroup.activate_action(actionName, actionParameter); + return GLib.SOURCE_REMOVE; + } + + if (app.action_group) { + _continue(); + } else { + app.activate(); + GLib.timeout_add(GLib.PRIORITY_DEFAULT, _continue, 500); + } +} + const MessageConstructors = { [MessageType.TEXT](msg) { let label = new St.Label(); @@ -121,6 +145,8 @@ const MessageConstructors = { [MessageType.CHOICE](msg, service) { let button = new St.Button(); + button.add_style_class_name('button'); + button.add_style_class_name('almond-button'); msg.bind_property('text', button, 'label', GObject.BindingFlags.SYNC_CREATE); button.connect('clicked', () => { let choiceJSON = JSON.stringify({ code: ['bookkeeping', 'choice', String(msg.choice_idx)], entities: {} }); @@ -132,9 +158,39 @@ const MessageConstructors = { return button; }, - [MessageType.LINK]() { + [MessageType.LINK](msg, service) { // recognize what kind of link this is, and spawn the app in the right way - return null; + let button = new St.Button(); + button.add_style_class_name('button'); + button.add_style_class_name('almond-button'); + msg.bind_property('text', button, 'label', GObject.BindingFlags.SYNC_CREATE); + + if (msg.link === '/user/register') { + // ??? we are not anonymous, this should never happen + throw new Error('Invalid link asking the user to register'); + } else if (msg.link === '/thingpedia/cheatsheet') { + button.connect('clicked', () => { + const url = 'https://thingpedia.stanford.edu' + msg.link + Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context(0, -1)); + }); + } else if (msg.link === '/apps') { + button.connect('clicked', () => { + activateGtkAction('win.switch-to', new GLib.Variant('s', 'page-my-stuff')); + }); + button.set_detailed_action_name('win.switch-to::page-my-stuff'); + } else if (msg.link.startsWith('/devices/oauth2/')) { + // "parse" the link in the context of a dummy base URI + let uri = Soup.URI.new_with_base(Soup.URI.new('https://invalid'), msg.link); + let kind = uri.get_path().substring('/devices/oauth2/'.length); + let query = Soup.form_decode(uri.get_query()); + button.connect('clicked', () => { + activateGtkAction('win.configure-device-oauth2', new GLib.Variant('(ss)', [kind, query.name||''])); + }); + } else { + throw new Error('Unexpected link to ' + msg.link); + } + + return button; }, [MessageType.BUTTON](msg, service) { @@ -154,6 +210,7 @@ const MessageConstructors = { [MessageType.ASK_SPECIAL](msg, service) { if (msg.ask_special_what === 'yesno') { let box = new St.BoxLayout(); + yes.add_style_class_name('button'); let yes = new St.Button({ label: _("Yes") }); @@ -165,6 +222,7 @@ const MessageConstructors = { let no = new St.Button({ label: _("No") }); + no.add_style_class_name('button'); no.connect('clicked', () => { handleSpecial(service, _("No"), 'no'); }); diff --git a/src/app/chatview.js b/src/app/chatview.js index d13226c..41b18c6 100644 --- a/src/app/chatview.js +++ b/src/app/chatview.js @@ -244,7 +244,7 @@ const MessageConstructors = { Gtk.get_current_event_time()); }); } else if (msg.link === '/apps') { - button.set_detailed_action_name('win.switch-to::page-my-goods'); + button.set_detailed_action_name('win.switch-to::page-my-stuff'); } else if (msg.link.startsWith('/devices/oauth2/')) { // "parse" the link in the context of a dummy base URI let uri = Soup.URI.new_with_base(Soup.URI.new('https://invalid'), msg.link);