From 57ff206de3c302b76c5cf34f5107e7fdab35b35f Mon Sep 17 00:00:00 2001 From: Lyr Date: Sat, 15 Jul 2023 01:46:19 +0200 Subject: [PATCH 1/4] added support for gnome 43 and 44 --- .prettierrc | 5 + extension.js | 595 +++++++++++++++++++++++++++++--------------------- metadata.json | 8 +- 3 files changed, 358 insertions(+), 250 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e97ea1b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 2, + "useTabs": false, + "singleQuote": true +} diff --git a/extension.js b/extension.js index 64fbfc9..7af4622 100644 --- a/extension.js +++ b/extension.js @@ -1,305 +1,408 @@ -const { St, Gio, Gtk, Soup, Clutter } = imports.gi; +const { Soup, St, Gio, Gtk, Clutter, GLib } = imports.gi; const Main = imports.ui.main; const Mainloop = imports.mainloop; - +const ByteArray = imports.byteArray; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Util = imports.misc.util; const MessageTray = imports.ui.messageTray; +let { PACKAGE_VERSION } = imports.misc.config; +PACKAGE_VERSION = Number(PACKAGE_VERSION); let githubNotifications; -function info(message) { - global.log('[GITHUB NOTIFICATIONS EXTENSION][INFO] ' + message); +function info(...messages) { + for (const m of messages) { + console.log('[GITHUB NOTIFICATIONS EXTENSION][INFO] ' + m); + } } -function error(message) { - global.log('[GITHUB NOTIFICATIONS EXTENSION][ERROR] ' + message); +function error(...messages) { + for (const m of messages) { + console.log('[GITHUB NOTIFICATIONS EXTENSION][ERROR] ' + m); + } } class GithubNotifications { - constructor() { - this.token = ''; - this.handle = ''; - this.hideWidget = false; - this.hideCount = false; - this.refreshInterval = 60; - this.githubInterval = 60; - this.timeout = null; - this.httpSession = null; - this.notifications = []; - this.lastModified = null; - this.retryAttempts = 0; - this.retryIntervals = [60, 120, 240, 480, 960, 1920, 3600]; - this.hasLazilyInit = false; - this.showAlertNotification = false; - this.showParticipatingOnly = false; - this._source = null; - this.settings = null; + constructor() { + this.token = ''; + this.handle = ''; + this.hideWidget = false; + this.hideCount = false; + this.refreshInterval = 60; + this.githubInterval = 60; + this.timeout = null; + this.httpSession = null; + this.notifications = []; + this.lastModified = null; + this.retryAttempts = 0; + this.retryIntervals = [60, 120, 240, 480, 960, 1920, 3600]; + this.hasLazilyInit = false; + this.showAlertNotification = false; + this.showParticipatingOnly = false; + this._source = null; + this.settings = null; + } + + interval() { + let i = this.refreshInterval; + if (this.retryAttempts > 0) { + i = this.retryIntervals[this.retryAttempts] || 3600; } - - interval() { - let i = this.refreshInterval - if (this.retryAttempts > 0) { - i = this.retryIntervals[this.retryAttempts] || 3600; - } - return Math.max(i, this.githubInterval); + return Math.max(i, this.githubInterval); + } + + lazyInit() { + this.hasLazilyInit = true; + this.reloadSettings(); + this.initHttp(); + this.settings.connect('changed', () => { + this.reloadSettings(); + this.initHttp(); + this.stopLoop(); + this.planFetch(5, false); + }); + this.initUI(); + } + + start() { + this.settings = ExtensionUtils.getSettings( + 'org.gnome.shell.extensions.github.notifications' + ); + if (!this.hasLazilyInit) { + this.lazyInit(); } - - lazyInit() { - this.hasLazilyInit = true; - this.reloadSettings(); - this.initHttp(); - this.settings.connect('changed', () => { - this.reloadSettings(); - this.initHttp(); - this.stopLoop(); - this.planFetch(5, false); - }); - this.initUI(); + this.fetchNotifications(); + Main.panel._rightBox.insert_child_at_index(this.box, 0); + } + + stop() { + this.stopLoop(); + Main.panel._rightBox.remove_child(this.box); + } + + reloadSettings() { + this.domain = this.settings.get_string('domain'); + this.token = this.settings.get_string('token'); + this.handle = this.settings.get_string('handle'); + this.hideWidget = this.settings.get_boolean('hide-widget'); + this.hideCount = this.settings.get_boolean('hide-notification-count'); + this.refreshInterval = this.settings.get_int('refresh-interval'); + this.showAlertNotification = this.settings.get_boolean('show-alert'); + this.showParticipatingOnly = this.settings.get_boolean( + 'show-participating-only' + ); + this.checkVisibility(); + } + + checkVisibility() { + if (this.box) { + this.box.visible = !this.hideWidget || this.notifications.length != 0; } - - start() { - this.settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.github.notifications'); - if (!this.hasLazilyInit) { - this.lazyInit(); - } - this.fetchNotifications(); - Main.panel._rightBox.insert_child_at_index(this.box, 0); + if (this.label) { + this.label.visible = !this.hideCount; } + } - stop() { - this.stopLoop(); - Main.panel._rightBox.remove_child(this.box); + stopLoop() { + if (this.timeout) { + Mainloop.source_remove(this.timeout); + this.timeout = null; } - - reloadSettings() { - this.domain = this.settings.get_string('domain'); - this.token = this.settings.get_string('token'); - this.handle = this.settings.get_string('handle'); - this.hideWidget = this.settings.get_boolean('hide-widget'); - this.hideCount = this.settings.get_boolean('hide-notification-count'); - this.refreshInterval = this.settings.get_int('refresh-interval'); - this.showAlertNotification = this.settings.get_boolean('show-alert'); - this.showParticipatingOnly = this.settings.get_boolean('show-participating-only'); - this.checkVisibility(); + } + + initUI() { + this.box = new St.BoxLayout({ + style_class: 'panel-button', + reactive: true, + can_focus: true, + track_hover: true, + }); + this.label = new St.Label({ + text: '' + this.notifications.length, + style_class: 'system-status-icon notifications-length', + y_align: Clutter.ActorAlign.CENTER, + y_expand: true, + }); + + this.checkVisibility(); + + let icon = new St.Icon({ + style_class: 'system-status-icon', + }); + icon.gicon = Gio.icon_new_for_string(`${Me.path}/github.svg`); + + this.box.add_actor(icon); + this.box.add_actor(this.label); + + this.box.connect('button-press-event', (_, event) => { + let button = event.get_button(); + + if (button == 1) { + this.showBrowserUri(); + } else if (button == 3) { + ExtensionUtils.openPrefs(); + } + }); + } + + showBrowserUri() { + try { + let url = 'https://' + this.domain + '/notifications'; + if (this.showParticipatingOnly) { + url = 'https://' + this.domain + '/notifications/participating'; + } + + Gtk.show_uri(null, url, Gtk.get_current_event_time()); + } catch (e) { + error('Cannot open uri ' + e); } + } - checkVisibility() { - if (this.box) { - this.box.visible = !this.hideWidget || this.notifications.length != 0; - } - if (this.label) { - this.label.visible = !this.hideCount; - } + initHttp() { + let path = '/notifications'; + if (this.showParticipatingOnly) { + path = '/notifications?participating=1'; } - stopLoop() { - if (this.timeout) { - Mainloop.source_remove(this.timeout); - this.timeout = null; - } + if (PACKAGE_VERSION >= 43) { + this.authUri = GLib.Uri.build( + GLib.UriFlags.None, + 'https', + null, + 'api.' + this.domain, + -1, + path, + null, + null + ); + } else { + let url = 'https://api.' + this.domain + path; + this.authUri = new Soup.URI(url); + this.authUri.set_user(this.handle); + this.authUri.set_password(this.token); } - initUI() { - this.box = new St.BoxLayout({ - style_class: 'panel-button', - reactive: true, - can_focus: true, - track_hover: true - }); - this.label = new St.Label({ - text: '' + this.notifications.length, - style_class: 'system-status-icon notifications-length', - y_align: Clutter.ActorAlign.CENTER, - y_expand: true, - }); - - this.checkVisibility(); - - let icon = new St.Icon({ - style_class: 'system-status-icon' - }); - icon.gicon = Gio.icon_new_for_string(`${Me.path}/github.svg`); - - this.box.add_actor(icon); - this.box.add_actor(this.label); - - this.box.connect('button-press-event', (_, event) => { - let button = event.get_button(); - - if (button == 1) { - this.showBrowserUri(); - } else if (button == 3) { - ExtensionUtils.openPrefs(); - } + if (this.httpSession) { + this.httpSession.abort(); + } else { + this.httpSession = new Soup.Session(); + this.httpSession.user_agent = + 'gnome-shell-extension github notification via libsoup'; + + if (PACKAGE_VERSION >= 43) { + this.auth = new Soup.AuthBasic(); + this.auth.authenticate(this.handle, this.token); + } else { + this.auth = new Soup.AuthBasic({ + host: 'api.' + this.domain, + realm: 'Github Api', }); + this.authManager = new Soup.AuthManager(); + this.authManager.use_auth(this.authUri, this.auth); + Soup.Session.prototype.add_feature.call( + this.httpSession, + this.authManager + ); + } } + } + planFetch(delay, retry) { + if (retry) { + this.retryAttempts++; + } else { + this.retryAttempts = 0; + } + this.stopLoop(); + this.timeout = Mainloop.timeout_add_seconds(delay, () => { + this.fetchNotifications(); + return false; + }); + } + + fetchNotifications() { + let message = new Soup.Message({ method: 'GET', uri: this.authUri }); + if (this.lastModified) { + // github's API is currently broken: marking a notification as read won't modify the "last-modified" header + // so this is useless for now + //message.request_headers.append('If-Modified-Since', this.lastModified); + } - showBrowserUri() { + if (PACKAGE_VERSION >= 43) { + message.request_headers.append( + 'Authorization', + this.auth.get_authorization(message) + ); + this.httpSession.send_and_read_async( + message, + GLib.PRIORITY_DEFAULT, + null, + (_, result) => { + try { + let body = this.httpSession.send_and_read_finish(result); + body = body.get_data(); + body = ByteArray.toString(body); + if (message.get_status() == 200 || message.get_status() == 304) { + if (message.get_response_headers().get_one('Last-Modified')) { + this.lastModified = message + .get_response_headers() + .get_one('Last-Modified'); + } + if (message.get_response_headers().get_one('X-Poll-Interval')) { + this.githubInterval = message + .get_response_headers() + .get_one('X-Poll-Interval'); + } + this.planFetch(this.interval(), false); + if (message.get_status() == 200) { + let data = JSON.parse(body); + this.updateNotifications(data); + } + return; + } + if (message.get_status() == 401) { + error( + 'Unauthorized. Check your github handle and token in the settings' + ); + this.planFetch(this.interval(), true); + this.label.set_text('!'); + return; + } + if (!message.message_body.data && message.get_status() > 400) { + error('HTTP error:' + message.get_status()); + this.planFetch(this.interval(), true); + return; + } + // if we reach this point, none of the cases above have been triggered + // which likely means there was an error locally or on the network + // therefore we should try again in a while + error('HTTP error:' + message.get_status()); + error('message error: ' + JSON.stringify(message)); + this.planFetch(this.interval(), true); + this.label.set_text('!'); + return; + } catch (e) { + error('HTTP exception:' + e); + return; + } + } + ); + } else { + this.httpSession.queue_message(message, (_, response) => { try { - let url = 'https://' + this.domain + '/notifications'; - if (this.showParticipatingOnly) { - url = 'https://' + this.domain + '/notifications/participating'; + if (response.status_code == 200 || response.status_code == 304) { + if (response.response_headers.get('Last-Modified')) { + this.lastModified = + response.response_headers.get('Last-Modified'); } - - Gtk.show_uri(null, url, Gtk.get_current_event_time()); + if (response.response_headers.get('X-Poll-Interval')) { + this.githubInterval = + response.response_headers.get('X-Poll-Interval'); + } + this.planFetch(this.interval(), false); + if (response.status_code == 200) { + let data = JSON.parse(response.response_body.data); + this.updateNotifications(data); + } + return; + } + if (response.status_code == 401) { + error( + 'Unauthorized. Check your github handle and token in the settings' + ); + this.planFetch(this.interval(), true); + this.label.set_text('!'); + return; + } + if (!response.response_body.data && response.status_code > 400) { + error('HTTP error:' + response.status_code); + this.planFetch(this.interval(), true); + return; + } + // if we reach this point, none of the cases above have been triggered + // which likely means there was an error locally or on the network + // therefore we should try again in a while + error('HTTP error:' + response.status_code); + error('response error: ' + JSON.stringify(response)); + this.planFetch(this.interval(), true); + this.label.set_text('!'); + return; } catch (e) { - error("Cannot open uri " + e) + error('HTTP exception:' + e); + return; } + }); } + } - initHttp() { - let url = 'https://api.' + this.domain + '/notifications'; - if (this.showParticipatingOnly) { - url = 'https://api.' + this.domain + '/notifications?participating=1'; - } - this.authUri = new Soup.URI(url); - this.authUri.set_user(this.handle); - this.authUri.set_password(this.token); + updateNotifications(data) { + let lastNotificationsCount = this.notifications.length; - if (this.httpSession) { - this.httpSession.abort(); - } else { - this.httpSession = new Soup.Session(); - this.httpSession.user_agent = 'gnome-shell-extension github notification via libsoup'; + this.notifications = data; + this.label && this.label.set_text('' + data.length); + this.checkVisibility(); + this.alertWithNotifications(lastNotificationsCount); + } - this.authManager = new Soup.AuthManager(); - this.auth = new Soup.AuthBasic({ host: 'api.' + this.domain, realm: 'Github Api' }); + alertWithNotifications(lastCount) { + let newCount = this.notifications.length; - this.authManager.use_auth(this.authUri, this.auth); - Soup.Session.prototype.add_feature.call(this.httpSession, this.authManager); - } - } + if (newCount && newCount > lastCount && this.showAlertNotification) { + try { + let message = 'You have ' + newCount + ' new notifications'; - planFetch(delay, retry) { - if (retry) { - this.retryAttempts++; - } else { - this.retryAttempts = 0; - } - this.stopLoop(); - this.timeout = Mainloop.timeout_add_seconds(delay, () => { - this.fetchNotifications(); - return false; - }); + this.notify('Github Notifications', message); + } catch (e) { + error('Cannot notify ' + e); + } } + } - fetchNotifications() { - let message = new Soup.Message({ method: 'GET', uri: this.authUri }); - if (this.lastModified) { - // github's API is currently broken: marking a notification as read won't modify the "last-modified" header - // so this is useless for now - //message.request_headers.append('If-Modified-Since', this.lastModified); - } + notify(title, message) { + let notification; - this.httpSession.queue_message(message, (_, response) => { - try { - if (response.status_code == 200 || response.status_code == 304) { - if (response.response_headers.get('Last-Modified')) { - this.lastModified = response.response_headers.get('Last-Modified'); - } - if (response.response_headers.get('X-Poll-Interval')) { - this.githubInterval = response.response_headers.get('X-Poll-Interval'); - } - this.planFetch(this.interval(), false); - if (response.status_code == 200) { - let data = JSON.parse(response.response_body.data); - this.updateNotifications(data); - } - return; - } - if (response.status_code == 401) { - error('Unauthorized. Check your github handle and token in the settings'); - this.planFetch(this.interval(), true); - this.label.set_text('!'); - return; - } - if (!response.response_body.data && response.status_code > 400) { - error('HTTP error:' + response.status_code); - this.planFetch(this.interval(), true); - return; - } - // if we reach this point, none of the cases above have been triggered - // which likely means there was an error locally or on the network - // therefore we should try again in a while - error('HTTP error:' + response.status_code); - error('response error: ' + JSON.stringify(response)); - this.planFetch(this.interval(), true); - this.label.set_text('!'); - return; - } catch (e) { - error('HTTP exception:' + e); - return; - } - }); - } + this.addNotificationSource(); - updateNotifications(data) { - let lastNotificationsCount = this.notifications.length; + if (this._source && this._source.notifications.length == 0) { + notification = new MessageTray.Notification(this._source, title, message); - this.notifications = data; - this.label && this.label.set_text('' + data.length); - this.checkVisibility(); - this.alertWithNotifications(lastNotificationsCount); + notification.setTransient(true); + notification.setResident(false); + notification.connect('activated', this.showBrowserUri.bind(this)); // Open on click + } else { + notification = this._source.notifications[0]; + notification.update(title, message, { clear: true }); } - alertWithNotifications(lastCount) { - let newCount = this.notifications.length; - - if (newCount && newCount > lastCount && this.showAlertNotification) { - try { - let message = 'You have ' + newCount + ' new notifications'; - - this.notify('Github Notifications', message); - } catch (e) { - error("Cannot notify " + e) - } - } + if (PACKAGE_VERSION >= 43) { + this._source.showNotification(notification); + } else { + this._source.notify(notification); } + } - notify(title, message) { - let notification; - - this.addNotificationSource(); - - if (this._source && this._source.notifications.length == 0) { - notification = new MessageTray.Notification(this._source, title, message); - - notification.setTransient(true); - notification.setResident(false); - notification.connect('activated', this.showBrowserUri.bind(this)); // Open on click - } else { - notification = this._source.notifications[0]; - notification.update(title, message, { clear: true }); - } - - this._source.notify(notification); + addNotificationSource() { + if (this._source) { + return; } - addNotificationSource() { - if (this._source) { - return; - } - - this._source = new MessageTray.SystemNotificationSource(); - this._source.connect('destroy', () => { - this._source = null; - }); - Main.messageTray.add(this._source); - } + this._source = new MessageTray.SystemNotificationSource(); + this._source.connect('destroy', () => { + this._source = null; + }); + Main.messageTray.add(this._source); + } } -function init() { -} +function init() {} function enable() { - githubNotifications = new GithubNotifications(); - githubNotifications.start(); + githubNotifications = new GithubNotifications(); + githubNotifications.start(); } function disable() { - githubNotifications.stop(); - githubNotifications = null; + githubNotifications.stop(); + githubNotifications = null; } diff --git a/metadata.json b/metadata.json index 0ec1737..fb06c24 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { - "description": "Integrate Github's notifications within the gnome desktop environment\nSource code is available here: https://github.com/alexduf/gnome-github-notifications", - "uuid": "github.notifications@alexandre.dufournet.gmail.com", - "name": "Github Notifications", - "shell-version": ["40", "41", "42"] + "description": "Integrate Github's notifications within the gnome desktop environment\nSource code is available here: https://github.com/alexduf/gnome-github-notifications", + "uuid": "github.notifications@alexandre.dufournet.gmail.com", + "name": "Github Notifications", + "shell-version": ["40", "41", "42", "43", "44"] } From 4dd11721dd54ba007eb4b5c33c6c0bbf04a6be19 Mon Sep 17 00:00:00 2001 From: Lyr Date: Sat, 15 Jul 2023 01:56:08 +0200 Subject: [PATCH 2/4] fixed timing event --- extension.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extension.js b/extension.js index 7af4622..424cbd6 100644 --- a/extension.js +++ b/extension.js @@ -153,7 +153,11 @@ class GithubNotifications { url = 'https://' + this.domain + '/notifications/participating'; } - Gtk.show_uri(null, url, Gtk.get_current_event_time()); + if (PACKAGE_VERSION >= 43) { + Gtk.show_uri(null, url, Gtk.EventController.get_current_event_time()); + } else { + Gtk.show_uri(null, url, Gtk.get_current_event_time()); + } } catch (e) { error('Cannot open uri ' + e); } From 90b7ebe7654746d091a101b907d5866933d574a9 Mon Sep 17 00:00:00 2001 From: Lyr Date: Wed, 11 Dec 2024 21:37:53 +0100 Subject: [PATCH 3/4] Updated readme and metadata --- README.md | 20 ++++++++++++-------- metadata.json | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c3be5c1..1a85f48 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,25 @@ -# gnome-github-notifications +# gnome-github-notifications continued + Integrate github's notifications within the gnome desktop environment ## Installation ### The automatic way -Go there and activate the extension: https://extensions.gnome.org/extension/1125/github-notifications/ -Don't forget to click on the configuration icon and follow the instructions there. + +*extensions.gnome.org is pending* ### The manual way -``` +```sh mkdir -p ~/.local/share/gnome-shell/extensions/ -cd ~/.local/share/gnome-shell/extensions/ -git clone git@github.com:alexduf/gnome-github-notifications.git github.notifications@alexandre.dufournet.gmail.com +git clone git@github.com:Lyr-7D1h/gnome-github-notifications.git ~/.local/share/gnome-shell/github.notifications@lyr.7d1h.pm.me ``` -Then in gnome-tweaks, configure the extension to give it a token and your github handle (instructions are provided in the configuration dialog). -If the extension isn't detected, restart gnome shell `Alt` + `F2`, type `r` then press `enter`. +After adding the extension, restart GNOME Shell for changes to take effect: +- Press Alt + F2, type r, and press Enter (on Xorg sessions). +- Log out and back in (on Wayland sessions). +```sh +gnome-extensions enable github.notifications@lyr.7d1h.pm.me +``` diff --git a/metadata.json b/metadata.json index fb06c24..64c626a 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { "description": "Integrate Github's notifications within the gnome desktop environment\nSource code is available here: https://github.com/alexduf/gnome-github-notifications", - "uuid": "github.notifications@alexandre.dufournet.gmail.com", + "uuid": "github.notifications@lyr.7d1h.pm.me", "name": "Github Notifications", - "shell-version": ["40", "41", "42", "43", "44"] + "shell-version": ["40", "41", "42", "43", "44", "47"] } From 09bf5af579ba13aefbb0ca0b4cae1a0bbd319720 Mon Sep 17 00:00:00 2001 From: Lyr Date: Thu, 12 Dec 2024 00:47:43 +0100 Subject: [PATCH 4/4] progress port to gnome 45 --- README.md | 6 ++ extension.js | 111 ++++++++++++++++++++++++----------- prefs.js | 162 ++++++++++++++++++++++++++++----------------------- 3 files changed, 172 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 1a85f48..d7ae04a 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,9 @@ After adding the extension, restart GNOME Shell for changes to take effect: ```sh gnome-extensions enable github.notifications@lyr.7d1h.pm.me ``` + +## Development + +1. Install (see [Installation](#installation)) +2. Make changes to the extension +3. Run `dbus-run-session -- gnome-shell --nested --wayland` diff --git a/extension.js b/extension.js index 424cbd6..e9b9fc4 100644 --- a/extension.js +++ b/extension.js @@ -1,16 +1,17 @@ -const { Soup, St, Gio, Gtk, Clutter, GLib } = imports.gi; -const Main = imports.ui.main; -const Mainloop = imports.mainloop; -const ByteArray = imports.byteArray; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const Util = imports.misc.util; -const MessageTray = imports.ui.messageTray; -let { PACKAGE_VERSION } = imports.misc.config; +import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import Soup from 'gi://Soup'; +import St from 'gi://St'; +import Gio from 'gi://Gio'; +import Gtk from 'gi://Gtk'; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import * as ExtensionUtils from 'resource:///org/gnome/shell/misc/extensionUtils.js'; +import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'; +import * as Config from 'resource:///org/gnome/shell/misc/config.js'; +let { PACKAGE_VERSION } = Config; PACKAGE_VERSION = Number(PACKAGE_VERSION); -let githubNotifications; - function info(...messages) { for (const m of messages) { console.log('[GITHUB NOTIFICATIONS EXTENSION][INFO] ' + m); @@ -23,8 +24,29 @@ function error(...messages) { } } -class GithubNotifications { - constructor() { +const Indicator = GObject.registerClass( + class Indicator extends PanelMenu.Button { + _init() { + super._init(0.0, _('Github Notifications')); + + this.add_child( + new St.Icon({ + icon_name: 'selection-mode-symbolic', + style_class: 'system-status-icon', + }) + ); + + let item = new PopupMenu.PopupMenuItem(_('Show Notification')); + item.connect('activate', () => { + Main.notify(_('Whatʼs up, folks?')); + }); + this.menu.addMenuItem(item); + } + } +); + +export default class GithubNotifications extends Extension { + _init() { this.token = ''; this.handle = ''; this.hideWidget = false; @@ -44,6 +66,24 @@ class GithubNotifications { this.settings = null; } + _sendHttpRequest(url, callback) { + const request = Soup.Message.new('GET', url); + + this._session.send_async(request, null, (session, result) => { + try { + session.send_finish(result); + if (request.get_status() === Soup.Status.OK) { + const response = request.get_response_body().data; + callback(true, response); + } else { + callback(false, `HTTP Error: ${request.get_status()}`); + } + } catch (error) { + callback(false, `Error: ${error.message}`); + } + }); + } + interval() { let i = this.refreshInterval; if (this.retryAttempts > 0) { @@ -65,10 +105,16 @@ class GithubNotifications { this.initUI(); } - start() { - this.settings = ExtensionUtils.getSettings( + enable() { + this._indicator = new Indicator(); + Main.panel.addToStatusArea(this.uuid, this._indicator); + + this.settings = this.getSettings( 'org.gnome.shell.extensions.github.notifications' ); + // this.settings = new Gio.Settings({ + // schema_id: 'org.gnome.shell.extensions.github.notifications', + // }); if (!this.hasLazilyInit) { this.lazyInit(); } @@ -76,7 +122,7 @@ class GithubNotifications { Main.panel._rightBox.insert_child_at_index(this.box, 0); } - stop() { + disable() { this.stopLoop(); Main.panel._rightBox.remove_child(this.box); } @@ -119,7 +165,7 @@ class GithubNotifications { track_hover: true, }); this.label = new St.Label({ - text: '' + this.notifications.length, + text: '' + this.notifications.length || '-', style_class: 'system-status-icon notifications-length', y_align: Clutter.ActorAlign.CENTER, y_expand: true, @@ -130,7 +176,7 @@ class GithubNotifications { let icon = new St.Icon({ style_class: 'system-status-icon', }); - icon.gicon = Gio.icon_new_for_string(`${Me.path}/github.svg`); + icon.gicon = Gio.icon_new_for_string(`${this.uuid}/github.svg`); this.box.add_actor(icon); this.box.add_actor(this.label); @@ -224,6 +270,17 @@ class GithubNotifications { return false; }); } + _readAllBytes(stream) { + const bytes = []; + const buffer = new Uint8Array(4096); // 4 KB buffer + let readBytes = 0; + + while ((readBytes = stream.read(buffer, null)) > 0) { + bytes.push(...buffer.subarray(0, readBytes)); + } + + return bytes; + } fetchNotifications() { let message = new Soup.Message({ method: 'GET', uri: this.authUri }); @@ -245,8 +302,8 @@ class GithubNotifications { (_, result) => { try { let body = this.httpSession.send_and_read_finish(result); - body = body.get_data(); - body = ByteArray.toString(body); + const textDecoder = new TextDecoder('utf-8'); + const text = textDecoder.decode(body); if (message.get_status() == 200 || message.get_status() == 304) { if (message.get_response_headers().get_one('Last-Modified')) { this.lastModified = message @@ -260,7 +317,7 @@ class GithubNotifications { } this.planFetch(this.interval(), false); if (message.get_status() == 200) { - let data = JSON.parse(body); + const data = JSON.parse(text); this.updateNotifications(data); } return; @@ -398,15 +455,3 @@ class GithubNotifications { Main.messageTray.add(this._source); } } - -function init() {} - -function enable() { - githubNotifications = new GithubNotifications(); - githubNotifications.start(); -} - -function disable() { - githubNotifications.stop(); - githubNotifications = null; -} diff --git a/prefs.js b/prefs.js index 48153c0..51b9e1c 100644 --- a/prefs.js +++ b/prefs.js @@ -1,9 +1,10 @@ -const { Gtk, Gio } = imports.gi; +import * as ExtensionUtils from 'resource:///org/gnome/Shell/misc/extensionUtils.js'; +import { Gio, Gtk } from 'gi://GLIB'; -const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const GITHUB_SETTINGS_SCHEMA = 'org.gnome.shell.extensions.github.notifications'; +const GITHUB_SETTINGS_SCHEMA = + 'org.gnome.shell.extensions.github.notifications'; const _settings = ExtensionUtils.getSettings(GITHUB_SETTINGS_SCHEMA); @@ -19,96 +20,109 @@ It should not include "http[s]://" or path params. See https://developer.github.com/v3/activity/notifications`; function makeLabeledOptionBox(labelText) { - const box = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - spacing: 10, - }); - const label = new Gtk.Label({ - label: labelText - }); - - box.append(label); - return box; + const box = new Gtk.Box({ + orientation: Gtk.Orientation.HORIZONTAL, + spacing: 10, + }); + const label = new Gtk.Label({ + label: labelText, + }); + + box.append(label); + return box; } function bindSettingToGtkWidget(boundSettingName, widget, property) { - _settings.bind(boundSettingName, widget, property, Gio.SettingsBindFlags.DEFAULT); + _settings.bind( + boundSettingName, + widget, + property, + Gio.SettingsBindFlags.DEFAULT + ); } function makeLabeledSwitchOptionBox(label, boundSettingName) { - const box = makeLabeledOptionBox(label); + const box = makeLabeledOptionBox(label); - const switch_ = new Gtk.Switch(); - bindSettingToGtkWidget(boundSettingName, switch_, 'state'); + const switch_ = new Gtk.Switch(); + bindSettingToGtkWidget(boundSettingName, switch_, 'state'); - box.append(switch_); - return box; + box.append(switch_); + return box; } function makeLabeledEntryOptionBox(label, boundSettingName) { - const box = makeLabeledOptionBox(label); + const box = makeLabeledOptionBox(label); - const entry = new Gtk.Entry(); - bindSettingToGtkWidget(boundSettingName, entry, 'text'); + const entry = new Gtk.Entry(); + bindSettingToGtkWidget(boundSettingName, entry, 'text'); - box.append(entry); - return box; + box.append(entry); + return box; } -function makeLabeledSpinButtonOptionBox(label, boundSettingName, min, max, step) { - const box = makeLabeledOptionBox(label); +function makeLabeledSpinButtonOptionBox( + label, + boundSettingName, + min, + max, + step +) { + const box = makeLabeledOptionBox(label); - const spinButton = Gtk.SpinButton.new_with_range(min, max, step); - bindSettingToGtkWidget(boundSettingName, spinButton, 'value'); + const spinButton = Gtk.SpinButton.new_with_range(min, max, step); + bindSettingToGtkWidget(boundSettingName, spinButton, 'value'); - box.append(spinButton); - return box; + box.append(spinButton); + return box; } function buildPrefsWidget() { - const mainBox = new Gtk.Box({ - orientation: Gtk.Orientation.VERTICAL, - 'margin-top': 20, - 'margin-bottom': 20, - 'margin-start': 20, - 'margin-end': 20, - spacing: 10, - }); - - const innerWidgets = [ - makeLabeledEntryOptionBox('Github Hostname', 'domain'), - makeLabeledEntryOptionBox('Github Token', 'token'), - makeLabeledEntryOptionBox('Github Handle', 'handle'), - makeLabeledSwitchOptionBox('Show notifications alert', 'show-alert'), - makeLabeledSpinButtonOptionBox( - 'Refresh interval (in seconds)*', - 'refresh-interval', - 60, - 86400, - 1, - ), - makeLabeledSwitchOptionBox( - 'Only count notifications if you\'re participating (mention, review asked...)', - 'show-participating-only', - ), - makeLabeledSwitchOptionBox('Hide notification count', 'hide-notification-count'), - makeLabeledSwitchOptionBox( - 'Hide widget when there are no notifications', - 'hide-widget' - ), - new Gtk.Label({ - label: TOKEN_EXPLAINER, - selectable: true, - 'use-markup': true - }), - ]; - - for (const w of innerWidgets) { - mainBox.append(w); - } - - return mainBox; + const mainBox = new Gtk.Box({ + orientation: Gtk.Orientation.VERTICAL, + 'margin-top': 20, + 'margin-bottom': 20, + 'margin-start': 20, + 'margin-end': 20, + spacing: 10, + }); + + const innerWidgets = [ + makeLabeledEntryOptionBox('Github Hostname', 'domain'), + makeLabeledEntryOptionBox('Github Token', 'token'), + makeLabeledEntryOptionBox('Github Handle', 'handle'), + makeLabeledSwitchOptionBox('Show notifications alert', 'show-alert'), + makeLabeledSpinButtonOptionBox( + 'Refresh interval (in seconds)*', + 'refresh-interval', + 60, + 86400, + 1 + ), + makeLabeledSwitchOptionBox( + "Only count notifications if you're participating (mention, review asked...)", + 'show-participating-only' + ), + makeLabeledSwitchOptionBox( + 'Hide notification count', + 'hide-notification-count' + ), + makeLabeledSwitchOptionBox( + 'Hide widget when there are no notifications', + 'hide-widget' + ), + new Gtk.Label({ + label: TOKEN_EXPLAINER, + selectable: true, + 'use-markup': true, + }), + ]; + + for (const w of innerWidgets) { + mainBox.append(w); + } + + return mainBox; } -function init() { -} +function init() {}