diff options
Diffstat (limited to '0001-heads-up-display-Add-extension-for-showing-persisten.patch')
-rw-r--r-- | 0001-heads-up-display-Add-extension-for-showing-persisten.patch | 986 |
1 files changed, 986 insertions, 0 deletions
diff --git a/0001-heads-up-display-Add-extension-for-showing-persisten.patch b/0001-heads-up-display-Add-extension-for-showing-persisten.patch new file mode 100644 index 0000000..5f08a79 --- /dev/null +++ b/0001-heads-up-display-Add-extension-for-showing-persisten.patch @@ -0,0 +1,986 @@ +From 8beb3b27486fd50f74c15d2cf9ed8ca22fb546c2 Mon Sep 17 00:00:00 2001 +From: Ray Strode <rstrode@redhat.com> +Date: Tue, 24 Aug 2021 15:03:57 -0400 +Subject: [PATCH] heads-up-display: Add extension for showing persistent heads + up display message + +--- + extensions/heads-up-display/extension.js | 436 ++++++++++++++++++ + extensions/heads-up-display/headsUpMessage.js | 170 +++++++ + extensions/heads-up-display/meson.build | 8 + + extensions/heads-up-display/metadata.json.in | 11 + + ...ll.extensions.heads-up-display.gschema.xml | 54 +++ + extensions/heads-up-display/prefs.js | 194 ++++++++ + extensions/heads-up-display/stylesheet.css | 32 ++ + meson.build | 1 + + 8 files changed, 906 insertions(+) + create mode 100644 extensions/heads-up-display/extension.js + create mode 100644 extensions/heads-up-display/headsUpMessage.js + create mode 100644 extensions/heads-up-display/meson.build + create mode 100644 extensions/heads-up-display/metadata.json.in + create mode 100644 extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml + create mode 100644 extensions/heads-up-display/prefs.js + create mode 100644 extensions/heads-up-display/stylesheet.css + +diff --git a/extensions/heads-up-display/extension.js b/extensions/heads-up-display/extension.js +new file mode 100644 +index 00000000..7cebfa99 +--- /dev/null ++++ b/extensions/heads-up-display/extension.js +@@ -0,0 +1,436 @@ ++/* exported init enable disable */ ++ ++const Signals = imports.signals; ++ ++const { ++ Atk, Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St, ++} = imports.gi; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++ ++const Main = imports.ui.main; ++const Layout = imports.ui.layout; ++const HeadsUpMessage = Me.imports.headsUpMessage; ++ ++const _ = ExtensionUtils.gettext; ++ ++var HeadsUpConstraint = GObject.registerClass({ ++ Properties: { ++ 'offset': GObject.ParamSpec.int('offset', ++ 'Offset', 'offset', ++ GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE, ++ -1, 0, -1), ++ 'active': GObject.ParamSpec.boolean('active', ++ 'Active', 'active', ++ GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE, ++ true), ++ }, ++}, class HeadsUpConstraint extends Layout.MonitorConstraint { ++ _init(props) { ++ super._init(props); ++ this._offset = 0; ++ this._active = true; ++ } ++ ++ get offset() { ++ return this._offset; ++ } ++ ++ set offset(o) { ++ this._offset = o ++ } ++ ++ get active() { ++ return this._active; ++ } ++ ++ set active(a) { ++ this._active = a; ++ } ++ ++ vfunc_update_allocation(actor, actorBox) { ++ if (!Main.layoutManager.primaryMonitor) ++ return; ++ ++ if (!this.active) ++ return; ++ ++ if (actor.has_allocation()) ++ return; ++ ++ const workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); ++ actorBox.init_rect(workArea.x, workArea.y + this.offset, workArea.width, workArea.height - this.offset); ++ } ++}); ++ ++class Extension { ++ constructor() { ++ ExtensionUtils.initTranslations(); ++ } ++ ++ enable() { ++ this._settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.heads-up-display'); ++ this._settingsChangedId = this._settings.connect('changed', this._updateMessage.bind(this)); ++ ++ this._idleMonitor = Meta.IdleMonitor.get_core(); ++ this._messageInhibitedUntilIdle = false; ++ this._oldMapWindow = Main.wm._mapWindow; ++ Main.wm._mapWindow = this._mapWindow; ++ this._windowManagerMapId = global.window_manager.connect('map', this._onWindowMap.bind(this)); ++ ++ if (Main.layoutManager._startingUp) ++ this._startupCompleteId = Main.layoutManager.connect('startup-complete', this._onStartupComplete.bind(this)); ++ else ++ this._onStartupComplete(this); ++ } ++ ++ disable() { ++ this._dismissMessage(); ++ ++ this._stopWatchingForIdle(); ++ ++ if (this._sessionModeUpdatedId) { ++ Main.sessionMode.disconnect(this._sessionModeUpdatedId); ++ this._sessionModeUpdatedId = 0; ++ } ++ ++ if (this._overviewShowingId) { ++ Main.overview.disconnect(this._overviewShowingId); ++ this._overviewShowingId = 0; ++ } ++ ++ if (this._overviewHiddenId) { ++ Main.overview.disconnect(this._overviewHiddenId); ++ this._overviewHiddenId = 0; ++ } ++ ++ if (this._screenShieldVisibleId) { ++ Main.screenShield._dialog._clock.disconnect(this._screenShieldVisibleId); ++ this._screenShieldVisibleId = 0; ++ } ++ ++ if (this._panelConnectionId) { ++ Main.layoutManager.panelBox.disconnect(this._panelConnectionId); ++ this._panelConnectionId = 0; ++ } ++ ++ if (this._oldMapWindow) { ++ Main.wm._mapWindow = this._oldMapWindow; ++ this._oldMapWindow = null; ++ } ++ ++ if (this._windowManagerMapId) { ++ global.window_manager.disconnect(this._windowManagerMapId); ++ this._windowManagerMapId = 0; ++ } ++ ++ if (this._startupCompleteId) { ++ Main.layoutManager.disconnect(this._startupCompleteId); ++ this._startupCompleteId = 0; ++ } ++ ++ if (this._settingsChangedId) { ++ this._settings.disconnect(this._settingsChangedId); ++ this._settingsChangedId = 0; ++ } ++ } ++ ++ _onWindowMap(shellwm, actor) { ++ const windowObject = actor.meta_window; ++ const windowType = windowObject.get_window_type(); ++ ++ if (windowType != Meta.WindowType.NORMAL) ++ return; ++ ++ if (!this._message || !this._message.visible) ++ return; ++ ++ const messageRect = new Meta.Rectangle({ x: this._message.x, y: this._message.y, width: this._message.width, height: this._message.height }); ++ const windowRect = windowObject.get_frame_rect(); ++ ++ if (windowRect.intersect(messageRect)) { ++ windowObject.move_frame(false, windowRect.x, this._message.y + this._message.height); ++ } ++ } ++ ++ _onStartupComplete() { ++ this._overviewShowingId = Main.overview.connect('showing', this._updateMessage.bind(this)); ++ this._overviewHiddenId = Main.overview.connect('hidden', this._updateMessage.bind(this)); ++ this._panelConnectionId = Main.layoutManager.panelBox.connect('notify::visible', this._updateMessage.bind(this)); ++ this._sessionModeUpdatedId = Main.sessionMode.connect('updated', this._onSessionModeUpdated.bind(this)); ++ ++ this._updateMessage(); ++ } ++ ++ _onSessionModeUpdated() { ++ if (!Main.sessionMode.hasWindows) ++ this._messageInhibitedUntilIdle = false; ++ ++ const dialog = Main.screenShield._dialog; ++ if (!Main.sessionMode.isGreeter && dialog && !this._screenShieldVisibleId) { ++ this._screenShieldVisibleId = dialog._clock.connect('notify::visible', ++ this._updateMessage.bind(this)); ++ this._screenShieldDestroyId = dialog._clock.connect('destroy', () => { ++ this._screenShieldVisibleId = 0; ++ this._screenShieldDestroyId = 0; ++ }); ++ } ++ this._updateMessage(); ++ } ++ ++ _stopWatchingForIdle() { ++ if (this._idleWatchId) { ++ this._idleMonitor.remove_watch(this._idleWatchId); ++ this._idleWatchId = 0; ++ } ++ ++ if (this._idleTimeoutChangedId) { ++ this._settings.disconnect(this._idleTimeoutChangedId); ++ this._idleTimeoutChangedId = 0; ++ } ++ } ++ ++ _onIdleTimeoutChanged() { ++ this._stopWatchingForIdle(); ++ this._messageInhibitedUntilIdle = false; ++ } ++ ++ _onUserIdle() { ++ this._messageInhibitedUntilIdle = false; ++ this._updateMessage(); ++ } ++ ++ _watchForIdle() { ++ this._stopWatchingForIdle(); ++ ++ const idleTimeout = this._settings.get_uint('idle-timeout'); ++ ++ this._idleTimeoutChangedId = this._settings.connect('changed::idle-timeout', this._onIdleTimeoutChanged.bind(this)); ++ this._idleWatchId = this._idleMonitor.add_idle_watch(idleTimeout * 1000, this._onUserIdle.bind(this)); ++ } ++ ++ _updateMessage() { ++ if (this._messageInhibitedUntilIdle) { ++ if (this._message) ++ this._dismissMessage(); ++ return; ++ } ++ ++ this._stopWatchingForIdle(); ++ ++ if (Main.sessionMode.hasOverview && Main.overview.visible) { ++ this._dismissMessage(); ++ return; ++ } ++ ++ if (!Main.layoutManager.panelBox.visible) { ++ this._dismissMessage(); ++ return; ++ } ++ ++ let supportedModes = []; ++ ++ if (this._settings.get_boolean('show-when-unlocked')) ++ supportedModes.push('user'); ++ ++ if (this._settings.get_boolean('show-when-unlocking') || ++ this._settings.get_boolean('show-when-locked')) ++ supportedModes.push('unlock-dialog'); ++ ++ if (this._settings.get_boolean('show-on-login-screen')) ++ supportedModes.push('gdm'); ++ ++ if (!supportedModes.includes(Main.sessionMode.currentMode) && ++ !supportedModes.includes(Main.sessionMode.parentMode)) { ++ this._dismissMessage(); ++ return; ++ } ++ ++ if (Main.sessionMode.currentMode === 'unlock-dialog') { ++ const dialog = Main.screenShield._dialog; ++ if (!this._settings.get_boolean('show-when-locked')) { ++ if (dialog._clock.visible) { ++ this._dismissMessage(); ++ return; ++ } ++ } ++ ++ if (!this._settings.get_boolean('show-when-unlocking')) { ++ if (!dialog._clock.visible) { ++ this._dismissMessage(); ++ return; ++ } ++ } ++ } ++ ++ const heading = this._settings.get_string('message-heading'); ++ const body = this._settings.get_string('message-body'); ++ ++ if (!heading && !body) { ++ this._dismissMessage(); ++ return; ++ } ++ ++ if (!this._message) { ++ this._message = new HeadsUpMessage.HeadsUpMessage(heading, body); ++ ++ this._message.connect('notify::allocation', this._adaptSessionForMessage.bind(this)); ++ this._message.connect('clicked', this._onMessageClicked.bind(this)); ++ } ++ ++ this._message.reactive = true; ++ this._message.track_hover = true; ++ ++ this._message.setHeading(heading); ++ this._message.setBody(body); ++ ++ if (!Main.sessionMode.hasWindows) { ++ this._message.track_hover = false; ++ this._message.reactive = false; ++ } ++ } ++ ++ _onMessageClicked() { ++ if (!Main.sessionMode.hasWindows) ++ return; ++ ++ this._watchForIdle(); ++ this._messageInhibitedUntilIdle = true; ++ this._updateMessage(); ++ } ++ ++ _dismissMessage() { ++ if (!this._message) { ++ return; ++ } ++ ++ this._message.visible = false; ++ this._message.destroy(); ++ this._message = null; ++ this._resetMessageTray(); ++ this._resetLoginDialog(); ++ } ++ ++ _resetMessageTray() { ++ if (!Main.messageTray) ++ return; ++ ++ if (this._updateMessageTrayId) { ++ global.stage.disconnect(this._updateMessageTrayId); ++ this._updateMessageTrayId = 0; ++ } ++ ++ if (this._messageTrayConstraint) { ++ Main.messageTray.remove_constraint(this._messageTrayConstraint); ++ this._messageTrayConstraint = null; ++ } ++ } ++ ++ _alignMessageTray() { ++ if (!Main.messageTray) ++ return; ++ ++ if (!this._message || !this._message.visible) { ++ this._resetMessageTray() ++ return; ++ } ++ ++ if (this._updateMessageTrayId) ++ return; ++ ++ this._updateMessageTrayId = global.stage.connect('before-update', () => { ++ if (!this._messageTrayConstraint) { ++ this._messageTrayConstraint = new HeadsUpConstraint({ primary: true }); ++ ++ Main.layoutManager.panelBox.bind_property('visible', ++ this._messageTrayConstraint, 'active', ++ GObject.BindingFlags.SYNC_CREATE); ++ ++ Main.messageTray.add_constraint(this._messageTrayConstraint); ++ } ++ ++ const panelBottom = Main.layoutManager.panelBox.y + Main.layoutManager.panelBox.height; ++ const messageBottom = this._message.y + this._message.height; ++ ++ this._messageTrayConstraint.offset = messageBottom - panelBottom; ++ global.stage.disconnect(this._updateMessageTrayId); ++ this._updateMessageTrayId = 0; ++ }); ++ } ++ ++ _resetLoginDialog() { ++ if (!Main.sessionMode.isGreeter) ++ return; ++ ++ if (!Main.screenShield || !Main.screenShield._dialog) ++ return; ++ ++ const dialog = Main.screenShield._dialog; ++ ++ if (this._authPromptAllocatedId) { ++ dialog.disconnect(this._authPromptAllocatedId); ++ this._authPromptAllocatedId = 0; ++ } ++ ++ if (this._updateLoginDialogId) { ++ global.stage.disconnect(this._updateLoginDialogId); ++ this._updateLoginDialogId = 0; ++ } ++ ++ if (this._loginDialogConstraint) { ++ dialog.remove_constraint(this._loginDialogConstraint); ++ this._loginDialogConstraint = null; ++ } ++ } ++ ++ _adaptLoginDialogForMessage() { ++ if (!Main.sessionMode.isGreeter) ++ return; ++ ++ if (!Main.screenShield || !Main.screenShield._dialog) ++ return; ++ ++ if (!this._message || !this._message.visible) { ++ this._resetLoginDialog() ++ return; ++ } ++ ++ const dialog = Main.screenShield._dialog; ++ ++ if (this._updateLoginDialogId) ++ return; ++ ++ this._updateLoginDialogId = global.stage.connect('before-update', () => { ++ let messageHeight = this._message.y + this._message.height; ++ if (dialog._logoBin.visible) ++ messageHeight -= dialog._logoBin.height; ++ ++ if (!this._logindDialogConstraint) { ++ this._loginDialogConstraint = new HeadsUpConstraint({ primary: true }); ++ dialog.add_constraint(this._loginDialogConstraint); ++ } ++ ++ this._loginDialogConstraint.offset = messageHeight; ++ ++ global.stage.disconnect(this._updateLoginDialogId); ++ this._updateLoginDialogId = 0; ++ }); ++ } ++ ++ _adaptSessionForMessage() { ++ this._alignMessageTray(); ++ ++ if (Main.sessionMode.isGreeter) { ++ this._adaptLoginDialogForMessage(); ++ if (!this._authPromptAllocatedId) { ++ const dialog = Main.screenShield._dialog; ++ this._authPromptAllocatedId = dialog._authPrompt.connect('notify::allocation', this._adaptLoginDialogForMessage.bind(this)); ++ } ++ } ++ } ++} ++ ++function init() { ++ return new Extension(); ++} +diff --git a/extensions/heads-up-display/headsUpMessage.js b/extensions/heads-up-display/headsUpMessage.js +new file mode 100644 +index 00000000..87a8c8ba +--- /dev/null ++++ b/extensions/heads-up-display/headsUpMessage.js +@@ -0,0 +1,170 @@ ++const { Atk, Clutter, GLib, GObject, Pango, St } = imports.gi; ++const Layout = imports.ui.layout; ++const Main = imports.ui.main; ++const Signals = imports.signals; ++ ++var HeadsUpMessageBodyLabel = GObject.registerClass({ ++}, class HeadsUpMessageBodyLabel extends St.Label { ++ _init(params) { ++ super._init(params); ++ ++ this._widthCoverage = 0.75; ++ this._heightCoverage = 0.25; ++ ++ this._workAreasChangedId = global.display.connect('workareas-changed', this._getWorkAreaAndMeasureLineHeight.bind(this)); ++ } ++ ++ _getWorkAreaAndMeasureLineHeight() { ++ if (!this.get_parent()) ++ return; ++ ++ this._workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); ++ ++ this.clutter_text.single_line_mode = true; ++ this.clutter_text.line_wrap = false; ++ ++ this._lineHeight = super.vfunc_get_preferred_height(-1)[0]; ++ ++ this.clutter_text.single_line_mode = false; ++ this.clutter_text.line_wrap = true; ++ } ++ ++ vfunc_parent_set(oldParent) { ++ this._getWorkAreaAndMeasureLineHeight(); ++ } ++ ++ vfunc_get_preferred_width(forHeight) { ++ const maxWidth = this._widthCoverage * this._workArea.width ++ ++ let [labelMinimumWidth, labelNaturalWidth] = super.vfunc_get_preferred_width(forHeight); ++ ++ labelMinimumWidth = Math.min(labelMinimumWidth, maxWidth); ++ labelNaturalWidth = Math.min(labelNaturalWidth, maxWidth); ++ ++ return [labelMinimumWidth, labelNaturalWidth]; ++ } ++ ++ vfunc_get_preferred_height(forWidth) { ++ const labelHeightUpperBound = this._heightCoverage * this._workArea.height; ++ const numberOfLines = Math.floor(labelHeightUpperBound / this._lineHeight); ++ this._numberOfLines = Math.max(numberOfLines, 1); ++ ++ const maxHeight = this._lineHeight * this._numberOfLines; ++ ++ let [labelMinimumHeight, labelNaturalHeight] = super.vfunc_get_preferred_height(forWidth); ++ ++ labelMinimumHeight = Math.min(labelMinimumHeight, maxHeight); ++ labelNaturalHeight = Math.min(labelNaturalHeight, maxHeight); ++ ++ return [labelMinimumHeight, labelNaturalHeight]; ++ } ++ ++ destroy() { ++ if (this._workAreasChangedId) { ++ global.display.disconnect(this._workAreasChangedId); ++ this._workAreasChangedId = 0; ++ } ++ ++ super.destroy(); ++ } ++}); ++ ++var HeadsUpMessage = GObject.registerClass({ ++}, class HeadsUpMessage extends St.Button { ++ _init(heading, body) { ++ super._init({ ++ style_class: 'message', ++ accessible_role: Atk.Role.NOTIFICATION, ++ can_focus: false, ++ opacity: 0, ++ }); ++ ++ Main.layoutManager.addChrome(this, { affectsInputRegion: true }); ++ ++ this.add_style_class_name('heads-up-display-message'); ++ ++ this._panelAllocationId = Main.layoutManager.panelBox.connect('notify::allocation', this._alignWithPanel.bind(this)); ++ this.connect('notify::allocation', this._alignWithPanel.bind(this)); ++ ++ const contentsBox = new St.BoxLayout({ style_class: 'heads-up-message-content', ++ vertical: true, ++ x_align: Clutter.ActorAlign.CENTER }); ++ this.add_actor(contentsBox); ++ ++ this.headingLabel = new St.Label({ style_class: 'heads-up-message-heading', ++ x_expand: true, ++ x_align: Clutter.ActorAlign.CENTER }); ++ this.setHeading(heading); ++ contentsBox.add_actor(this.headingLabel); ++ this.contentsBox = contentsBox; ++ ++ this.bodyLabel = new HeadsUpMessageBodyLabel({ style_class: 'heads-up-message-body', ++ x_expand: true, ++ y_expand: true }); ++ contentsBox.add_actor(this.bodyLabel); ++ ++ this.setBody(body); ++ } ++ ++ vfunc_parent_set(oldParent) { ++ this._alignWithPanel(); ++ } ++ ++ _alignWithPanel() { ++ if (this._afterUpdateId) ++ return; ++ ++ this._afterUpdateId = global.stage.connect('before-update', () => { ++ let x = Main.panel.x; ++ let y = Main.panel.y + Main.panel.height; ++ ++ x += Main.panel.width / 2; ++ x -= this.width / 2; ++ x = Math.floor(x); ++ this.set_position(x,y); ++ this.opacity = 255; ++ ++ global.stage.disconnect(this._afterUpdateId); ++ this._afterUpdateId = 0; ++ }); ++ } ++ ++ setHeading(text) { ++ if (text) { ++ const heading = text ? text.replace(/\n/g, ' ') : ''; ++ this.headingLabel.text = heading; ++ this.headingLabel.visible = true; ++ } else { ++ this.headingLabel.text = text; ++ this.headingLabel.visible = false; ++ } ++ } ++ ++ setBody(text) { ++ this.bodyLabel.text = text; ++ if (text) { ++ this.bodyLabel.visible = true; ++ } else { ++ this.bodyLabel.visible = false; ++ } ++ } ++ ++ destroy() { ++ if (this._panelAllocationId) { ++ Main.layoutManager.panelBox.disconnect(this._panelAllocationId); ++ this._panelAllocationId = 0; ++ } ++ ++ if (this._afterUpdateId) { ++ global.stage.disconnect(this._afterUpdateId); ++ this._afterUpdateId = 0; ++ } ++ ++ if (this.bodyLabel) { ++ this.bodyLabel.destroy(); ++ this.bodyLabel = null; ++ } ++ ++ super.destroy(); ++ } ++}); +diff --git a/extensions/heads-up-display/meson.build b/extensions/heads-up-display/meson.build +new file mode 100644 +index 00000000..40c3de0a +--- /dev/null ++++ b/extensions/heads-up-display/meson.build +@@ -0,0 +1,8 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_sources += files('headsUpMessage.js', 'prefs.js') ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/heads-up-display/metadata.json.in b/extensions/heads-up-display/metadata.json.in +new file mode 100644 +index 00000000..e7ab71aa +--- /dev/null ++++ b/extensions/heads-up-display/metadata.json.in +@@ -0,0 +1,11 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"gettext-domain": "@gettext_domain@", ++"name": "Heads-up Display Message", ++"description": "Add a message to be displayed on screen always above all windows and chrome.", ++"original-authors": [ "rstrode@redhat.com" ], ++"shell-version": [ "@shell_current@" ], ++"url": "@url@", ++"session-modes": [ "gdm", "lock-screen", "unlock-dialog", "user" ] ++} +diff --git a/extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml b/extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml +new file mode 100644 +index 00000000..ea1f3774 +--- /dev/null ++++ b/extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml +@@ -0,0 +1,54 @@ ++<schemalist gettext-domain="gnome-shell-extensions"> ++ <schema id="org.gnome.shell.extensions.heads-up-display" ++ path="/org/gnome/shell/extensions/heads-up-display/"> ++ <key name="idle-timeout" type="u"> ++ <default>30</default> ++ <summary>Idle Timeout</summary> ++ <description> ++ Number of seconds until message is reshown after user goes idle. ++ </description> ++ </key> ++ <key name="message-heading" type="s"> ++ <default>""</default> ++ <summary>Message to show at top of display</summary> ++ <description> ++ The top line of the heads up display message. ++ </description> ++ </key> ++ <key name="message-body" type="s"> ++ <default>""</default> ++ <summary>Banner message</summary> ++ <description> ++ A message to always show at the top of the screen. ++ </description> ++ </key> ++ <key name="show-on-login-screen" type="b"> ++ <default>true</default> ++ <summary>Show on login screen</summary> ++ <description> ++ Whether or not the message should display on the login screen ++ </description> ++ </key> ++ <key name="show-when-locked" type="b"> ++ <default>false</default> ++ <summary>Show on screen shield</summary> ++ <description> ++ Whether or not the message should display when the screen is locked ++ </description> ++ </key> ++ <key name="show-when-unlocking" type="b"> ++ <default>false</default> ++ <summary>Show on unlock screen</summary> ++ <description> ++ Whether or not the message should display on the unlock screen. ++ </description> ++ </key> ++ <key name="show-when-unlocked" type="b"> ++ <default>false</default> ++ <summary>Show in user session</summary> ++ <description> ++ Whether or not the message should display when the screen is unlocked. ++ </description> ++ </key> ++ </schema> ++</schemalist> +diff --git a/extensions/heads-up-display/prefs.js b/extensions/heads-up-display/prefs.js +new file mode 100644 +index 00000000..a7106e07 +--- /dev/null ++++ b/extensions/heads-up-display/prefs.js +@@ -0,0 +1,194 @@ ++ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com> ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see <http://www.gnu.org/licenses/>. ++ */ ++ ++const { Gio, GObject, Gdk, Gtk } = imports.gi; ++const ExtensionUtils = imports.misc.extensionUtils; ++const Gettext = imports.gettext.domain('gnome-shell-extensions'); ++const _ = Gettext.gettext; ++const N_ = e => e; ++const cssData = ` ++ .no-border { ++ border: none; ++ } ++ ++ .border { ++ border: 1px solid; ++ border-radius: 3px; ++ border-color: #b6b6b3; ++ box-shadow: inset 0 0 0 1px rgba(74, 144, 217, 0); ++ background-color: white; ++ } ++ ++ .margins { ++ padding-left: 8px; ++ padding-right: 8px; ++ padding-bottom: 8px; ++ } ++ ++ .contents { ++ padding: 20px; ++ } ++ ++ .message-label { ++ font-weight: bold; ++ } ++`; ++ ++var settings; ++ ++function init() { ++ settings = ExtensionUtils.getSettings("org.gnome.shell.extensions.heads-up-display"); ++ const cssProvider = new Gtk.CssProvider(); ++ cssProvider.load_from_data(cssData); ++ ++ const display = Gdk.Display.get_default(); ++ Gtk.StyleContext.add_provider_for_display(display, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); ++} ++ ++function buildPrefsWidget() { ++ ExtensionUtils.initTranslations(); ++ ++ const contents = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 10, ++ visible: true, ++ }); ++ ++ contents.append(buildSwitch('show-when-locked', _("Show message when screen is locked"))); ++ contents.append(buildSwitch('show-when-unlocking', _("Show message on unlock screen"))); ++ contents.append(buildSwitch('show-when-unlocked', _("Show message when screen is unlocked"))); ++ contents.append(buildSpinButton('idle-timeout', _("Seconds after user goes idle before reshowing message"))); ++ contents.get_style_context().add_class("contents"); ++ ++ const outerMessageBox = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 5, ++ visible: true, ++ }); ++ contents.append(outerMessageBox); ++ ++ const messageLabel = new Gtk.Label({ ++ label: 'Message', ++ halign: Gtk.Align.START, ++ visible: true, ++ }); ++ messageLabel.get_style_context().add_class("message-label"); ++ outerMessageBox.append(messageLabel); ++ ++ const innerMessageBox = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 0, ++ visible: true, ++ }); ++ innerMessageBox.get_style_context().add_class("border"); ++ outerMessageBox.append(innerMessageBox); ++ ++ innerMessageBox.append(buildEntry('message-heading', _("Message Heading"))); ++ innerMessageBox.append(buildTextView('message-body', _("Message Body"))); ++ return contents; ++} ++ ++function buildTextView(key, labelText) { ++ const textView = new Gtk.TextView({ ++ accepts_tab: false, ++ visible: true, ++ wrap_mode: Gtk.WrapMode.WORD, ++ }); ++ ++ settings.bind(key, textView.get_buffer(), 'text', Gio.SettingsBindFlags.DEFAULT); ++ ++ const scrolledWindow = new Gtk.ScrolledWindow({ ++ hexpand: true, ++ vexpand: true, ++ visible: true, ++ }); ++ const styleContext = scrolledWindow.get_style_context(); ++ styleContext.add_class("margins"); ++ ++ scrolledWindow.set_child(textView); ++ return scrolledWindow; ++} ++function buildEntry(key, labelText) { ++ const entry = new Gtk.Entry({ ++ placeholder_text: labelText, ++ visible: true, ++ }); ++ const styleContext = entry.get_style_context(); ++ styleContext.add_class("no-border"); ++ settings.bind(key, entry, 'text', Gio.SettingsBindFlags.DEFAULT); ++ ++ entry.get_settings()['gtk-entry-select-on-focus'] = false; ++ ++ return entry; ++} ++ ++function buildSpinButton(key, labelText) { ++ const hbox = new Gtk.Box({ ++ orientation: Gtk.Orientation.HORIZONTAL, ++ spacing: 10, ++ visible: true, ++ }); ++ const label = new Gtk.Label({ ++ hexpand: true, ++ label: labelText, ++ visible: true, ++ xalign: 0, ++ }); ++ const adjustment = new Gtk.Adjustment({ ++ value: 0, ++ lower: 0, ++ upper: 2147483647, ++ step_increment: 1, ++ page_increment: 60, ++ page_size: 60, ++ }); ++ const spinButton = new Gtk.SpinButton({ ++ adjustment: adjustment, ++ climb_rate: 1.0, ++ digits: 0, ++ max_width_chars: 3, ++ visible: true, ++ width_chars: 3, ++ }); ++ settings.bind(key, spinButton, 'value', Gio.SettingsBindFlags.DEFAULT); ++ hbox.append(label); ++ hbox.append(spinButton); ++ return hbox; ++} ++ ++function buildSwitch(key, labelText) { ++ const hbox = new Gtk.Box({ ++ orientation: Gtk.Orientation.HORIZONTAL, ++ spacing: 10, ++ visible: true, ++ }); ++ const label = new Gtk.Label({ ++ hexpand: true, ++ label: labelText, ++ visible: true, ++ xalign: 0, ++ }); ++ const switcher = new Gtk.Switch({ ++ active: settings.get_boolean(key), ++ }); ++ settings.bind(key, switcher, 'active', Gio.SettingsBindFlags.DEFAULT); ++ hbox.append(label); ++ hbox.append(switcher); ++ return hbox; ++} +diff --git a/extensions/heads-up-display/stylesheet.css b/extensions/heads-up-display/stylesheet.css +new file mode 100644 +index 00000000..93034469 +--- /dev/null ++++ b/extensions/heads-up-display/stylesheet.css +@@ -0,0 +1,32 @@ ++.heads-up-display-message { ++ background-color: rgba(0.24, 0.24, 0.24, 0.80); ++ border: 1px solid black; ++ border-radius: 6px; ++ color: #eeeeec; ++ font-size: 11pt; ++ margin-top: 0.5em; ++ margin-bottom: 0.5em; ++ padding: 0.9em; ++} ++ ++.heads-up-display-message:insensitive { ++ background-color: rgba(0.24, 0.24, 0.24, 0.33); ++} ++ ++.heads-up-display-message:hover { ++ background-color: rgba(0.24, 0.24, 0.24, 0.2); ++ border: 1px solid rgba(0.0, 0.0, 0.0, 0.5); ++ color: #4d4d4d; ++ transition-duration: 250ms; ++} ++ ++.heads-up-message-heading { ++ height: 1.75em; ++ font-size: 1.25em; ++ font-weight: bold; ++ text-align: center; ++} ++ ++.heads-up-message-body { ++ text-align: center; ++} +diff --git a/meson.build b/meson.build +index 582535c4..ecc86fc8 100644 +--- a/meson.build ++++ b/meson.build +@@ -39,6 +39,7 @@ classic_extensions = [ + default_extensions = classic_extensions + default_extensions += [ + 'drive-menu', ++ 'heads-up-display', + 'screenshot-window-sizer', + 'windowsNavigator', + 'workspace-indicator' +-- +2.33.1 + |