commit bb312e83cde2d53432fe0709b638d2b4d5b0acba Author: Christine Dodrill Date: Mon Sep 13 08:52:24 2021 -0400 initial commit Signed-off-by: Christine Dodrill diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4cb9bcc --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ + YOLO LICENSE + Version 1, July 10 2015 + +THIS SOFTWARE LICENSE IS PROVIDED "ALL CAPS" SO THAT YOU KNOW IT IS SUPER +SERIOUS AND YOU DON'T MESS AROUND WITH COPYRIGHT LAW BECAUSE YOU WILL GET IN +TROUBLE HERE ARE SOME OTHER BUZZWORDS COMMONLY IN THESE THINGS WARRANTIES +LIABILITY CONTRACT TORT LIABLE CLAIMS RESTRICTION MERCHANTABILITY SUBJECT TO +THE FOLLOWING CONDITIONS: + +1. #yolo +2. #swag +3. #blazeit diff --git a/convenience.js b/convenience.js new file mode 100644 index 0000000..a9dfc38 --- /dev/null +++ b/convenience.js @@ -0,0 +1,91 @@ +/* + Copyright (c) 2011-2012, Giovanni Campagna + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the GNOME nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +const Gettext = imports.gettext; +const Gio = imports.gi.Gio; + +const Config = imports.misc.config; +const ExtensionUtils = imports.misc.extensionUtils; + +/** + * initTranslations: + * @domain: (optional): the gettext domain to use + * + * Initialize Gettext to load translations from extensionsdir/locale. + * If @domain is not provided, it will be taken from metadata['gettext-domain'] + */ +function initTranslations(domain) { + let extension = ExtensionUtils.getCurrentExtension(); + + domain = domain || extension.metadata['gettext-domain']; + + // check if this extension was built with "make zip-file", and thus + // has the locale files in a subfolder + // otherwise assume that extension has been installed in the + // same prefix as gnome-shell + let localeDir = extension.dir.get_child('locale'); + if (localeDir.query_exists(null)) + Gettext.bindtextdomain(domain, localeDir.get_path()); + else + Gettext.bindtextdomain(domain, Config.LOCALEDIR); +} + +/** + * getSettings: + * @schema: (optional): the GSettings schema id + * + * Builds and return a GSettings schema for @schema, using schema files + * in extensionsdir/schemas. If @schema is not provided, it is taken from + * metadata['settings-schema']. + */ +function getSettings(schema) { + let extension = ExtensionUtils.getCurrentExtension(); + + schema = schema || extension.metadata['settings-schema']; + + const GioSSS = Gio.SettingsSchemaSource; + + // check if this extension was built with "make zip-file", and thus + // has the schema files in a subfolder + // otherwise assume that extension has been installed in the + // same prefix as gnome-shell (and therefore schemas are available + // in the standard folders) + let schemaDir = extension.dir.get_child('schemas'); + let schemaSource; + if (schemaDir.query_exists(null)) + schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), + GioSSS.get_default(), + false); + else + schemaSource = GioSSS.get_default(); + + let schemaObj = schemaSource.lookup(schema, true); + if (!schemaObj) + throw new Error('Schema ' + schema + ' could not be found for extension ' + + extension.metadata.uuid + '. Please check your installation.'); + + return new Gio.Settings({settings_schema: schemaObj}); +} diff --git a/extension.js b/extension.js new file mode 100644 index 0000000..6a2e6f6 --- /dev/null +++ b/extension.js @@ -0,0 +1,49 @@ +const {St, Clutter, Soup} = imports.gi; +const Main = imports.ui.main; + +const Self = imports.misc.extensionUtils.getCurrentExtension(); +const Timer = Self.imports.timer; + +let panelButton; + +function getFront() { + let soupSyncSession = new Soup.SessionSync(); + + let message = Soup.Message.new('GET', "https://home.cetacean.club/front"); + let responseCode = soupSyncSession.send_message(message); + let res; + if(responseCode == 200) { + return message['response-body'].data; + } +} + +function updateButton() { + let panelButtonText = new St.Label({ + text : getFront(), + y_align: Clutter.ActorAlign.CENTER, + }); + panelButton.set_child(panelButtonText); +} + +function init() { + panelButton = new St.Bin({ + style_class : "panel-button", + }); +} + +function enable() { + updateButton(); + Main.panel._rightBox.insert_child_at_index(panelButton, 0); + + let timer = new Timer.AFTimer(); + timer.registerCallback(updateButton); + timer.setMinutes(5); + timer.start(); +} + +function disable() { + Main.panel._rightBox.remove_child(panelButton); + + let timer = new Timer.AFTimer(); + timer.cleanup(); +} diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..f2ec932 --- /dev/null +++ b/metadata.json @@ -0,0 +1,10 @@ +{ + "name" : "Who Is Front?", + "description" : "Shows who is front in Within", + "shell-version" : [ + "40" + ], + "url" : "", + "uuid" : "whoisfront@within.website", + "version" : 0.1 +} diff --git a/settings.js b/settings.js new file mode 100644 index 0000000..71c08d4 --- /dev/null +++ b/settings.js @@ -0,0 +1,38 @@ +const Gio = imports.gi.Gio; + +const Self = imports.misc.extensionUtils.getCurrentExtension(); +const Convenience = Self.imports.convenience; + +var Settings = class { + + /** + * Settings object. + * + * @param [schema] + * @private + */ + constructor(schema) { + this._settings = Convenience.getSettings(schema); + } + + observe(key, callback) { + return this._settings.connect('changed::' + key, callback); + } + + disconnect(handler) { + return this._settings.disconnect(handler); + } + + set(key, type, value) { + if (this._settings['set_' + type](key, value)) { + Gio.Settings.sync(); // wait for write + } else { + throw "Could not set " + key + " (type: " + type + ") with the value " + value; + } + } + + get(key, type) { + return this._settings['get_' + type](key); + } + +}; diff --git a/timer.js b/timer.js new file mode 100644 index 0000000..67b1815 --- /dev/null +++ b/timer.js @@ -0,0 +1,145 @@ + +const Lang = imports.lang; +const GLib = imports.gi.GLib; + +const Self = imports.misc.extensionUtils.getCurrentExtension(); +const Prefs = Self.imports.settings; + +let _afTimerInstance = null; + +// Singleton implementation of _AFTimer +var AFTimer = function () { + if (!_afTimerInstance) { + _afTimerInstance = new _AFTimer(); + } + return _afTimerInstance; +}; + +/** + * Timer for the auto fetch feature. + * + * @type {Lang} + */ +var _AFTimer = class { + + constructor() { + + this._timeout = null; + this._timoutEndCallback = null; + this._minutes = 30; + + this._settings = new Prefs.Settings(); + } + + isActive() { + return this._settings.get('auto-fetch', 'boolean'); + } + + remainingMinutes() { + let minutesElapsed = this._minutesElapsed(); + let remainder = minutesElapsed % this._minutes; + return Math.max(this._minutes - remainder, 0); + } + + registerCallback(callback) { + this._timoutEndCallback = callback; + } + + /** + * Sets the minutes of the timer. + * + * @param minutes + */ + setMinutes(minutes) { + this._minutes = minutes; + } + + /** + * Start the timer. + * + * @return void + */ + start() { + this.cleanup(); + + let last = this._settings.get('timer-last-trigger', 'int64'); + if (last === 0) { + this.reset(); + } + + let millisRemaining = this.remainingMinutes() * 60 * 1000; + + // set new wallpaper if the interval was surpassed and set the timestamp to when it should have been updated + if (this._surpassedInterval()) { + if (this._timoutEndCallback) { + this._timoutEndCallback(); + } + let millisOverdue = (this._minutes * 60 * 1000) - millisRemaining; + this._settings.set('timer-last-trigger', 'int64', Date.now() - millisOverdue); + } + + // actual timer function + this._timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, millisRemaining, () => { + if (this._timoutEndCallback) { + this._timoutEndCallback(); + } + + this.reset(); // reset timer + this.start(); // restart timer + }); + } + + /** + * Stop the timer. + * + * @return void + */ + stop() { + this._settings.set('timer-last-trigger', 'int64', 0); + this.cleanup(); + } + + /** + * Cleanup the timeout callback if it exists. + * + * @return void + */ + cleanup() { + if (this._timeout) { // only remove if a timeout is active + GLib.source_remove(this._timeout); + this._timeout = null; + } + } + + /** + * Reset the timer. + * + * @return void + */ + reset() { + this._settings.set('timer-last-trigger', 'int64', new Date().getTime()); + this.cleanup(); + } + + _minutesElapsed() { + let now = Date.now(); + let last = this._settings.get('timer-last-trigger', 'int64'); + + if (last === 0) { + return 0; + } + + let elapsed = Math.max(now - last, 0); + return Math.floor(elapsed / (60 * 1000)); + } + + _surpassedInterval() { + let now = Date.now(); + let last = this._settings.get('timer-last-trigger', 'int64'); + let diff = now - last; + let intervalLength = this._minutes * 60 * 1000; + + return diff > intervalLength; + } + +};