[gnome] Update extensions

This commit is contained in:
2024-10-31 10:19:02 -04:00
parent 851ec4a772
commit 6d1e6d1132
238 changed files with 8705 additions and 4185 deletions

View File

@ -4,7 +4,7 @@ Bring some color to your GNOME desktop by syncing your desktop and lockscreen wa
[![Get it on GNOME extensions](/screenshot/get_it_on_gnome_extensions.png)](https://extensions.gnome.org/extension/1262/bing-wallpaper-changer/) [![<3 Sponsor this project on GitHub <3](/screenshot/sponsor.png)](https://github.com/sponsors/neffo) [![Get it on GNOME extensions](/screenshot/get_it_on_gnome_extensions.png)](https://extensions.gnome.org/extension/1262/bing-wallpaper-changer/) [![<3 Sponsor this project on GitHub <3](/screenshot/sponsor.png)](https://github.com/sponsors/neffo)
![Screenshot](/screenshot/notification.png) ![Screenshot](/screenshot/overview.jpg)
As featured on [OMG! Ubuntu](https://www.omgubuntu.co.uk/2017/07/bing-wallpaper-changer-gnome-extension). As featured on [OMG! Ubuntu](https://www.omgubuntu.co.uk/2017/07/bing-wallpaper-changer-gnome-extension).
@ -18,10 +18,44 @@ Also, check out my related [Google Earth View wallpaper extension](https://githu
* Image gallery to view, select and curate stored images * Image gallery to view, select and curate stored images
* Optionally delete old images after a week, or you can keep (and curate) them forever * Optionally delete old images after a week, or you can keep (and curate) them forever
* Override the lockscreen blur (NEW: lockscreen blur is now dynamic!) * Override the lockscreen blur (NEW: lockscreen blur is now dynamic!)
* Language support: English (en), German (de), Dutch (nl), Italian (it), Polish (pl), Chinese (zh_CN, zh_TW), French (fr_FR), Portuguese (pt, pt_BR), Ukrainian (uk), Russian (ru_RU), Spanish (es), Korean (ko), Indonesian (id), Catalan (ca), Norwegian Bokmål (nb) & Nynorsk (nn), Swedish (sv), Arabic (ar), Hungarian (hu), Japanese (ja), Czech (cs_CZ), Finnish (fi_FI) and Turkish (tr) - a HUGE thanks to the translators * Language support: English (en), German (de), Dutch (nl), Italian (it), Polish (pl), Chinese (zh_CN, zh_TW), French (fr_FR), Portuguese (pt, pt_BR), Ukrainian (uk), Russian (ru_RU), Spanish (es), Korean (ko), Indonesian (id), Catalan (ca), Norwegian Bokmål (nb) & Nynorsk (nn), Swedish (sv), Arabic (ar), Hungarian (hu), Japanese (ja), Czech (cs_CZ), Finnish (fi_FI) and Turkish (tr), Persian (fa_ir) - a HUGE thanks to the translators
* Image preview in menus & ability to manually set wallpapers individually or copy image to clipboard * Image preview in menus & ability to manually set wallpapers individually or copy image to clipboard
* A selection of different theme-aware indicator (tray) icons to choose (or hide it completely) * A selection of different theme-aware indicator (tray) icons to choose (or hide it completely)
## Quickstart guide
* Install from [GNOME extensions](https://extensions.gnome.org/extension/1262/bing-wallpaper-changer/), by default your wallpaper will be synced to the current Bing image of the day - if that's all you want you don't have to do anymore, everything is automatic
* Bing Wallpaper (by default) builds a collection of images over time (this can be disabled if required)
### Control bar
![Bing Wallpaper menu control bar](/screenshot/controlbar.png)
* 🤍 - Favorite/unfavorite current image (can be used to shuffle only favorite images and favorites are never deleted automatically)
* 🗑️ - Trash/untrash current image (exclude from shuffle selection or optionally deleted from disk)
* ⏪ - select previous day's image (in date order)
* ⏩ - select next day's image (in date order)
* ⏭️ - select today's image (skip to current)
* 🎲 - I'm feeling lucky, show me a random image (by default you should have at least 8 images available, curated with favorite and trash buttons)
### Quick settings
![Bing Wallpaper menu control bar](/screenshot/quicksettings.png)
* Always show new images - when a new Bing wallpaper is available switch to it immediately
* Image shuffle mode - switch to a random image at user defined intervals (default once per day or once per startup)
* Image shuffle only favorites - only select favorite images (🤍), by default 'trashed' images are always excluded
* Image shuffle only UHD resolution - occasionally some images are not UHD, exclude these from selection
### Gallery
![Gallery item](/screenshot/gallery.png)
The 4 buttons in the gallery (3rd page in the preferences) do have tool-tips but these do the following:
- Favorite - favorite this image (equivalent to doing this via the control bar)
- Apply - set this image as wallpaper
- View - open image in image viewer
- Info - open the Bing description of the image
- Trash - trash the image
## TODO ## TODO
* add more languages (#14) - [please help if you can](https://github.com/neffo/bing-wallpaper-gnome-extension/issues/14) * add more languages (#14) - [please help if you can](https://github.com/neffo/bing-wallpaper-gnome-extension/issues/14)
@ -33,10 +67,25 @@ Also, check out my related [Google Earth View wallpaper extension](https://githu
* Bing may detect your location incorrectly (and force a locale as above) - if you see this, please let me know what Bing.com itself does * Bing may detect your location incorrectly (and force a locale as above) - if you see this, please let me know what Bing.com itself does
* GNOME Shell themes can break some GNOME popup menu elements (toggle switches for example). This impacts GNOME more generally, not just this extension. Double check you are running latest versions of your themes (or disable them). * GNOME Shell themes can break some GNOME popup menu elements (toggle switches for example). This impacts GNOME more generally, not just this extension. Double check you are running latest versions of your themes (or disable them).
## Requirements ## System Requirements
GNOME 3.36+ or 40+ (Ubuntu 20.04 LTS or later, older versions of the extension work with 3.18+, but are no longer supported). GNOME 3.36+ or 40+ (Ubuntu 20.04 LTS or later, older versions of the extension work with 3.18+, but are no longer supported).
## Package dependencies
Below packages are required to build the extension
```
npm
gettext
intltool
zip
```
For Ubuntu you can hit below command to install
```
sudo apt install npm gettext intltool zip -y
```
## Install ## Install
[Install from extensions.gnome.org](https://extensions.gnome.org/extension/1262/bing-wallpaper-changer/) [Install from extensions.gnome.org](https://extensions.gnome.org/extension/1262/bing-wallpaper-changer/)
@ -53,7 +102,7 @@ sh install.sh
## Enable debug logging ## Enable debug logging
Enable debug logging through the exptension preferences 'Debug options' tab or if unable to open preferences you can enable debugging using dconf-editor with this command: Enable debug logging through the extension preferences 'Debug options' tab or if unable to open preferences you can enable debugging using dconf-editor with this command:
``` ```
GSETTINGS_SCHEMA_DIR=$HOME/.local/share/gnome-shell/extensions/BingWallpaper@ineffable-gmail.com/schemas dconf-editor /org/gnome/shell/extensions/bingwallpaper/ GSETTINGS_SCHEMA_DIR=$HOME/.local/share/gnome-shell/extensions/BingWallpaper@ineffable-gmail.com/schemas dconf-editor /org/gnome/shell/extensions/bingwallpaper/
``` ```

View File

@ -20,6 +20,7 @@ var shellVersionMajor = parseInt(Config.PACKAGE_VERSION.split('.')[0]);
var shellVersionMinor = parseInt(Config.PACKAGE_VERSION.split('.')[1]); var shellVersionMinor = parseInt(Config.PACKAGE_VERSION.split('.')[1]);
var shellVersionPoint = parseInt(Config.PACKAGE_VERSION.split('.')[2]); var shellVersionPoint = parseInt(Config.PACKAGE_VERSION.split('.')[2]);
var blurField = shellVersionMajor >= 46 ? "radius" : "sigma";
// default BWP mild blur // default BWP mild blur
var BWP_BLUR_SIGMA = 2; var BWP_BLUR_SIGMA = 2;
var BWP_BLUR_BRIGHTNESS = 55; var BWP_BLUR_BRIGHTNESS = 55;
@ -50,7 +51,7 @@ export function _updateBackgroundEffects_BWP(monitorIndex) {
if (effect) { if (effect) {
effect.set({ // GNOME defaults when login prompt is visible effect.set({ // GNOME defaults when login prompt is visible
brightness: BLUR_BRIGHTNESS, brightness: BLUR_BRIGHTNESS,
sigma: BLUR_SIGMA * themeContext.scale_factor, [blurField]: BLUR_SIGMA * themeContext.scale_factor,
}); });
} }
} }
@ -59,7 +60,7 @@ export function _updateBackgroundEffects_BWP(monitorIndex) {
if (effect) { if (effect) {
effect.set({ // adjustable blur when clock is visible effect.set({ // adjustable blur when clock is visible
brightness: BWP_BLUR_BRIGHTNESS * 0.01, // we use 0-100 rather than 0-1, so divide by 100 brightness: BWP_BLUR_BRIGHTNESS * 0.01, // we use 0-100 rather than 0-1, so divide by 100
sigma: BWP_BLUR_SIGMA * themeContext.scale_factor, [blurField]: BWP_BLUR_SIGMA * themeContext.scale_factor,
}); });
} }
} }
@ -91,7 +92,7 @@ export function _clampValue(value) {
export default class Blur { export default class Blur {
constructor() { constructor() {
this.enabled = false; this.enabled = false;
log('Bing Wallpaper adjustable blur is '+supportedVersion()?'available':'not available'); log('Bing Wallpaper adjustable blur is '+(supportedVersion()?'available':'not available'));
} }
set_blur_strength(value) { set_blur_strength(value) {

View File

@ -108,7 +108,7 @@ export default class Carousel {
this.log('Delete requested for '+filename); this.log('Delete requested for '+filename);
Utils.deleteImage(filename); Utils.deleteImage(filename);
Utils.setImageHiddenStatus(this.settings, image.urlbase, true); Utils.setImageHiddenStatus(this.settings, image.urlbase, true);
Utils.cleanupImageList(this.settings); // hide image instead Utils.purgeImages(this.settings); // hide image instead
widget.get_parent().get_parent().set_visible(false); // bit of a hack widget.get_parent().get_parent().set_visible(false); // bit of a hack
if (this.callbackfunc) if (this.callbackfunc)
this.callbackfunc(); this.callbackfunc();

View File

@ -55,10 +55,16 @@ const newMenuSwitchItem = (label, state) => {
} }
function log(msg) { function log(msg) {
if (bingWallpaperIndicator && bingWallpaperIndicator._settings.get_boolean('debug-logging')) if (BingDebug())
console.log('BingWallpaper extension: ' + msg); // disable to keep the noise down in journal console.log('BingWallpaper extension: ' + msg); // disable to keep the noise down in journal
} }
function BingDebug() {
if (bingWallpaperIndicator && bingWallpaperIndicator._settings.get_boolean('debug-logging'))
return true;
return false;
}
function notifyError(msg) { function notifyError(msg) {
Main.notifyError("BingWallpaper extension error", msg); Main.notifyError("BingWallpaper extension error", msg);
} }
@ -152,12 +158,12 @@ class BingWallpaperIndicator extends Button {
this.toggleSetBackground = newMenuSwitchItem(_("Set background image"), this._settings.get_boolean('set-background')); this.toggleSetBackground = newMenuSwitchItem(_("Set background image"), this._settings.get_boolean('set-background'));
this.toggleSelectNew = newMenuSwitchItem(_("Always show new images"), this._settings.get_boolean('revert-to-current-image')); this.toggleSelectNew = newMenuSwitchItem(_("Always show new images"), this._settings.get_boolean('revert-to-current-image'));
this.toggleShuffle = newMenuSwitchItem(_("Image shuffle mode"), true); this.toggleShuffle = newMenuSwitchItem(_("Image shuffle mode"), true);
this.toggleShuffleOnlyFaves = newMenuSwitchItem(_("Image shuffle only favourites"), this._settings.get_boolean('random-mode-include-only-favourites')); this.toggleShuffleOnlyFaves = newMenuSwitchItem(_("Image shuffle only favorites"), this._settings.get_boolean('random-mode-include-only-favourites'));
this.toggleNotifications = newMenuSwitchItem(_("Enable desktop notifications"), this._settings.get_boolean('notify')); /*this.toggleNotifications = newMenuSwitchItem(_("Enable desktop notifications"), this._settings.get_boolean('notify'));*/
this.toggleImageCount = newMenuSwitchItem(_("Show image count"), this._settings.get_boolean('show-count-in-image-title')); this.toggleImageCount = newMenuSwitchItem(_("Show image count"), this._settings.get_boolean('show-count-in-image-title'));
this.toggleShuffleOnlyUHD = newMenuSwitchItem(_("Image shuffle only UHD resolutions"), this._settings.get_boolean('random-mode-include-only-uhd')); this.toggleShuffleOnlyUHD = newMenuSwitchItem(_("Image shuffle only UHD resolutions"), this._settings.get_boolean('random-mode-include-only-uhd'));
[this.toggleNotifications, /*this.toggleImageCount, this.toggleSetBackground,*/ this.toggleSelectNew, [/*this.toggleNotifications, this.toggleImageCount, this.toggleSetBackground,*/ this.toggleSelectNew,
this.toggleShuffle, this.toggleShuffleOnlyFaves, this.toggleShuffleOnlyUHD] this.toggleShuffle, this.toggleShuffleOnlyFaves, this.toggleShuffleOnlyUHD]
.forEach(e => this.settingsSubMenu.menu.addMenuItem(e)); .forEach(e => this.settingsSubMenu.menu.addMenuItem(e));
@ -220,15 +226,21 @@ class BingWallpaperIndicator extends Button {
// listen for configuration changes // listen for configuration changes
_setConnections() { _setConnections() {
this._settings.connect('changed::hide', () => { this.settings_connections = [];
this.visible = !this._settings.get_boolean('hide');
}); this.settings_connections.push(
this._settings.connect('changed::hide', () => {
this.visible = !this._settings.get_boolean('hide');
})
);
let settingConnections = [ let settingConnections = [
{signal: 'changed::icon-name', call: this._setIcon}, {signal: 'changed::icon-name', call: this._setIcon},
{signal: 'changed::market', call: this._refresh}, {signal: 'changed::market', call: this._refresh},
{signal: 'changed::set-background', call: this._setBackground}, {signal: 'changed::set-background', call: this._setBackground},
{signal: 'changed::override-lockscreen-blur', call: this._setBlur}, {signal: 'changed::override-lockscreen-blur', call: this._setBlur},
{signal: 'changed::lockscreen-blur-strength', call: this._setBlur},
{signal: 'changed::lockscreen-blur-brightness', call: this._setBlur},
{signal: 'changed::selected-image', call: this._setImage}, {signal: 'changed::selected-image', call: this._setImage},
{signal: 'changed::delete-previous', call: this._cleanUpImages}, {signal: 'changed::delete-previous', call: this._cleanUpImages},
{signal: 'changed::notify', call: this._notifyCurrentImage}, {signal: 'changed::notify', call: this._notifyCurrentImage},
@ -244,11 +256,10 @@ class BingWallpaperIndicator extends Button {
// _setShuffleToggleState // _setShuffleToggleState
settingConnections.forEach((e) => { settingConnections.forEach((e) => {
this._settings.connect(e.signal, e.call.bind(this)); this.settings_connections.push(
this._settings.connect(e.signal, e.call.bind(this))
);
}); });
this._settings.connect('changed::lockscreen-blur-strength', blur.set_blur_strength.bind(this, this._settings.get_int('lockscreen-blur-strength')));
this._settings.connect('changed::lockscreen-blur-brightness', blur.set_blur_brightness.bind(this, this._settings.get_int('lockscreen-blur-brightness')));
// ensure we're in a sensible initial state // ensure we're in a sensible initial state
this._setIcon(); this._setIcon();
@ -275,19 +286,23 @@ class BingWallpaperIndicator extends Button {
let toggles = [ /*{key: 'set-background', toggle: this.toggleSetBackground},*/ let toggles = [ /*{key: 'set-background', toggle: this.toggleSetBackground},*/
{key: 'revert-to-current-image', toggle: this.toggleSelectNew}, {key: 'revert-to-current-image', toggle: this.toggleSelectNew},
{key: 'notify', toggle: this.toggleNotifications}, /*{key: 'notify', toggle: this.toggleNotifications},
/*{key: 'show-count-in-image-title', toggle: this.toggleImageCount},*/ {key: 'show-count-in-image-title', toggle: this.toggleImageCount},*/
{key: 'random-mode-enabled', toggle: this.toggleShuffle}, {key: 'random-mode-enabled', toggle: this.toggleShuffle},
{key: 'random-mode-include-only-favourites', toggle: this.toggleShuffleOnlyFaves}, {key: 'random-mode-include-only-favourites', toggle: this.toggleShuffleOnlyFaves},
/*{key: 'random-mode-include-only-unhidden', toggle: this.toggleShuffleOnlyUnhidden},*/ /*{key: 'random-mode-include-only-unhidden', toggle: this.toggleShuffleOnlyUnhidden},*/
{key: 'random-mode-include-only-uhd', toggle: this.toggleShuffleOnlyUHD}]; {key: 'random-mode-include-only-uhd', toggle: this.toggleShuffleOnlyUHD}];
toggles.forEach( (e) => { toggles.forEach( (e) => {
this._settings.connect('changed::'+e.key, () => { this.settings_connections.push(
e.toggle.setToggleState(this._settings.get_boolean(e.key)); this._settings.connect('changed::'+e.key, () => {
}); log(e.key+' setting changed to '+ (this._settings.get_boolean(e.key)?'true':'false'));
e.toggle.setToggleState(this._settings.get_boolean(e.key));
})
);
e.toggle.connect('toggled', (item, state) => { e.toggle.connect('toggled', (item, state) => {
this._settings.set_boolean(e.key, state); log(e.key+' switch toggled to '+ (state?'true':'false'));
this._setBooleanSetting(e.key, state);
}); });
}); });
@ -300,7 +315,32 @@ class BingWallpaperIndicator extends Button {
[this.clipboardImageItem, this.clipboardURLItem]. [this.clipboardImageItem, this.clipboardURLItem].
forEach(e => e.setSensitive(false)); forEach(e => e.setSensitive(false));
} }
} }
_setBooleanSetting(key, state) {
let success = this._settings.set_boolean(key, state);
log('key '+key+' set to ' + (state?'true':'false') + ' (returned ' + (success?'true':'false')+')');
}
_setStringSetting(key, value) {
let success = this._settings.set_string(key, value);
log('key '+key+' set to ' + value + ' (returned ' + (success?'true':'false')+')');
}
_setIntSetting(key, value) {
let success = this._settings.set_int(key, value);
log('key '+key+' set to ' + value + ' (returned ' + (success?'true':'false')+')');
}
_onDestroy() {
this._unsetConnections();
}
_unsetConnections() {
this.settings_connections.forEach((e) => {
this._settings.disconnect(e);
});
}
_openPrefs() { _openPrefs() {
this._extension.openPreferences(); this._extension.openPreferences();
@ -312,7 +352,6 @@ class BingWallpaperIndicator extends Button {
this.clipboardImageItem.setSensitive(!this._updatePending && this.imageURL != ""); this.clipboardImageItem.setSensitive(!this._updatePending && this.imageURL != "");
this.clipboardURLItem.setSensitive(!this._updatePending && this.imageURL != ""); this.clipboardURLItem.setSensitive(!this._updatePending && this.imageURL != "");
this.thumbnailItem.setSensitive(!this._updatePending && this.imageURL != ""); this.thumbnailItem.setSensitive(!this._updatePending && this.imageURL != "");
//this.showItem.setSensitive(!this._updatePending && this.title != "" && this.explanation != "");
this.dwallpaperItem.setSensitive(!this._updatePending && this.filename != ""); this.dwallpaperItem.setSensitive(!this._updatePending && this.filename != "");
this.swallpaperItem.setSensitive(!this._updatePending && this.filename != ""); this.swallpaperItem.setSensitive(!this._updatePending && this.filename != "");
this.titleItem.setSensitive(!this._updatePending && this.imageinfolink != ""); this.titleItem.setSensitive(!this._updatePending && this.imageinfolink != "");
@ -348,7 +387,7 @@ class BingWallpaperIndicator extends Button {
if (this._settings.get_boolean('notify')) { if (this._settings.get_boolean('notify')) {
let image = this._getCurrentImage(); let image = this._getCurrentImage();
if (image) { if (image) {
this._createNotification(image); this._createImageNotification(image);
} }
} }
} }
@ -555,7 +594,7 @@ class BingWallpaperIndicator extends Button {
} }
_curImage() { _curImage() {
this._settings.set_string('selected-image', 'current'); this._setStringSetting('selected-image', 'current');
this._gotoImage(0); this._gotoImage(0);
} }
@ -569,13 +608,13 @@ class BingWallpaperIndicator extends Button {
if (randomEnabled) { if (randomEnabled) {
log('enabled shuffle mode, by setting a shuffe timer (5 seconds)'); log('enabled shuffle mode, by setting a shuffe timer (5 seconds)');
this._restartShuffleTimeout(5); this._restartShuffleTimeout(5);
this._settings.set_boolean('revert-to-current-image', false); this._setBooleanSetting('revert-to-current-image', false);
} }
else { else {
// clear shuffle timer // clear shuffle timer
if (this._shuffleTimeout) if (this._shuffleTimeout)
GLib.source_remove(this._shuffleTimeout); GLib.source_remove(this._shuffleTimeout);
this._settings.set_boolean('revert-to-current-image', true); this._setBooleanSetting('revert-to-current-image', true);
} }
} }
@ -627,7 +666,7 @@ class BingWallpaperIndicator extends Button {
let newImage = Utils.getImageByIndex(imageList, curIndex + relativePos); let newImage = Utils.getImageByIndex(imageList, curIndex + relativePos);
if (newImage) if (newImage)
this._settings.set_string('selected-image', newImage.urlbase.replace('/th?id=OHR.', '')); this._setStringSetting('selected-image', newImage.urlbase.replace('/th?id=OHR.', ''));
} }
_getCurrentImage() { _getCurrentImage() {
@ -645,10 +684,17 @@ class BingWallpaperIndicator extends Button {
this._restartTimeout(); this._restartTimeout();
let market = this._settings.get_string('market'); let market = this._settings.get_string('market');
// Soup3 should be the version used, but in the past some distros have packaged older versions only
if (Soup.MAJOR_VERSION >= 3) { if (Soup.MAJOR_VERSION >= 3) {
let url = BingImageURL; let url = BingImageURL;
let params = Utils.BingParams; let params = Utils.BingParams;
params['mkt'] = ( market != 'auto' ? market : '' ); params['mkt'] = ( market != 'auto' ? market : '' );
// if we've set previous days to be something less than 8 and
// delete previous is active we want to just request a subset of wallpapers
if (this._settings.get_boolean('delete-previous') == true && this._settings.get_int('previous-days')<8) {
params['n'] = ""+this._settings.get_int('previous-days');
}
let request = Soup.Message.new_from_encoded_form('GET', url, Soup.form_encode_hash(params)); let request = Soup.Message.new_from_encoded_form('GET', url, Soup.form_encode_hash(params));
request.request_headers.append('Accept', 'application/json'); request.request_headers.append('Accept', 'application/json');
@ -660,6 +706,7 @@ class BingWallpaperIndicator extends Button {
} }
catch(error) { catch(error) {
log('unable to send libsoup json message '+error); log('unable to send libsoup json message '+error);
notifyError('Unable to fetch Bing metadata\n'+error);
} }
} }
else { else {
@ -675,6 +722,7 @@ class BingWallpaperIndicator extends Button {
} }
catch (error) { catch (error) {
log('unable to send libsoup json message '+error); log('unable to send libsoup json message '+error);
notifyError('Unable to fetch Bing metadata\n'+error);
} }
} }
} }
@ -694,6 +742,7 @@ class BingWallpaperIndicator extends Button {
} }
catch (error) { catch (error) {
log('Network error occured: ' + error); log('Network error occured: ' + error);
notifyError('network error occured\n'+error);
this._updatePending = false; this._updatePending = false;
this._restartTimeout(TIMEOUT_SECONDS_ON_HTTP_ERROR); this._restartTimeout(TIMEOUT_SECONDS_ON_HTTP_ERROR);
} }
@ -759,13 +808,13 @@ class BingWallpaperIndicator extends Button {
log('WARNING: Bing returning market data for ' + datamarket + ' rather than selected ' + prefmarket); log('WARNING: Bing returning market data for ' + datamarket + ' rather than selected ' + prefmarket);
Utils.purgeImages(this._settings); // delete older images if enabled Utils.purgeImages(this._settings); // delete older images if enabled
//Utils.cleanupImageList(this._settings); // disabled, as old images should still be downloadble in theory //Utils.cleanupImageList(this._settings); // merged into purgeImages
this._downloadAllImages(); // fetch missing images that are still available this._downloadAllImages(); // fetch missing images that are still available
Utils.populateImageListResolutions(this._settings); Utils.populateImageListResolutions(this._settings);
if (newImages.length > 0 && this._settings.get_boolean('revert-to-current-image')) { if (newImages.length > 0 && this._settings.get_boolean('revert-to-current-image')) {
// user wants to switch to the new image when it arrives // user wants to switch to the new image when it arrives
this._settings.set_string('selected-image', 'current'); this._setStringSetting('selected-image', 'current');
} }
if (this._settings.get_boolean('notify')) { if (this._settings.get_boolean('notify')) {
@ -773,7 +822,7 @@ class BingWallpaperIndicator extends Button {
// notify all new images // notify all new images
newImages.forEach((image) => { newImages.forEach((image) => {
log('New image to notify: ' + Utils.getImageTitle(image)); log('New image to notify: ' + Utils.getImageTitle(image));
this._createNotification(image); this._createImageNotification(image);
}); });
} }
else { else {
@ -781,7 +830,7 @@ class BingWallpaperIndicator extends Button {
let last = newImages.pop(); let last = newImages.pop();
if (last) { if (last) {
log('New image to notify: ' + Utils.getImageTitle(last)); log('New image to notify: ' + Utils.getImageTitle(last));
this._createNotification(last); this._createImageNotification(last);
} }
} }
} }
@ -791,6 +840,7 @@ class BingWallpaperIndicator extends Button {
} }
catch (error) { catch (error) {
log('_parseData() failed with error ' + error + ' @ '+error.lineNumber); log('_parseData() failed with error ' + error + ' @ '+error.lineNumber);
notifyError('Bing metadata parsing error check ' + error + ' @ '+error.lineNumber);
log(error.stack); log(error.stack);
} }
} }
@ -801,15 +851,25 @@ class BingWallpaperIndicator extends Button {
} }
} }
_createNotification(image) { _createImageNotification(image) {
// set notifications icon
let source = new MessageTray.Source('Bing Wallpaper', 'preferences-desktop-wallpaper-symbolic');
Main.messageTray.add(source);
let msg = _('Bing Wallpaper of the Day for') + ' ' + this._localeDate(image.fullstartdate); let msg = _('Bing Wallpaper of the Day for') + ' ' + this._localeDate(image.fullstartdate);
let details = Utils.getImageTitle(image); let details = Utils.getImageTitle(image);
let notification = new MessageTray.Notification(source, msg, details); this._createNotification(msg, details);
notification.setTransient(this._settings.get_boolean('transient')); log('_createImageNotification: '+msg+' details: '+details);
source.showNotification(notification); }
_createNotification(msg, details) {
const systemSource = MessageTray.getSystemSource();
const bingNotify = new MessageTray.Notification({
source: systemSource,
title: msg,
body: details,
gicon: new Gio.ThemedIcon({name: 'image-x-generic'}),
iconName: 'image-x-generic',
});
systemSource.addNotification(bingNotify);
//Main.notify(msg, details);
log('_createNotification: '+msg+' details: '+details);
} }
_shuffleImage() { _shuffleImage() {
@ -889,7 +949,7 @@ class BingWallpaperIndicator extends Button {
this.dimensions.width = image.width?image.width:null; this.dimensions.width = image.width?image.width:null;
this.dimensions.height = image.height?image.height:null; this.dimensions.height = image.height?image.height:null;
this.selected_image = Utils.getImageUrlBase(image); this.selected_image = Utils.getImageUrlBase(image);
this._settings.set_string('selected-image', this.selected_image); this._setStringSetting('selected-image', this.selected_image);
if (("favourite" in image) && image.favourite === true ) { if (("favourite" in image) && image.favourite === true ) {
this.favourite_status = true; this.favourite_status = true;
@ -944,14 +1004,14 @@ class BingWallpaperIndicator extends Button {
let stateJSON = JSON.stringify(state); let stateJSON = JSON.stringify(state);
log('Storing state as JSON: ' + stateJSON); log('Storing state as JSON: ' + stateJSON);
this._settings.set_string('state', stateJSON); this._setStringSetting('state', stateJSON);
} }
} }
_reStoreState() { _reStoreState() {
try { try {
// patch for relative paths, ensures that users running git version don't end up with broken state - see EGO review for version 38 https://extensions.gnome.org/review/30299 // patch for relative paths, ensures that users running git version don't end up with broken state - see EGO review for version 38 https://extensions.gnome.org/review/30299
this._settings.set_string('download-folder', this._settings.get_string('download-folder').replace('$HOME', '~')); this._setStringSetting('download-folder', this._settings.get_string('download-folder').replace('$HOME', '~'));
let stateJSON = this._settings.get_string('state'); let stateJSON = this._settings.get_string('state');
let state = JSON.parse(stateJSON); let state = JSON.parse(stateJSON);
let maxLongDate = null; let maxLongDate = null;
@ -1015,7 +1075,9 @@ class BingWallpaperIndicator extends Button {
let BingWallpaperDir = Utils.getWallpaperDir(this._settings); let BingWallpaperDir = Utils.getWallpaperDir(this._settings);
let dir = Gio.file_new_for_path(BingWallpaperDir); let dir = Gio.file_new_for_path(BingWallpaperDir);
if (!dir.query_exists(null)) { if (!dir.query_exists(null)) {
dir.make_directory_with_parents(null); //dir.make_directory_with_parents(null);
notifyError('Download folder '+BingWallpaperDir+' does not exist or is not writable');
return;
} }
log("Downloading " + url + " to " + file.get_uri()); log("Downloading " + url + " to " + file.get_uri());
let request = Soup.Message.new('GET', url); let request = Soup.Message.new('GET', url);
@ -1035,6 +1097,7 @@ class BingWallpaperIndicator extends Button {
} }
catch (error) { catch (error) {
log('error sending libsoup message '+error); log('error sending libsoup message '+error);
notifyError('Network error '+error);
} }
} }
@ -1059,12 +1122,14 @@ class BingWallpaperIndicator extends Button {
} }
catch(e) { catch(e) {
log('Error writing file: ' + e); log('Error writing file: ' + e);
notifyError('Image '+file.get_path()+' is not writable, check folder permissions or select a different folder\n'+e);
} }
} }
); );
} }
catch (error) { catch (error) {
log('Unable download image '+error); log('Unable download image '+error);
notifyError('Image '+file.get_path()+' file error, check folder permissions, disk space or select a different folder\n'+e);
} }
} }

View File

@ -6,9 +6,10 @@
"settings-schema": "org.gnome.shell.extensions.bingwallpaper", "settings-schema": "org.gnome.shell.extensions.bingwallpaper",
"shell-version": [ "shell-version": [
"45", "45",
"46" "46",
"47"
], ],
"url": "https://github.com/neffo/bing-wallpaper-gnome-extension", "url": "https://github.com/neffo/bing-wallpaper-gnome-extension",
"uuid": "BingWallpaper@ineffable-gmail.com", "uuid": "BingWallpaper@ineffable-gmail.com",
"version": 49 "version": 50
} }

View File

@ -73,6 +73,8 @@ export default class BingWallpaperExtensionPreferences extends ExtensionPreferen
const debugSwitch = buildable.get_object('debug_switch'); const debugSwitch = buildable.get_object('debug_switch');
const revertSwitch = buildable.get_object('revert_switch'); const revertSwitch = buildable.get_object('revert_switch');
const trash_purge_switch = buildable.get_object('trash_purge_switch'); const trash_purge_switch = buildable.get_object('trash_purge_switch');
const delete_previous_switch = buildable.get_object('delete_previous_switch');
const delete_previous_adjustment = buildable.get_object('delete_previous_adjustment');
const always_export_switch = buildable.get_object('always_export_switch'); const always_export_switch = buildable.get_object('always_export_switch');
const gallery_page = buildable.get_object('gallery_page'); const gallery_page = buildable.get_object('gallery_page');
const carouselFlowBox = buildable.get_object('carouselFlowBox'); const carouselFlowBox = buildable.get_object('carouselFlowBox');
@ -213,6 +215,7 @@ export default class BingWallpaperExtensionPreferences extends ExtensionPreferen
settings.bind('random-interval', randomIntervalEntry, 'value', Gio.SettingsBindFlags.DEFAULT); settings.bind('random-interval', randomIntervalEntry, 'value', Gio.SettingsBindFlags.DEFAULT);
settings.bind('trash-deletes-images', trash_purge_switch, 'active', Gio.SettingsBindFlags.DEFAULT); settings.bind('trash-deletes-images', trash_purge_switch, 'active', Gio.SettingsBindFlags.DEFAULT);
settings.bind('always-export-bing-json', always_export_switch, 'active', Gio.SettingsBindFlags.DEFAULT); settings.bind('always-export-bing-json', always_export_switch, 'active', Gio.SettingsBindFlags.DEFAULT);
settings.bind('delete-previous', delete_previous_switch, 'active', Gio.SettingsBindFlags.DEFAULT);
// button opens Nautilus at our image folder // button opens Nautilus at our image folder
openBtn.connect('clicked', (widget) => { openBtn.connect('clicked', (widget) => {
@ -276,6 +279,7 @@ export default class BingWallpaperExtensionPreferences extends ExtensionPreferen
settings.bind('override-lockscreen-blur', overrideSwitch, 'active', Gio.SettingsBindFlags.DEFAULT); settings.bind('override-lockscreen-blur', overrideSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
settings.bind('lockscreen-blur-strength', strengthEntry, 'value', Gio.SettingsBindFlags.DEFAULT); settings.bind('lockscreen-blur-strength', strengthEntry, 'value', Gio.SettingsBindFlags.DEFAULT);
settings.bind('lockscreen-blur-brightness', brightnessEntry, 'value', Gio.SettingsBindFlags.DEFAULT); settings.bind('lockscreen-blur-brightness', brightnessEntry, 'value', Gio.SettingsBindFlags.DEFAULT);
settings.bind('previous-days', delete_previous_adjustment, 'value', Gio.SettingsBindFlags.DEFAULT);
// add a couple of preset buttons // add a couple of preset buttons
defaultBtn.connect('clicked', (widget) => { defaultBtn.connect('clicked', (widget) => {

View File

@ -69,7 +69,14 @@
<description>Save disk space</description> <description>Save disk space</description>
</key> </key>
<key name="keep-favourites" type="b">
<default>true</default>
<summary>Keep previous wallpapers if they are favourites</summary>
<description>Sensible default behaivour</description>
</key>
<key name="previous-days" type="i"> <key name="previous-days" type="i">
<range min="1" max="90" />
<default>8</default> <default>8</default>
<summary>Number of days to keep wallpapers</summary> <summary>Number of days to keep wallpapers</summary>
<description>Delete wallpapers after this number of days</description> <description>Delete wallpapers after this number of days</description>

View File

@ -162,7 +162,7 @@ Author: Michael Carroll
</child> </child>
<child> <child>
<object class="GtkButton" id="randomButton"> <object class="GtkButton" id="randomButton">
<property name="label" translatable="yes">Set random mode</property> <property name="label" translatable="yes">Image shuffle mode</property>
<property name="receives-default">1</property> <property name="receives-default">1</property>
<layout> <layout>
<property name="column">0</property> <property name="column">0</property>

View File

@ -201,6 +201,11 @@ Bing Wallpaper GNOME extension by: Michael Carroll
<property name="subtitle" translatable="yes">Enable logging to system journal</property> <property name="subtitle" translatable="yes">Enable logging to system journal</property>
</object> </object>
</child> </child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="adv_group">
<property name="title" translatable="yes">Advanced options</property>
<child> <child>
<object class="AdwSwitchRow" id="revert_switch"> <object class="AdwSwitchRow" id="revert_switch">
<property name="title" translatable="yes">Always show new images</property> <property name="title" translatable="yes">Always show new images</property>
@ -210,7 +215,7 @@ Bing Wallpaper GNOME extension by: Michael Carroll
<child> <child>
<object class="AdwSwitchRow" id="trash_purge_switch"> <object class="AdwSwitchRow" id="trash_purge_switch">
<property name="title" translatable="yes">Purge on trash</property> <property name="title" translatable="yes">Purge on trash</property>
<property name="subtitle" translatable="yes">Trashing an image will remove it from database</property> <property name="subtitle" translatable="yes">Trashing an image will remove it from database and disk</property>
</object> </object>
</child> </child>
<child> <child>
@ -233,6 +238,26 @@ Bing Wallpaper GNOME extension by: Michael Carroll
</property> </property>
</object> </object>
</child> </child>
<child>
<object class="AdwSwitchRow" id="delete_previous_switch">
<property name="title" translatable="yes">Delete older images</property>
<property name="subtitle" translatable="yes">Automatically delete older images (excludes favourites)</property>
</object>
</child>
<child>
<object class="AdwSpinRow" id="delete_previous_entry">
<property name="title" translatable="yes">Number of days to keep images</property>
<property name="subtitle" translatable="yes">Keep images for this number of days (favourites are kept forever)</property>
<property name="adjustment">
<object class="GtkAdjustment" id="delete_previous_adjustment">
<property name="lower">1</property>
<property name="upper">90</property>
<property name="page_increment">5</property>
<property name="step_increment">1</property>
</object>
</property>
</object>
</child>
</object> </object>
</child> </child>
<child> <child>

View File

@ -118,14 +118,16 @@ export function fetch_change_log(version, label, httpSession) {
httpSession.send_and_read_async(request, GLib.PRIORITY_DEFAULT, null, (httpSession, message) => { httpSession.send_and_read_async(request, GLib.PRIORITY_DEFAULT, null, (httpSession, message) => {
let data = decoder.decode(httpSession.send_and_read_finish(message).get_data()); let data = decoder.decode(httpSession.send_and_read_finish(message).get_data());
let text = JSON.parse(data).body; let text = JSON.parse(data).body;
label.set_label(text); if (text)
label.set_label(text);
}); });
} }
else { else {
httpSession.queue_message(request, (httpSession, message) => { httpSession.queue_message(request, (httpSession, message) => {
let data = message.response_body.data; let data = message.response_body.data;
let text = JSON.parse(data).body; let text = JSON.parse(data).body;
label.set_label(text); if (text)
label.set_label(text);
}); });
} }
} }
@ -299,26 +301,6 @@ export function getImageByIndex(imageList, index) {
return imageList[index]; return imageList[index];
} }
export function cleanupImageList(settings) {
if (settings.get_boolean('trash-deletes-images') == false)
return;
let curList = imageListSortByDate(getImageList(settings));
let cutOff = GLib.DateTime.new_now_utc().add_days(-8); // 8 days ago
let newList = [];
curList.forEach( function (x, i) {
let filename = imageToFilename(settings, x);
let diff = dateFromLongDate(x.fullstartdate, 0).difference(cutOff);
// image is still downloadable (< 8 days old) or still on disk, so we keep
if (diff > 0 || Gio.file_new_for_path(filename).query_exists(null)) {
newList.push(x);
}
else {
BingLog('Cleaning up: '+filename);
}
});
setImageList(settings, newList);
}
export function populateImageListResolutions(settings) { export function populateImageListResolutions(settings) {
let curList = imageListSortByDate(getImageList(settings)); let curList = imageListSortByDate(getImageList(settings));
let newList = []; let newList = [];
@ -337,7 +319,11 @@ export function populateImageListResolutions(settings) {
export function getFetchableImageList(settings) { export function getFetchableImageList(settings) {
let imageList = getImageList(settings); let imageList = getImageList(settings);
let cutOff = GLib.DateTime.new_now_utc().add_days(-8); // 8 days ago let maxpictures = settings.get_int('previous-days');
let maxdownload = 8;
if (maxpictures < maxdownload && maxpictures >=1)
maxdownload = maxpictures;
let cutOff = GLib.DateTime.new_now_utc().add_days(-maxdownload); // default 8 days ago, 1 day = 1 picture
let dlList = []; let dlList = [];
imageList.forEach( function (x, i) { imageList.forEach( function (x, i) {
let diff = dateFromLongDate(x.fullstartdate, 0).difference(cutOff); let diff = dateFromLongDate(x.fullstartdate, 0).difference(cutOff);
@ -369,7 +355,10 @@ export function getWallpaperDir(settings) {
dir.make_directory_with_parents(null); dir.make_directory_with_parents(null);
} }
//FIXME: test if dir is good and writable //FIXME: test if dir is good and writable
return BingWallpaperDir; if (dir.query_exists(null))
return BingWallpaperDir;
else
return null;
} }
export function setWallpaperDir(settings, uri) { export function setWallpaperDir(settings, uri) {
@ -547,24 +536,43 @@ export function deleteImage(to_delete) {
} }
} }
// add image to persistant list so we can delete it later (in chronological order), delete the oldest image (if user wants this) // optionally purge trashed images (default is not, these just don't get select in random mode), optionally purge older images
export function purgeImages(settings) { export function purgeImages(settings) {
let deletepictures = settings.get_boolean('delete-previous'); let deleteprevious = settings.get_boolean('delete-previous');
if (deletepictures === false) let keepfavourites = settings.get_boolean('keep-favourites');
return; let emptytrash = settings.get_boolean('trash-deletes-images');
let maxDays = settings.get_int('previous-days');
BingLog('purgeImages() dp: '+(deleteprevious?'true':'false')+'days:'+maxDays+' favs: '+(keepfavourites?'true':'false')+' trash: '+(emptytrash?'true':'false'));
/*if (deleteprevious === false)
return;*/
let imagelist = imageListSortByDate(getImageList(settings)); let imagelist = imageListSortByDate(getImageList(settings));
let maxpictures = settings.get_int('previous-days');
let origlength = imagelist.length; let origlength = imagelist.length;
while (imagelist.length > maxpictures) { let cutOff = GLib.DateTime.new_now_utc().add_days(-maxDays); // 8 days ago
var to_delete = imagelist.shift(); // get the first (oldest item from the list) let newList = [];
if (deletepictures && to_delete != '') { imagelist.forEach( function (image, i) {
let imageFilename = imageToFilename(settings, to_delete); var diff = dateFromLongDate(image.fullstartdate, 0).difference(cutOff); // relative age of image, < 0 we can delete
// always keep favourites, keep images that are less than minimum period (previous days) or if clean up delete previous is disabled (default)
var keep_image = (keepfavourites && image.favourite && image.favourite === true) || diff > 0 || !deleteprevious;
var ok_to_delete = !keep_image || (emptytrash && image.hidden);
var imageFilename = imageToFilename(settings, image);
if (emptytrash && image.hidden && diff < 0)
ok_to_delete = true;
if (deleteprevious && image != '' && ok_to_delete) {
BingLog('deleting '+imageFilename); BingLog('deleting '+imageFilename);
deleteImage(imageFilename); deleteImage(imageFilename);
} }
} else {
BingLog('keeping '+imageFilename);
newList.push(image);
}
});
setImageList(settings, newList);
BingLog('cleaned up image list, count was '+origlength+' now '+imagelist.length); BingLog('cleaned up image list, count was '+origlength+' now '+imagelist.length);
cleanupImageList(settings); //cleanupImageList(settings);
validate_imagename(settings); // if we deleted our current image, we want to reset it to something valid validate_imagename(settings); // if we deleted our current image, we want to reset it to something valid
} }
@ -605,7 +613,8 @@ export function importBingJSON(settings) {
let parsed = JSON.parse(decoder.decode(contents)); // FIXME: triggers GJS warning without the conversion, need to investigate let parsed = JSON.parse(decoder.decode(contents)); // FIXME: triggers GJS warning without the conversion, need to investigate
// need to implement some checks for validity here // need to implement some checks for validity here
mergeImageLists(settings, parsed); mergeImageLists(settings, parsed);
cleanupImageList(settings); // remove the older missing images purgeImages(settings); // remove the older missing images
//cleanupImageList(settings);
} }
} }
else { else {

View File

@ -33,7 +33,7 @@ var debugEnabled = false;
/** /**
* @param {boolean} d Enable/Disable debug logging * @param {boolean} d Enable/Disable debug logging
*/ */
function setDebugEnabled(d) { export function setDebugEnabled(d) {
debugEnabled = d; debugEnabled = d;
} }
@ -41,9 +41,9 @@ function setDebugEnabled(d) {
* @param {string} msg the message to log * @param {string} msg the message to log
* @class * @class
*/ */
function TalkativeLog(msg) { export function TalkativeLog(msg) {
if (debugEnabled) if (debugEnabled)
log(`[ESC]${msg}`); console.log(`[ESC]${msg}`);
} }
/** /**
@ -53,8 +53,8 @@ function TalkativeLog(msg) {
* *
* @returns {string} the version * @returns {string} the version
*/ */
function getFullVersion() { export function getFullVersion() {
return '1.9.0'; // FULL_VERSION return '1.10.0'; // FULL_VERSION
} }
/** /**
@ -64,7 +64,7 @@ function getFullVersion() {
* @param {string} name filename of the image * @param {string} name filename of the image
* @returns {Gio.FileIcon} the icon * @returns {Gio.FileIcon} the icon
*/ */
function loadIcon(extensionDir, name) { export function loadIcon(extensionDir, name) {
return new Gio.FileIcon({ return new Gio.FileIcon({
file: Gio.File.new_for_path( file: Gio.File.new_for_path(
getImagePath(extensionDir, name) getImagePath(extensionDir, name)
@ -79,8 +79,6 @@ function loadIcon(extensionDir, name) {
* @param {string} name filename of the image * @param {string} name filename of the image
* @returns {string} the path * @returns {string} the path
*/ */
function getImagePath(extensionDir, name) { export function getImagePath(extensionDir, name) {
return extensionDir.get_child(`images/${name}`).get_path(); return extensionDir.get_child(`images/${name}`).get_path();
} }
export {TalkativeLog, getFullVersion, setDebugEnabled, loadIcon, getImagePath};

View File

@ -3,7 +3,7 @@
/** /**
* @type {{_display(): Meta_Display, number_of_displays(): int}} * @type {{_display(): Meta_Display, number_of_displays(): int}}
*/ */
var DisplayApi = { export const DisplayApi = {
/** /**
* Returns the Wayland display or screen * Returns the Wayland display or screen
* *
@ -36,5 +36,3 @@ var DisplayApi = {
this._display().set_cursor(cursor); this._display().set_cursor(cursor);
}, },
}; };
export {DisplayApi};

View File

@ -5,9 +5,10 @@
"name": "EasyScreenCast", "name": "EasyScreenCast",
"settings-schema": "org.gnome.shell.extensions.EasyScreenCast", "settings-schema": "org.gnome.shell.extensions.EasyScreenCast",
"shell-version": [ "shell-version": [
"46" "46",
"47"
], ],
"url": "https://github.com/EasyScreenCast/EasyScreenCast", "url": "https://github.com/EasyScreenCast/EasyScreenCast",
"uuid": "EasyScreenCast@iacopodeenosee.gmail.com", "uuid": "EasyScreenCast@iacopodeenosee.gmail.com",
"version": 50 "version": 51
} }

View File

@ -27,6 +27,7 @@ import Meta from 'gi://Meta';
import Clutter from 'gi://Clutter'; import Clutter from 'gi://Clutter';
import St from 'gi://St'; import St from 'gi://St';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
@ -38,15 +39,7 @@ import {DisplayApi} from './display_module.js';
/** /**
* @type {Capture} * @type {Capture}
*/ */
const Capture = GObject.registerClass({ class Capture extends Signals.EventEmitter {
GTypeName: 'EasyScreenCast_Capture',
Signals: {
'captured-event': {
param_types: [Clutter.Event.$gtype],
},
'stop': {},
},
}, class Capture extends GObject.Object {
constructor() { constructor() {
super(); super();
Lib.TalkativeLog('-£-capture selection init'); Lib.TalkativeLog('-£-capture selection init');
@ -183,14 +176,9 @@ const Capture = GObject.registerClass({
toString() { toString() {
return this.GTypeName; return this.GTypeName;
} }
}); }
var SelectionArea = GObject.registerClass({ export class SelectionArea extends Signals.EventEmitter {
GTypeName: 'EasyScreenCast_SelectionArea',
Signals: {
'stop': {},
},
}, class SelectionArea extends GObject.Object {
constructor() { constructor() {
super(); super();
Lib.TalkativeLog('-£-area selection init'); Lib.TalkativeLog('-£-area selection init');
@ -238,14 +226,9 @@ var SelectionArea = GObject.registerClass({
toString() { toString() {
return this.GTypeName; return this.GTypeName;
} }
}); }
var SelectionWindow = GObject.registerClass({ export class SelectionWindow extends Signals.EventEmitter {
GTypeName: 'EasyScreenCast_SelectionWindow',
Signals: {
'stop': {},
},
}, class SelectionWindow extends GObject.Object {
constructor() { constructor() {
super(); super();
Lib.TalkativeLog('-£-window selection init'); Lib.TalkativeLog('-£-window selection init');
@ -285,12 +268,12 @@ var SelectionWindow = GObject.registerClass({
if (this._selectedWindow) { if (this._selectedWindow) {
this._capture._stop(); this._capture._stop();
var maxHeight = global.screen_height; let maxHeight = global.screen_height;
var maxWidth = global.screen_width; let maxWidth = global.screen_width;
Lib.TalkativeLog(`-£-global screen area H: ${maxHeight} W: ${maxWidth}`); Lib.TalkativeLog(`-£-global screen area H: ${maxHeight} W: ${maxWidth}`);
var [w, h] = this._selectedWindow.get_size(); let [w, h] = this._selectedWindow.get_size();
var [wx, wy] = this._selectedWindow.get_position(); let [wx, wy] = this._selectedWindow.get_position();
Lib.TalkativeLog(`-£-windows pre wx: ${wx} wy: ${wy} height: ${h} width: ${w}`); Lib.TalkativeLog(`-£-windows pre wx: ${wx} wy: ${wy} height: ${h} width: ${w}`);
@ -331,22 +314,17 @@ var SelectionWindow = GObject.registerClass({
toString() { toString() {
return this.GTypeName; return this.GTypeName;
} }
}); }
var SelectionDesktop = GObject.registerClass({ export class SelectionDesktop extends Signals.EventEmitter {
GTypeName: 'EasyScreenCast_SelectionDesktop',
Signals: {
'stop': {},
},
}, class SelectionDesktop extends GObject.Object {
constructor() { constructor() {
super(); super();
Lib.TalkativeLog('-£-desktop selection init'); Lib.TalkativeLog('-£-desktop selection init');
const displayCount = DisplayApi.number_displays(); const displayCount = DisplayApi.number_displays();
Lib.TalkativeLog(`-£-Number of monitor ${displayCount}`); Lib.TalkativeLog(`-£-Number of monitor ${displayCount}`);
for (var i = 0; i < displayCount; i++) { for (let i = 0; i < displayCount; i++) {
var tmpM = DisplayApi.display_geometry_for_index(i); let tmpM = DisplayApi.display_geometry_for_index(i);
Lib.TalkativeLog(`-£-monitor ${i} geometry x=${tmpM.x} y=${tmpM.y} w=${tmpM.width} h=${tmpM.height}`); Lib.TalkativeLog(`-£-monitor ${i} geometry x=${tmpM.x} y=${tmpM.y} w=${tmpM.width} h=${tmpM.height}`);
} }
@ -376,10 +354,10 @@ var SelectionDesktop = GObject.registerClass({
let tmpM = Main.layoutManager.currentMonitor; let tmpM = Main.layoutManager.currentMonitor;
var x = tmpM.x; let x = tmpM.x;
var y = tmpM.y; let y = tmpM.y;
var height = tmpM.height; let height = tmpM.height;
var width = tmpM.width; let width = tmpM.width;
Lib.TalkativeLog(`-£-desktop x: ${x} y: ${y} height: ${height} width: ${width}`); Lib.TalkativeLog(`-£-desktop x: ${x} y: ${y} height: ${height} width: ${width}`);
this._capture._saveRect(x, y, height, width); this._capture._saveRect(x, y, height, width);
@ -389,11 +367,9 @@ var SelectionDesktop = GObject.registerClass({
toString() { toString() {
return this.GTypeName; return this.GTypeName;
} }
}); }
var AreaRecording = GObject.registerClass({ export class AreaRecording extends GObject.Object {
GTypeName: 'EasyScreenCast_AreaRecording',
}, class AreaRecording extends GObject.Object {
constructor() { constructor() {
super(); super();
Lib.TalkativeLog('-£-area recording init'); Lib.TalkativeLog('-£-area recording init');
@ -407,9 +383,9 @@ var AreaRecording = GObject.registerClass({
y: -10, y: -10,
}); });
var [recX, recY, recW, recH] = Ext.Indicator.getSelectedRect(); let [recX, recY, recW, recH] = Ext.Indicator.getSelectedRect();
var tmpH = Main.layoutManager.currentMonitor.height; let tmpH = Main.layoutManager.currentMonitor.height;
var tmpW = Main.layoutManager.currentMonitor.width; let tmpW = Main.layoutManager.currentMonitor.width;
Main.uiGroup.add_child(this._areaRecording); Main.uiGroup.add_child(this._areaRecording);
@ -463,7 +439,7 @@ var AreaRecording = GObject.registerClass({
toString() { toString() {
return this.GTypeName; return this.GTypeName;
} }
}); }
/** /**
* @param {number} x1 left position * @param {number} x1 left position
@ -530,5 +506,3 @@ function _selectWindow(windows, x, y) {
return filtered[0]; return filtered[0];
} }
export {SelectionArea, SelectionWindow, SelectionDesktop, AreaRecording};

View File

@ -15,51 +15,51 @@
import GObject from 'gi://GObject'; import GObject from 'gi://GObject';
// setting keys // setting keys
var INPUT_AUDIO_SOURCE_SETTING_KEY = 'input-audio-source'; export const INPUT_AUDIO_SOURCE_SETTING_KEY = 'input-audio-source';
var ACTIVE_POST_CMD_SETTING_KEY = 'execute-post-cmd'; export const ACTIVE_POST_CMD_SETTING_KEY = 'execute-post-cmd';
var POST_CMD_SETTING_KEY = 'post-cmd'; export const POST_CMD_SETTING_KEY = 'post-cmd';
var ACTIVE_PRE_CMD_SETTING_KEY = 'execute-pre-cmd'; export const ACTIVE_PRE_CMD_SETTING_KEY = 'execute-pre-cmd';
var PRE_CMD_SETTING_KEY = 'pre-cmd'; export const PRE_CMD_SETTING_KEY = 'pre-cmd';
var ACTIVE_CUSTOM_GSP_SETTING_KEY = 'active-custom-gsp'; export const ACTIVE_CUSTOM_GSP_SETTING_KEY = 'active-custom-gsp';
var ACTIVE_SHORTCUT_SETTING_KEY = 'active-shortcut'; export const ACTIVE_SHORTCUT_SETTING_KEY = 'active-shortcut';
var SHORTCUT_KEY_SETTING_KEY = 'shortcut-key'; export const SHORTCUT_KEY_SETTING_KEY = 'shortcut-key';
var TIME_DELAY_SETTING_KEY = 'delay-time'; export const TIME_DELAY_SETTING_KEY = 'delay-time';
var SHOW_NOTIFY_ALERT_SETTING_KEY = 'show-notify-alert'; export const SHOW_NOTIFY_ALERT_SETTING_KEY = 'show-notify-alert';
var SHOW_AREA_REC_SETTING_KEY = 'show-area-rec'; export const SHOW_AREA_REC_SETTING_KEY = 'show-area-rec';
var VERBOSE_DEBUG_SETTING_KEY = 'verbose-debug'; export const VERBOSE_DEBUG_SETTING_KEY = 'verbose-debug';
var PIPELINE_REC_SETTING_KEY = 'pipeline'; export const PIPELINE_REC_SETTING_KEY = 'pipeline';
var FPS_SETTING_KEY = 'fps'; export const FPS_SETTING_KEY = 'fps';
var STATUS_INDICATORS_SETTING_KEY = 'status-indicators'; export const STATUS_INDICATORS_SETTING_KEY = 'status-indicators';
var X_POS_SETTING_KEY = 'x-pos'; export const X_POS_SETTING_KEY = 'x-pos';
var Y_POS_SETTING_KEY = 'y-pos'; export const Y_POS_SETTING_KEY = 'y-pos';
var WIDTH_SETTING_KEY = 'width-rec'; export const WIDTH_SETTING_KEY = 'width-rec';
var HEIGHT_SETTING_KEY = 'height-rec'; export const HEIGHT_SETTING_KEY = 'height-rec';
var DRAW_CURSOR_SETTING_KEY = 'draw-cursor'; export const DRAW_CURSOR_SETTING_KEY = 'draw-cursor';
var AREA_SCREEN_SETTING_KEY = 'area-screen'; export const AREA_SCREEN_SETTING_KEY = 'area-screen';
var FILE_NAME_SETTING_KEY = 'file-name'; export const FILE_NAME_SETTING_KEY = 'file-name';
var FILE_FOLDER_SETTING_KEY = 'file-folder'; export const FILE_FOLDER_SETTING_KEY = 'file-folder';
var FILE_CONTAINER_SETTING_KEY = 'file-container'; export const FILE_CONTAINER_SETTING_KEY = 'file-container';
var FILE_RESOLUTION_TYPE_SETTING_KEY = 'file-resolution-type'; export const FILE_RESOLUTION_TYPE_SETTING_KEY = 'file-resolution-type';
var FILE_RESOLUTION_KAR_SETTING_KEY = 'file-resolution-kar'; export const FILE_RESOLUTION_KAR_SETTING_KEY = 'file-resolution-kar';
var FILE_RESOLUTION_WIDTH_SETTING_KEY = 'file-resolution-width'; export const FILE_RESOLUTION_WIDTH_SETTING_KEY = 'file-resolution-width';
var FILE_RESOLUTION_HEIGHT_SETTING_KEY = 'file-resolution-height'; export const FILE_RESOLUTION_HEIGHT_SETTING_KEY = 'file-resolution-height';
var QUALITY_SETTING_KEY = 'quality-index'; export const QUALITY_SETTING_KEY = 'quality-index';
var DEVICE_INDEX_WEBCAM_SETTING_KEY = 'device-webcam-index'; export const DEVICE_INDEX_WEBCAM_SETTING_KEY = 'device-webcam-index';
var DEVICE_WEBCAM_SETTING_KEY = 'device-webcam'; export const DEVICE_WEBCAM_SETTING_KEY = 'device-webcam';
var QUALITY_WEBCAM_SETTING_KEY = 'quality-webcam'; export const QUALITY_WEBCAM_SETTING_KEY = 'quality-webcam';
var WIDTH_WEBCAM_SETTING_KEY = 'width-webcam'; export const WIDTH_WEBCAM_SETTING_KEY = 'width-webcam';
var HEIGHT_WEBCAM_SETTING_KEY = 'height-webcam'; export const HEIGHT_WEBCAM_SETTING_KEY = 'height-webcam';
var TYPE_UNIT_WEBCAM_SETTING_KEY = 'type-unit-webcam'; export const TYPE_UNIT_WEBCAM_SETTING_KEY = 'type-unit-webcam';
var MARGIN_X_WEBCAM_SETTING_KEY = 'margin-x-webcam'; export const MARGIN_X_WEBCAM_SETTING_KEY = 'margin-x-webcam';
var MARGIN_Y_WEBCAM_SETTING_KEY = 'margin-y-webcam'; export const MARGIN_Y_WEBCAM_SETTING_KEY = 'margin-y-webcam';
var ALPHA_CHANNEL_WEBCAM_SETTING_KEY = 'alpha-channel-webcam'; export const ALPHA_CHANNEL_WEBCAM_SETTING_KEY = 'alpha-channel-webcam';
var CORNER_POSITION_WEBCAM_SETTING_KEY = 'corner-position-webcam'; export const CORNER_POSITION_WEBCAM_SETTING_KEY = 'corner-position-webcam';
// shortcut tree view columns // shortcut tree view columns
var SHORTCUT_COLUMN_KEY = 0; export const SHORTCUT_COLUMN_KEY = 0;
var SHORTCUT_COLUMN_MODS = 1; export const SHORTCUT_COLUMN_MODS = 1;
var Settings = GObject.registerClass(class EasyScreenCastSettings extends GObject.Object { export const Settings = GObject.registerClass(class EasyScreenCastSettings extends GObject.Object {
constructor(settings) { constructor(settings) {
super(); super();
this._settings = settings; this._settings = settings;
@ -137,56 +137,10 @@ var Settings = GObject.registerClass(class EasyScreenCastSettings extends GObjec
* @param {boolean} audio with or without audio * @param {boolean} audio with or without audio
* @returns {string} * @returns {string}
*/ */
function getGSPstd(audio) { export function getGSPstd(audio) {
// TODO update gsp // TODO update gsp
if (audio) if (audio)
return 'queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! videorate ! vp8enc min_quantizer=0 max_quantizer=5 cpu-used=3 deadline=1000000 threads=%T ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! mux. pulsesrc ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! audioconvert ! vorbisenc ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! mux. webmmux name=mux '; return 'queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! videorate ! vp8enc min_quantizer=0 max_quantizer=5 cpu-used=3 deadline=1000000 threads=%T ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! mux. pulsesrc ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! audioconvert ! vorbisenc ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! mux. webmmux name=mux ';
else else
return 'vp9enc min_quantizer=0 max_quantizer=5 cpu-used=3 deadline=1000000 threads=%T ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! webmmux'; return 'vp9enc min_quantizer=0 max_quantizer=5 cpu-used=3 deadline=1000000 threads=%T ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! webmmux';
} }
export {
getGSPstd,
Settings,
INPUT_AUDIO_SOURCE_SETTING_KEY,
ACTIVE_POST_CMD_SETTING_KEY,
POST_CMD_SETTING_KEY,
ACTIVE_PRE_CMD_SETTING_KEY,
PRE_CMD_SETTING_KEY,
ACTIVE_CUSTOM_GSP_SETTING_KEY,
ACTIVE_SHORTCUT_SETTING_KEY,
SHORTCUT_KEY_SETTING_KEY,
TIME_DELAY_SETTING_KEY,
SHOW_NOTIFY_ALERT_SETTING_KEY,
SHOW_AREA_REC_SETTING_KEY,
VERBOSE_DEBUG_SETTING_KEY,
PIPELINE_REC_SETTING_KEY,
FPS_SETTING_KEY,
STATUS_INDICATORS_SETTING_KEY,
X_POS_SETTING_KEY,
Y_POS_SETTING_KEY,
WIDTH_SETTING_KEY,
HEIGHT_SETTING_KEY,
DRAW_CURSOR_SETTING_KEY,
AREA_SCREEN_SETTING_KEY,
FILE_NAME_SETTING_KEY,
FILE_FOLDER_SETTING_KEY,
FILE_CONTAINER_SETTING_KEY,
FILE_RESOLUTION_TYPE_SETTING_KEY,
FILE_RESOLUTION_KAR_SETTING_KEY,
FILE_RESOLUTION_WIDTH_SETTING_KEY,
FILE_RESOLUTION_HEIGHT_SETTING_KEY,
QUALITY_SETTING_KEY,
DEVICE_INDEX_WEBCAM_SETTING_KEY,
DEVICE_WEBCAM_SETTING_KEY,
QUALITY_WEBCAM_SETTING_KEY,
WIDTH_WEBCAM_SETTING_KEY,
HEIGHT_WEBCAM_SETTING_KEY,
TYPE_UNIT_WEBCAM_SETTING_KEY,
MARGIN_X_WEBCAM_SETTING_KEY,
MARGIN_Y_WEBCAM_SETTING_KEY,
ALPHA_CHANNEL_WEBCAM_SETTING_KEY,
CORNER_POSITION_WEBCAM_SETTING_KEY,
SHORTCUT_COLUMN_KEY,
SHORTCUT_COLUMN_MODS
};

View File

@ -27,7 +27,7 @@ let ElapsedSec;
/** /**
* @type {TimerDelay} * @type {TimerDelay}
*/ */
const TimerDelay = GObject.registerClass({ export const TimerDelay = GObject.registerClass({
GTypeName: 'EasyScreenCast_TimerDelay', GTypeName: 'EasyScreenCast_TimerDelay',
}, class TimerDelay extends GObject.Object { }, class TimerDelay extends GObject.Object {
/** /**
@ -145,7 +145,7 @@ let secpassed = 0;
/** /**
* @type {TimerCounting} * @type {TimerCounting}
*/ */
var TimerCounting = GObject.registerClass({ export const TimerCounting = GObject.registerClass({
GTypeName: 'EasyScreenCast_TimerCounting', GTypeName: 'EasyScreenCast_TimerCounting',
}, class TimerCounting extends GObject.Object { }, class TimerCounting extends GObject.Object {
/** /**
@ -255,5 +255,3 @@ var TimerCounting = GObject.registerClass({
} }
} }
}); });
export {TimerDelay, TimerCounting};

View File

@ -27,7 +27,7 @@ import * as Ext from './extension.js';
/** /**
* @type {MixerAudio} * @type {MixerAudio}
*/ */
var MixerAudio = GObject.registerClass({ export const MixerAudio = GObject.registerClass({
GTypeName: 'EasyScreenCast_MixerAudio', GTypeName: 'EasyScreenCast_MixerAudio',
}, class MixerAudio extends GObject.Object { }, class MixerAudio extends GObject.Object {
constructor() { constructor() {
@ -317,5 +317,3 @@ var MixerAudio = GObject.registerClass({
} }
} }
}); });
export {MixerAudio};

View File

@ -20,7 +20,7 @@ import * as Lib from './convenience.js';
/** /**
* @type {ExecuteStuff} * @type {ExecuteStuff}
*/ */
var ExecuteStuff = GObject.registerClass({ export const ExecuteStuff = GObject.registerClass({
GTypeName: 'EasyScreenCast_ExecuteStuff', GTypeName: 'EasyScreenCast_ExecuteStuff',
}, class ExecuteStuff extends GObject.Object { }, class ExecuteStuff extends GObject.Object {
/** /**
@ -262,5 +262,3 @@ var ExecuteStuff = GObject.registerClass({
} }
} }
}); });
export {ExecuteStuff};

View File

@ -321,7 +321,7 @@ const CONTAINER = [webmVP8, webmVP9, mp4, mkv, ogg, mp4Aac];
* *
* @returns {string} * @returns {string}
*/ */
function composeGSP(settings, mixer) { export function composeGSP(settings, mixer) {
Lib.TalkativeLog('-§-COMPOSE GSP'); Lib.TalkativeLog('-§-COMPOSE GSP');
let tmpGSP = ''; let tmpGSP = '';
@ -703,7 +703,7 @@ function _getWebCamDimension(settings) {
* @param {int} container selected container format * @param {int} container selected container format
* @returns {string} * @returns {string}
*/ */
function getDescr(quality, container) { export function getDescr(quality, container) {
Lib.TalkativeLog(`-§-get description Q-> ${quality} C-> ${container}`); Lib.TalkativeLog(`-§-get description Q-> ${quality} C-> ${container}`);
return CONTAINER[container].quality[quality].descr; return CONTAINER[container].quality[quality].descr;
@ -716,7 +716,7 @@ function getDescr(quality, container) {
* @param {int} container selected container format * @param {int} container selected container format
* @returns {number} * @returns {number}
*/ */
function getFps(quality, container) { export function getFps(quality, container) {
Lib.TalkativeLog(`-§-get fps Q-> ${quality} C-> ${container}`); Lib.TalkativeLog(`-§-get fps Q-> ${quality} C-> ${container}`);
return CONTAINER[container].quality[quality].fps; return CONTAINER[container].quality[quality].fps;
@ -728,10 +728,8 @@ function getFps(quality, container) {
* @param {int} container selected container format * @param {int} container selected container format
* @returns {string} * @returns {string}
*/ */
function getFileExtension(container) { export function getFileExtension(container) {
Lib.TalkativeLog(`-§-get file extension C-> ${container}`); Lib.TalkativeLog(`-§-get file extension C-> ${container}`);
return CONTAINER[container].fileExt; return CONTAINER[container].fileExt;
} }
export {composeGSP, getDescr, getFps, getFileExtension};

View File

@ -27,7 +27,7 @@ import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'
/** /**
* @type {NotifyManager} * @type {NotifyManager}
*/ */
var NotifyManager = GObject.registerClass({ export const NotifyManager = GObject.registerClass({
GTypeName: 'EasyScreenCast_NotifyManager', GTypeName: 'EasyScreenCast_NotifyManager',
}, class NotifyManager extends GObject.Object { }, class NotifyManager extends GObject.Object {
/** /**
@ -143,5 +143,3 @@ var NotifyManager = GObject.registerClass({
} }
} }
}); });
export {NotifyManager};

View File

@ -27,7 +27,7 @@ import * as Ext from './extension.js';
/** /**
* @type {CaptureVideo} * @type {CaptureVideo}
*/ */
var CaptureVideo = GObject.registerClass({ export const CaptureVideo = GObject.registerClass({
GTypeName: 'EasyScreenCast_CaptureVideo', GTypeName: 'EasyScreenCast_CaptureVideo',
}, class CaptureVideo extends GObject.Object { }, class CaptureVideo extends GObject.Object {
/** /**
@ -212,5 +212,3 @@ var CaptureVideo = GObject.registerClass({
return result; return result;
} }
}); });
export {CaptureVideo};

View File

@ -17,7 +17,7 @@ import GLib from 'gi://GLib';
import Gst from 'gi://Gst?version=1.0'; import Gst from 'gi://Gst?version=1.0';
import * as Lib from './convenience.js'; import * as Lib from './convenience.js';
var HelperWebcam = GObject.registerClass({ export const HelperWebcam = GObject.registerClass({
GTypeName: 'EasyScreenCast_HelperWebcam', GTypeName: 'EasyScreenCast_HelperWebcam',
}, class HelperWebcam extends GObject.Object { }, class HelperWebcam extends GObject.Object {
/** /**
@ -241,10 +241,18 @@ var HelperWebcam = GObject.registerClass({
// example, a pipewiresrc or a v4l2src. For now, we are only // example, a pipewiresrc or a v4l2src. For now, we are only
// using v4l2src. // using v4l2src.
// See also: Gst.DeviceMonitor.get_providers: pipewiredeviceprovider,decklinkdeviceprovider,v4l2deviceprovider // See also: Gst.DeviceMonitor.get_providers: pipewiredeviceprovider,decklinkdeviceprovider,v4l2deviceprovider
// CLI: "/usr/bin/gst-device-monitor-1.0 Video/Source"
// //
// So, here we filter the devices, that have a device.path property, which // So, here we filter the devices, that have a device.path property, which
// means, these are only v4l2 devices // means, these are only v4l2 devices
var filtered = list.filter(device => device.get_properties().get_string('device.path') !== null);
var filtered = list.filter(device => {
let props = device.get_properties();
let hasDevice = props != null && props.get_string('device.path') !== null;
if (props != null)
props.free();
return hasDevice;
});
Lib.TalkativeLog(`-@-devices number after filtering for v4l2: ${filtered.length}`); Lib.TalkativeLog(`-@-devices number after filtering for v4l2: ${filtered.length}`);
return filtered; return filtered;
@ -311,5 +319,3 @@ var HelperWebcam = GObject.registerClass({
} }
} }
}); });
export {HelperWebcam};

View File

@ -1,8 +1 @@
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#222" d="M7 0C6 0 6 1 6 1v1H5s-.707-.016-1.45.355C2.814 2.727 2 3.668 2 5v8s-.016.707.355 1.45C2.727 15.187 3.668 16 5 16h6s.707.016 1.45-.355C13.187 15.273 14 14.332 14 13V5c0-1.332-.812-2.273-1.55-2.645C11.706 1.985 11 2 11 2h-1V1c0-1-1-1-1-1zM5 4h6c.555 0 1 .445 1 1v8c0 .555-.445 1-1 1H5c-.555 0-1-.445-1-1V5c0-.555.445-1 1-1m0 1v2h6V5zm0 3v2h6V8zm0 3v2h6v-2zm0 0"/></svg>
<svg width="16" height="16.001" xmlns="http://www.w3.org/2000/svg">
<g fill="#474747">
<path d="M5 5v2h6V5z" overflow="visible"/>
<path d="M5.469 0c-.49 0-.797.216-1.032.456C4.202.696 4 1.012 4 1.486V2H2v14h12V2h-2v-.406l-.002-.028a1.616 1.616 0 0 0-.416-1.011c-.236-.28-.62-.585-1.2-.553L10.438 0zm.53 2h4v2h2v10H4V4h2z" color="#bebebe" font-family="sans-serif" font-weight="400" overflow="visible" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none" white-space="normal"/>
<path d="m5 8v2h6v-2zm0 3v2h6v-2z" overflow="visible"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 909 B

After

Width:  |  Height:  |  Size: 451 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#2e3436" d="M5 0v2s-.707-.016-1.45.355C2.814 2.727 2 3.668 2 5v1H0v1h2v1H0v1h2v1H0v1h2s-.016.707.355 1.445C2.727 13.187 3.668 14 5 14h1v2h1v-2h1v2h1v-2h1v2h1v-2s.707.016 1.45-.355C13.186 13.273 14 12.332 14 11v-1h2V9h-2V8h2V7h-2V6h2V5h-2c0-1.332-.813-2.273-1.55-2.645C11.706 1.985 11 2 11 2h-1V0H9v2H8V0H7v2H6V0zm0 4h6c.555 0 1 .445 1 1v6c0 .555-.445 1-1 1H5c-.555 0-1-.445-1-1V5c0-.555.445-1 1-1zm0 1v6h6V5z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#222"><path d="M5 5h6v6H5zm8 0h3v1h-3zm0 2h3v1h-3zm0 2h3v1h-3zM0 6h3v1H0zm0 2h3v1H0zm0 2h3v1H0zM5 0h1v3H5zm2 0h1v3H7zm2 0h1v3H9zm1 13h1v3h-1zm-2 0h1v3H8zm-2 0h1v3H6zm0 0"/><path d="M5 2C3.355 2 2 3.355 2 5v6c0 1.645 1.355 3 3 3h6c1.645 0 3-1.355 3-3V5c0-1.645-1.355-3-3-3zm0 2h6c.57 0 1 .43 1 1v6c0 .57-.43 1-1 1H5c-.57 0-1-.43-1-1V5c0-.57.43-1 1-1m0 0"/></g></svg>

Before

Width:  |  Height:  |  Size: 492 B

After

Width:  |  Height:  |  Size: 437 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#2e3434"><path d="M8 0c-.234 0-.459.012-.688.031-1.832 1.216-1.77 3.335-.859 5.405A3 3 0 0 1 8 5a3 3 0 0 1 .418.031c.919-2.154 2.91-3.702 3.55-3.968A7.955 7.955 0 0 0 8 0zM4 1.094a7.982 7.982 0 0 0-3.219 3.5c.12 2.215 1.974 3.23 4.225 3.472A3 3 0 0 1 5 8a3 3 0 0 1 .646-1.85C4.248 4.28 3.912 1.787 4 1.094zm9.156 1.968c-1.423-.044-2.706.924-3.719 2.305a3 3 0 0 1 1.35 1.528C13.127 6.624 15.48 7.587 16 8a7.956 7.956 0 0 0-1.438-4.563 3.386 3.386 0 0 0-1.406-.374zm-2.158 4.872L11 8a3 3 0 0 1-.646 1.85c1.398 1.87 1.734 4.363 1.646 5.056a7.982 7.982 0 0 0 3.219-3.5c-.12-2.214-1.972-3.228-4.221-3.472zM0 8c0 1.698.536 3.268 1.438 4.563 1.973.987 3.79-.113 5.128-1.936A3 3 0 0 1 5.22 9.105C2.877 9.38.52 8.413 0 8zm9.547 2.564A3 3 0 0 1 8 11a3 3 0 0 1-.418-.031c-.919 2.154-2.91 3.702-3.55 3.969a7.955 7.955 0 0 0 4.655 1.03c1.833-1.215 1.772-3.334.86-5.404z"/><circle cx="8" cy="8" r="2"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#222"><path d="M8 0c-.234 0-.459.012-.688.031-1.832 1.216-1.77 3.335-.859 5.405A3 3 0 0 1 8 5a3 3 0 0 1 .418.031c.919-2.154 2.91-3.702 3.55-3.968A7.96 7.96 0 0 0 8 0M4 1.094a8 8 0 0 0-3.219 3.5c.12 2.215 1.974 3.23 4.225 3.472L5 8a3 3 0 0 1 .646-1.85C4.248 4.28 3.912 1.787 4 1.094m9.156 1.968c-1.423-.044-2.706.924-3.719 2.305a3 3 0 0 1 1.35 1.528C13.127 6.624 15.48 7.587 16 8a7.96 7.96 0 0 0-1.438-4.563 3.4 3.4 0 0 0-1.406-.374zm-2.158 4.872L11 8a3 3 0 0 1-.646 1.85c1.398 1.87 1.734 4.363 1.646 5.056a8 8 0 0 0 3.219-3.5c-.12-2.214-1.972-3.228-4.221-3.472M0 8c0 1.698.536 3.268 1.438 4.563 1.973.987 3.79-.113 5.128-1.936A3 3 0 0 1 5.22 9.105C2.877 9.38.52 8.413 0 8m9.547 2.564A3 3 0 0 1 8 11a3 3 0 0 1-.418-.031c-.919 2.154-2.91 3.702-3.55 3.969a7.96 7.96 0 0 0 4.655 1.03c1.833-1.215 1.772-3.334.86-5.404"/><circle cx="8" cy="8" r="2"/></g></svg>

Before

Width:  |  Height:  |  Size: 970 B

After

Width:  |  Height:  |  Size: 926 B

View File

@ -1,15 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#222" d="M0 0v2h.086c.5 0 .914.414.914.914V16h2v-2.008h1c0 1.11.89 2 2 2h5c1.11 0 2-.89 2-2 1.66 0 3-1.34 3-3v-6c0-1.66-1.34-3-3-3H2.848A2.9 2.9 0 0 0 .086 0zm3 3.992h10c.547 0 1 .453 1 1v6c0 .547-.453 1-1 1h-2v2h-1v-2H9v2H8v-2H7v2H6v-2H3zm3 1v1H5v3h1v1h1v-1h1v1h1v-1h1v1h1v-1h1v-3h-1v-1h-1v1H9v-1H8v1H7v-1zm0 0"/></svg>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(3.38015e-16,5.5202,-5.5202,3.38015e-16,526.335,-21.2284)">
<path d="M18,18L18,8L6,8L6,12L14,12L14,24L6,24L6,41.942L14,41.942L14,50.565L6,50.565L6,68.188L14,68.188L14,84L18,84L18,76L43,76L43,83C43.262,84.981 44.621,85.866 46.73,86L76.27,86C77.384,86 78.257,85.827 78.879,85.369C79.549,84.876 80,84.108 80,83L80,76L84.83,76C88.477,76.314 90.397,75.227 91.886,73.383C93.128,71.845 94.269,69.879 94,66.83L94,18L18,18ZM14,64.188L10,64.188L10,54.565L14,54.565L14,64.188ZM14,37.942L10,37.942L10,28L14,28L14,37.942ZM76,81.05C76,81.783 75.758,82.05 74.84,82.05L48.16,82.05C46.867,82.364 46.769,81.824 47,81.05L47,76L76,76L76,81.05ZM90,65.17C90.472,69.878 88.349,72.291 83.17,72L18,72L18,22L90,22L90,65.17Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.90206e-16,4.73942,-4.73942,2.90206e-16,491.213,161.673)">
<path d="M44,66.88C54.906,66.88 63.88,57.906 63.88,47C63.88,36.094 54.906,27.12 44,27.12C33.094,27.12 24.12,36.094 24.12,47C24.131,57.902 33.098,66.869 44,66.88ZM42,62.74C37.914,62.223 34.185,60.135 31.61,56.92L37.67,52.92C38.832,54.175 40.338,55.062 42,55.47L42,62.74ZM46,62.74L46,55.46C47.653,55.082 49.159,54.227 50.33,53L56.39,57C53.801,60.185 50.074,62.244 46,62.74ZM58.06,39.65C60.332,43.951 60.484,49.072 58.47,53.5L52.34,49.5C52.582,48.689 52.707,47.847 52.71,47C52.712,45.831 52.474,44.673 52.01,43.6L58.06,39.65ZM46,31.26C49.762,31.74 53.23,33.554 55.77,36.37L49.63,40.37C48.581,39.483 47.337,38.856 46,38.54L46,31.26ZM48.71,47C48.71,49.584 46.584,51.71 44,51.71C41.416,51.71 39.29,49.584 39.29,47C39.29,44.416 41.416,42.29 44,42.29C46.582,42.295 48.705,44.418 48.71,47ZM42,31.26L42,38.53C40.664,38.854 39.421,39.484 38.37,40.37L32.23,36.37C34.77,33.554 38.238,31.74 42,31.26ZM36,43.6C35.536,44.673 35.298,45.831 35.3,47C35.304,47.843 35.429,48.682 35.67,49.49L29.53,53.49C27.53,49.059 27.681,43.945 29.94,39.64L36,43.6Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.65262e-16,4.33206,-4.33206,2.65262e-16,472.204,-12.7952)">
<g>
<path d="M44,66.88C54.906,66.88 63.88,57.906 63.88,47C63.88,36.094 54.906,27.12 44,27.12C33.094,27.12 24.12,36.094 24.12,47C24.131,57.902 33.098,66.869 44,66.88ZM42,62.74C37.914,62.223 34.185,60.135 31.61,56.92L37.67,52.92C38.832,54.175 40.338,55.062 42,55.47L42,62.74ZM46,62.74L46,55.46C47.653,55.082 49.159,54.227 50.33,53L56.39,57C53.801,60.185 50.074,62.244 46,62.74ZM58.06,39.65C60.332,43.951 60.484,49.072 58.47,53.5L52.34,49.5C52.582,48.689 52.707,47.847 52.71,47C52.712,45.831 52.474,44.673 52.01,43.6L58.06,39.65ZM46,31.26C49.762,31.74 53.23,33.554 55.77,36.37L49.63,40.37C48.581,39.483 47.337,38.856 46,38.54L46,31.26ZM48.71,47C48.71,49.584 46.584,51.71 44,51.71C41.416,51.71 39.29,49.584 39.29,47C39.29,44.416 41.416,42.29 44,42.29C46.582,42.295 48.705,44.418 48.71,47ZM42,31.26L42,38.53C40.664,38.854 39.421,39.484 38.37,40.37L32.23,36.37C34.77,33.554 38.238,31.74 42,31.26ZM36,43.6C35.536,44.673 35.298,45.831 35.3,47C35.304,47.843 35.429,48.682 35.67,49.49L29.53,53.49C27.53,49.059 27.681,43.945 29.94,39.64L36,43.6Z" style="fill-rule:nonzero;"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 395 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#2e3436"><path d="M3 2C1.34 2 0 3.34 0 5v4c0 1.66 1.34 3 3 3h10c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3zm0 2h10c.555 0 1 .445 1 1v4c0 .555-.445 1-1 1H3c-.555 0-1-.445-1-1V5c0-.555.445-1 1-1z"/><path fill-opacity=".5" d="M4 5h2v4H4zm3 0h2v4H7zm3 0h2v4h-2z"/><path d="M2 10h12v4H2z"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#222"><path d="M3 2C1.34 2 0 3.34 0 5v4c0 1.66 1.34 3 3 3h10c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3zm0 2h10c.555 0 1 .445 1 1v4c0 .555-.445 1-1 1H3c-.555 0-1-.445-1-1V5c0-.555.445-1 1-1"/><path fill-opacity=".5" d="M4 5h2v4H4zm3 0h2v4H7zm3 0h2v4h-2z"/><path d="M2 10h12v4H2z"/></g></svg>

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 358 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#2e3436"><path fill-opacity=".349" d="M12 1a1 1 0 0 1 .707.293l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 1 1-1.414-1.414L12.586 6H5c-.55 0-1-.45-1-1s.45-1 1-1h7.586l-1.293-1.293A1 1 0 0 1 12 1zm0 0"/><path d="M4 15a1 1 0 0 1-.707-.293l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 1 1 1.414 1.414L3.414 10H11c.55 0 1 .45 1 1s-.45 1-1 1H3.414l1.293 1.293A1 1 0 0 1 4 15zm0 0"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#222"><path fill-opacity=".349" d="M12 1a1 1 0 0 1 .707.293l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 1 1-1.414-1.414L12.586 6H5c-.55 0-1-.45-1-1s.45-1 1-1h7.586l-1.293-1.293A1 1 0 0 1 12 1m0 0"/><path d="M4 15a1 1 0 0 1-.707-.293l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 1 1 1.414 1.414L3.414 10H11c.55 0 1 .45 1 1s-.45 1-1 1H3.414l1.293 1.293A1 1 0 0 1 4 15m0 0"/></g></svg>

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 431 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#2e3436" d="M12 1a1 1 0 0 0-.707 1.707L12.586 4H5c-.55 0-1 .45-1 1s.45 1 1 1h7.586l-1.293 1.293a1 1 0 1 0 1.414 1.414l3-3a1 1 0 0 0 0-1.414l-3-3A1 1 0 0 0 12 1zM4 7a.993.993 0 0 0-.707.293l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 1 0 1.414-1.414L3.414 12H11c.55 0 1-.45 1-1s-.45-1-1-1H3.414l1.293-1.293A1 1 0 0 0 4 7zm0 0"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#222" d="M12 1a1 1 0 0 0-.707 1.707L12.586 4H5c-.55 0-1 .45-1 1s.45 1 1 1h7.586l-1.293 1.293a1 1 0 1 0 1.414 1.414l3-3a1 1 0 0 0 0-1.414l-3-3A1 1 0 0 0 12 1M4 7a1 1 0 0 0-.707.293l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 1 0 1.414-1.414L3.414 12H11c.55 0 1-.45 1-1s-.45-1-1-1H3.414l1.293-1.293A1 1 0 0 0 4 7m0 0"/></svg>

Before

Width:  |  Height:  |  Size: 397 B

After

Width:  |  Height:  |  Size: 387 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#2e3436"><path d="M12 1a1 1 0 0 1 .707.293l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 1 1-1.414-1.414L12.586 6H5c-.55 0-1-.45-1-1s.45-1 1-1h7.586l-1.293-1.293A1 1 0 0 1 12 1zm0 0"/><path fill-opacity=".349" d="M4 15a1 1 0 0 1-.707-.293l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 1 1 1.414 1.414L3.414 10H11c.55 0 1 .45 1 1s-.45 1-1 1H3.414l1.293 1.293A1 1 0 0 1 4 15zm0 0"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#222"><path d="M12 1a1 1 0 0 1 .707.293l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 1 1-1.414-1.414L12.586 6H5c-.55 0-1-.45-1-1s.45-1 1-1h7.586l-1.293-1.293A1 1 0 0 1 12 1m0 0"/><path fill-opacity=".349" d="M4 15a1 1 0 0 1-.707-.293l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 1 1 1.414 1.414L3.414 10H11c.55 0 1 .45 1 1s-.45 1-1 1H3.414l1.293 1.293A1 1 0 0 1 4 15m0 0"/></g></svg>

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 431 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#2e3436" d="M4 0C2.355 0 1 1.355 1 3v10c0 1.645 1.355 3 3 3h8c1.645 0 3-1.355 3-3V3c0-1.645-1.355-3-3-3zm0 2h8c.57 0 1 .43 1 1v9c0 .57-.43 1-1 1H4c-.555 0-1-.445-1-1V3c0-.555.445-1 1-1zm4 1C5.79 3 4 4.79 4 7v4h4c2.5 0 4-1.79 4-4s-1.79-4-4-4zm0 2a2 2 0 1 1-2 2 2 2 0 0 1 2-2zm0 0"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#222" d="M4 0C2.355 0 1 1.355 1 3v10c0 1.645 1.355 3 3 3h8c1.645 0 3-1.355 3-3V3c0-1.645-1.355-3-3-3zm0 2h8c.57 0 1 .43 1 1v9c0 .57-.43 1-1 1H4c-.555 0-1-.445-1-1V3c0-.555.445-1 1-1m4 1C5.79 3 4 4.79 4 7v4h4c2.5 0 4-1.79 4-4s-1.79-4-4-4m0 2a2 2 0 1 1-2 2 2 2 0 0 1 2-2m0 0"/></svg>

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 356 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#2e3436" d="M3 0C1.34 0 0 1.34 0 3v7c0 1.66 1.34 3 3 3h10c1.66 0 3-1.34 3-3V3c0-1.66-1.34-3-3-3zm0 2h10c.555 0 1 .445 1 1v7c0 .555-.445 1-1 1H3c-.555 0-1-.445-1-1V3c0-.555.445-1 1-1zm2 12a2 2 0 0 0-2 2h10a2 2 0 0 0-2-2zm0 0"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#222" d="M3 0C1.34 0 0 1.34 0 3v7c0 1.66 1.34 3 3 3h10c1.66 0 3-1.34 3-3V3c0-1.66-1.34-3-3-3zm0 2h10c.555 0 1 .445 1 1v7c0 .555-.445 1-1 1H3c-.555 0-1-.445-1-1V3c0-.555.445-1 1-1m2 12a2 2 0 0 0-2 2h10a2 2 0 0 0-2-2zm0 0"/></svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 303 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#2e3434" d="M8 0C6.338 0 5 1.338 5 3v6c0 .116.007.23.02.342A4 4 0 0 0 4 12a4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-1.018-2.666A3.05 3.05 0 0 0 11 9V3c0-1.662-1.338-3-3-3zm0 2c.554 0 1 .446 1 1v1H8v1h1v1H8v1h1v1H8v1h1v1.27a2 2 0 0 1 .316.23 2 2 0 0 1 .082.082 2 2 0 0 1 .182.203 2 2 0 0 1 .08.117 2 2 0 0 1 .125.215 2 2 0 0 1 .063.135A2 2 0 0 1 10 12a2 2 0 0 1-2 2 2 2 0 0 1-2-2 2 2 0 0 1 .15-.732 2 2 0 0 1 .07-.155 2 2 0 0 1 .12-.205 2 2 0 0 1 .082-.119 2 2 0 0 1 .197-.219 2 2 0 0 1 .06-.06A2 2 0 0 1 7 10.277V3c0-.554.446-1 1-1zm0 9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#222" d="M8 0C6.338 0 5 1.338 5 3v6q0 .174.02.342A4 4 0 0 0 4 12a4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-1.018-2.666A3 3 0 0 0 11 9V3c0-1.662-1.338-3-3-3m0 2c.554 0 1 .446 1 1v1H8v1h1v1H8v1h1v1H8v1h1v1.27a2 2 0 0 1 .316.23l.082.082a2 2 0 0 1 .182.203 2 2 0 0 1 .08.117 2 2 0 0 1 .125.215 2 2 0 0 1 .063.135A2 2 0 0 1 10 12a2 2 0 0 1-2 2 2 2 0 0 1-2-2 2 2 0 0 1 .15-.732 2 2 0 0 1 .07-.155 2 2 0 0 1 .12-.205 2 2 0 0 1 .082-.119 2 2 0 0 1 .197-.219l.06-.06A2 2 0 0 1 7 10.277V3c0-.554.446-1 1-1m0 9a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1"/></svg>

Before

Width:  |  Height:  |  Size: 674 B

After

Width:  |  Height:  |  Size: 635 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M4 .004v3H2.977A1.97 1.97 0 0 0 1 4.977v2.05c0 .13.012.254.035.375A4.99 4.99 0 0 0 6 13.004 3.015 3.015 0 0 0 9 16h2c1.094 0 2-.906 2-2h1v2h2v-2c0-1.094-.906-2-2-2h-1c-1.094 0-2 .906-2 2H9c-.563 0-1-.434-1-.996a4.99 4.99 0 0 0 4.965-5.602A1.97 1.97 0 0 0 13 7.027v-2.05a1.97 1.97 0 0 0-1.977-1.973H10v-3H8v3H6v-3Zm0 0" style="stroke:none;fill-rule:nonzero;fill:#2e3434;fill-opacity:1"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#222" d="M4 .004v3H2.977A1.97 1.97 0 0 0 1 4.977v2.05q0 .194.035.375A4.99 4.99 0 0 0 6 13.004 3.015 3.015 0 0 0 9 16h2c1.094 0 2-.906 2-2h1v2h2v-2c0-1.094-.906-2-2-2h-1c-1.094 0-2 .906-2 2H9c-.563 0-1-.434-1-.996a4.99 4.99 0 0 0 4.965-5.602A2 2 0 0 0 13 7.027v-2.05a1.97 1.97 0 0 0-1.977-1.973H10v-3H8v3H6v-3Zm0 0"/></svg>

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 397 B

View File

@ -9,9 +9,10 @@
"settings-schema": "org.gnome.shell.extensions.vitals", "settings-schema": "org.gnome.shell.extensions.vitals",
"shell-version": [ "shell-version": [
"45", "45",
"46" "46",
"47"
], ],
"url": "https://github.com/corecoding/Vitals", "url": "https://github.com/corecoding/Vitals",
"uuid": "Vitals@CoreCoding.com", "uuid": "Vitals@CoreCoding.com",
"version": 66 "version": 69
} }

View File

@ -1222,6 +1222,7 @@
<item translatable="yes">BAT0</item> <item translatable="yes">BAT0</item>
<item translatable="yes">BAT1</item> <item translatable="yes">BAT1</item>
<item translatable="yes">BAT2</item> <item translatable="yes">BAT2</item>
<item translatable="yes">BATT</item>
<item translatable="yes">CMB0</item> <item translatable="yes">CMB0</item>
<item translatable="yes">macsmc-battery</item> <item translatable="yes">macsmc-battery</item>
</items> </items>

View File

@ -140,6 +140,8 @@ export const Sensors = GObject.registerClass({
let used = total - avail let used = total - avail
let utilized = used / total; let utilized = used / total;
let swapUsed = swapTotal - swapFree
let swapUtilized = swapUsed / swapTotal;
this._returnValue(callback, 'Usage', utilized, 'memory', 'percent'); this._returnValue(callback, 'Usage', utilized, 'memory', 'percent');
this._returnValue(callback, 'memory', utilized, 'memory-group', 'percent'); this._returnValue(callback, 'memory', utilized, 'memory-group', 'percent');
@ -148,7 +150,10 @@ export const Sensors = GObject.registerClass({
this._returnValue(callback, 'Allocated', used, 'memory', 'memory'); this._returnValue(callback, 'Allocated', used, 'memory', 'memory');
this._returnValue(callback, 'Cached', cached, 'memory', 'memory'); this._returnValue(callback, 'Cached', cached, 'memory', 'memory');
this._returnValue(callback, 'Free', memFree, 'memory', 'memory'); this._returnValue(callback, 'Free', memFree, 'memory', 'memory');
this._returnValue(callback, 'Swap', swapTotal - swapFree, 'memory', 'memory'); this._returnValue(callback, 'Swap Total', swapTotal, 'memory', 'memory');
this._returnValue(callback, 'Swap Free', swapFree, 'memory', 'memory');
this._returnValue(callback, 'Swap Used', swapUsed, 'memory', 'memory');
this._returnValue(callback, 'Swap Usage', swapUtilized, 'memory', 'percent');
}).catch(err => { }); }).catch(err => { });
} }
@ -373,9 +378,11 @@ export const Sensors = GObject.registerClass({
// addresses issue #161 // addresses issue #161
let battery_key = 'BAT'; // BAT0, BAT1 and BAT2 let battery_key = 'BAT'; // BAT0, BAT1 and BAT2
if (battery_slot == 3) { if (battery_slot == 3) {
battery_slot = 'T';
} else if (battery_slot == 4) {
battery_key = 'CMB'; // CMB0 battery_key = 'CMB'; // CMB0
battery_slot = 0; battery_slot = 0;
} else if (battery_slot == 4) { } else if (battery_slot == 5) {
battery_key = 'macsmc-battery'; // supports Asahi linux battery_key = 'macsmc-battery'; // supports Asahi linux
battery_slot = ''; battery_slot = '';
} }

View File

@ -37,6 +37,7 @@ Gio._promisify(GdkPixbuf.Pixbuf, 'get_file_info_async');
Gio._promisify(GdkPixbuf.Pixbuf, 'new_from_stream_at_scale_async', Gio._promisify(GdkPixbuf.Pixbuf, 'new_from_stream_at_scale_async',
'new_from_stream_finish'); 'new_from_stream_finish');
Gio._promisify(St.IconInfo.prototype, 'load_symbolic_async'); Gio._promisify(St.IconInfo.prototype, 'load_symbolic_async');
Gio._promisify(Gio.DBusConnection.prototype, 'call');
const MAX_UPDATE_FREQUENCY = 30; // In ms const MAX_UPDATE_FREQUENCY = 30; // In ms
const FALLBACK_ICON_NAME = 'image-loading-symbolic'; const FALLBACK_ICON_NAME = 'image-loading-symbolic';
@ -199,10 +200,10 @@ class AppIndicatorProxy extends DBusProxy {
})); }));
} }
_onSignal(...args) { _onSignal(sender, signal, ...args) {
this._onSignalAsync(...args).catch(e => { this._onSignalAsync(sender, signal, ...args).catch(e => {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
logError(e); logError(e, `Error while processing signal '${signal}'`);
}); });
} }
@ -311,7 +312,10 @@ class AppIndicatorProxy extends DBusProxy {
} catch (e) { } catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
// the property may not even exist, silently ignore it // the property may not even exist, silently ignore it
Util.Logger.debug(`While refreshing property ${propertyName}: ${e}`); Util.Logger.debug(`Error when calling 'Get(${propertyName})' ` +
`in ${this.gName}, ${this.gObjectPath}, ` +
`org.freedesktop.DBus.Properties, ${this.gInterfaceName} ` +
`while refreshing property ${propertyName}: ${e}`);
this.set_cached_property(propertyName, null); this.set_cached_property(propertyName, null);
this._cancellables.delete(propertyName); this._cancellables.delete(propertyName);
delete this._changedProperties[propertyName]; delete this._changedProperties[propertyName];
@ -435,6 +439,25 @@ export class AppIndicator extends Signals.EventEmitter {
} }
} }
// We try to lookup the activate method to see if the app supports it
try {
const introspectionVariant = await this._proxy.gConnection.call(
this._proxy.gNameOwner, this._proxy.gObjectPath,
'org.freedesktop.DBus.Introspectable', 'Introspect', null, null,
Gio.DBusCallFlags.NONE, -1, cancellable);
const [introspectionXml] = introspectionVariant.deep_unpack();
const nodeInfo = Gio.DBusNodeInfo.new_for_xml(introspectionXml);
const interfaceInfo = nodeInfo.lookup_interface(this._proxy.gInterfaceName);
this.supportsActivation = !!interfaceInfo.lookup_method('Activate');
this._hasAyatanaSecondaryActivate =
!!interfaceInfo.lookup_method('XAyatanaSecondaryActivate');
} catch (e) {
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
Util.Logger.debug(
`${this.uniqueId}, check for Activation support: ${e.message}`);
}
}
try { try {
this._commandLine = await Util.getProcessName(this.busName, this._commandLine = await Util.getProcessName(this.busName,
cancellable, GLib.PRIORITY_LOW); cancellable, GLib.PRIORITY_LOW);

View File

@ -130,6 +130,11 @@ export class DbusMenuItem extends Signals.EventEmitter {
this.emit('property-changed', prop, this.propertyGetVariant(prop)); this.emit('property-changed', prop, this.propertyGetVariant(prop));
} }
resetProperties() {
Object.entries(PropertyStore.DefaultValues).forEach(([prop, value]) =>
this.propertySet(prop, value));
}
getChildrenIds() { getChildrenIds() {
return this._children_ids.concat(); // clone it! return this._children_ids.concat(); // clone it!
} }
@ -297,6 +302,8 @@ export const DBusClient = GObject.registerClass({
if (!item) if (!item)
return; return;
item.resetProperties();
for (const [prop, value] of Object.entries(properties)) for (const [prop, value] of Object.entries(properties))
item.propertySet(prop, value); item.propertySet(prop, value);
}); });
@ -495,6 +502,9 @@ export const DBusClient = GObject.registerClass({
// we don't need to cache and burst-send that since it will not happen that frequently // we don't need to cache and burst-send that since it will not happen that frequently
async sendAboutToShow(id) { async sendAboutToShow(id) {
if (this._hasAboutToShow === false)
return;
/* Some indicators (you, dropbox!) don't use the right signature /* Some indicators (you, dropbox!) don't use the right signature
* and don't return a boolean, so we need to support both cases */ * and don't return a boolean, so we need to support both cases */
try { try {
@ -507,6 +517,13 @@ export const DBusClient = GObject.registerClass({
ret.is_of_type(new GLib.VariantType('()'))) ret.is_of_type(new GLib.VariantType('()')))
this._requestLayoutUpdate(); this._requestLayoutUpdate();
} catch (e) { } catch (e) {
Util.Logger.debug('Error when calling \'AboutToShow()\' in ' +
`${this.gName}, ${this.gObjectPath}, ${this.gInterfaceName}`);
if (e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD) ||
e.matches(Gio.DBusError, Gio.DBusError.FAILED)) {
this._hasAboutToShow = false;
return;
}
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
logError(e); logError(e);
} }

View File

@ -22,7 +22,7 @@ import * as TrayIconsManager from './trayIconsManager.js';
import * as Util from './util.js'; import * as Util from './util.js';
import {SettingsManager} from './settingsManager.js'; import {SettingsManager} from './settingsManager.js';
export default class DashToDockExtension extends Extension.Extension { export default class AppIndicatorExtension extends Extension.Extension {
constructor(...args) { constructor(...args) {
super(...args); super(...args);

View File

@ -49,7 +49,7 @@ export class IconCache {
} }
const oldIcon = this._cache.get(id); const oldIcon = this._cache.get(id);
if (!oldIcon || !oldIcon.equals(icon)) { if (!oldIcon || !oldIcon.equal(icon)) {
Util.Logger.debug(`IconCache: adding ${id}: ${icon}`); Util.Logger.debug(`IconCache: adding ${id}: ${icon}`);
this._cache.set(id, icon); this._cache.set(id, icon);
} else { } else {

View File

@ -276,15 +276,15 @@ class IndicatorStatusIcon extends BaseStatusIcon {
yAlign: Clutter.ActorAlign.CENTER, yAlign: Clutter.ActorAlign.CENTER,
}); });
this._label = new St.Label(); this._label = new St.Label();
this._labelBin.add_actor(this._label); Util.addActor(this._labelBin, this._label);
this._box.add_actor(this._labelBin); Util.addActor(this._box, this._labelBin);
} }
this._label.set_text(label); this._label.set_text(label);
if (!this._box.contains(this._labelBin)) if (!this._box.contains(this._labelBin))
this._box.add_actor(this._labelBin); // FIXME: why is it suddenly necessary? Util.addActor(this._box, this._labelBin); // FIXME: why is it suddenly necessary?
} else if (this._label) { } else if (this._label) {
this._labelBin.destroy_all_children(); this._labelBin.destroy_all_children();
this._box.remove_actor(this._labelBin); Util.removeActor(this._box, this._labelBin);
this._labelBin.destroy(); this._labelBin.destroy();
delete this._labelBin; delete this._labelBin;
delete this._label; delete this._label;
@ -420,13 +420,6 @@ class IndicatorStatusIcon extends BaseStatusIcon {
return Clutter.EVENT_PROPAGATE; return Clutter.EVENT_PROPAGATE;
} }
vfunc_button_release_event(event) {
if (!this._indicator.supportsActivation)
return this._maybeHandleDoubleClick(event);
return Clutter.EVENT_PROPAGATE;
}
vfunc_scroll_event(event) { vfunc_scroll_event(event) {
// Since Clutter 1.10, clutter will always send a smooth scrolling event // Since Clutter 1.10, clutter will always send a smooth scrolling event
// with explicit deltas, no matter what input device is used // with explicit deltas, no matter what input device is used

View File

@ -5,9 +5,11 @@
"name": "AppIndicator and KStatusNotifierItem Support", "name": "AppIndicator and KStatusNotifierItem Support",
"settings-schema": "org.gnome.shell.extensions.appindicator", "settings-schema": "org.gnome.shell.extensions.appindicator",
"shell-version": [ "shell-version": [
"45" "45",
"46",
"47"
], ],
"url": "https://github.com/ubuntu/gnome-shell-extension-appindicator", "url": "https://github.com/ubuntu/gnome-shell-extension-appindicator",
"uuid": "appindicatorsupport@rgcjonas.gmail.com", "uuid": "appindicatorsupport@rgcjonas.gmail.com",
"version": 57 "version": 59
} }

View File

@ -45,6 +45,19 @@ export class TrayIconsManager extends Signals.EventEmitter {
this._changedId = SettingsManager.getDefaultGSettings().connect( this._changedId = SettingsManager.getDefaultGSettings().connect(
'changed::legacy-tray-enabled', () => this._toggle()); 'changed::legacy-tray-enabled', () => this._toggle());
// On theme changed, need to update the bg color to match style,
// This may not be required anymore on newer shell versions that use
// ARGBA visuals.
this._styleChangedID = Main.panel.connect('style-changed', () => {
const panelBgColor = this._getPanelBgColor();
const {bgColor} = this._tray ?? {bgColor: null};
if (bgColor === panelBgColor || bgColor?.equal(panelBgColor))
return;
this._disable();
this._toggle();
});
this._toggle(); this._toggle();
} }
@ -55,11 +68,16 @@ export class TrayIconsManager extends Signals.EventEmitter {
this._disable(); this._disable();
} }
_getPanelBgColor() {
return Main.panel?.get_parent()
? Main.panel.get_theme_node()?.get_background_color() : null;
}
_enable() { _enable() {
if (this._tray) if (this._tray)
return; return;
this._tray = new Shell.TrayManager(); this._tray = new Shell.TrayManager({bgColor: this._getPanelBgColor()});
Util.connectSmart(this._tray, 'tray-icon-added', this, this.onTrayIconAdded); Util.connectSmart(this._tray, 'tray-icon-added', this, this.onTrayIconAdded);
Util.connectSmart(this._tray, 'tray-icon-removed', this, this.onTrayIconRemoved); Util.connectSmart(this._tray, 'tray-icon-removed', this, this.onTrayIconRemoved);
@ -71,14 +89,8 @@ export class TrayIconsManager extends Signals.EventEmitter {
return; return;
IndicatorStatusIcon.getTrayIcons().forEach(i => i.destroy()); IndicatorStatusIcon.getTrayIcons().forEach(i => i.destroy());
if (this._tray.unmanage_screen) { this._tray.unmanage_screen();
this._tray.unmanage_screen(); this._tray = null;
this._tray = null;
} else {
// FIXME: This is very ugly, but it's needed by old shell versions
this._tray = null;
imports.system.gc(); // force finalizing tray to unmanage screen
}
} }
onTrayIconAdded(_tray, icon) { onTrayIconAdded(_tray, icon) {
@ -98,6 +110,7 @@ export class TrayIconsManager extends Signals.EventEmitter {
destroy() { destroy() {
this.emit('destroy'); this.emit('destroy');
SettingsManager.getDefaultGSettings().disconnect(this._changedId); SettingsManager.getDefaultGSettings().disconnect(this._changedId);
Main.panel.disconnect(this._styleChangedID);
this._disable(); this._disable();
trayIconsManager = null; trayIconsManager = null;
} }

View File

@ -19,8 +19,6 @@ import GLib from 'gi://GLib';
import GObject from 'gi://GObject'; import GObject from 'gi://GObject';
import St from 'gi://St'; import St from 'gi://St';
const ByteArray = imports.byteArray;
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Config from 'resource:///org/gnome/shell/misc/config.js'; import * as Config from 'resource:///org/gnome/shell/misc/config.js';
import * as Signals from 'resource:///org/gnome/shell/misc/signals.js'; import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
@ -102,7 +100,8 @@ export async function getProcessName(connectionName, cancellable = null,
const cmdFile = Gio.File.new_for_path(`/proc/${pid}/cmdline`); const cmdFile = Gio.File.new_for_path(`/proc/${pid}/cmdline`);
const inputStream = await cmdFile.read_async(priority, cancellable); const inputStream = await cmdFile.read_async(priority, cancellable);
const bytes = await inputStream.read_bytes_async(2048, priority, cancellable); const bytes = await inputStream.read_bytes_async(2048, priority, cancellable);
return ByteArray.toString(bytes.toArray().map(v => !v ? 0x20 : v)); const textDecoder = new TextDecoder();
return textDecoder.decode(bytes.toArray().map(v => !v ? 0x20 : v));
} }
export async function* introspectBusObject(bus, name, cancellable, export async function* introspectBusObject(bus, name, cancellable,
@ -376,6 +375,20 @@ export function tryCleanupOldIndicators() {
new Set(indicators).forEach(i => i.destroy()); new Set(indicators).forEach(i => i.destroy());
} }
export function addActor(obj, actor) {
if (obj.add_actor)
obj.add_actor(actor);
else
obj.add_child(actor);
}
export function removeActor(obj, actor) {
if (obj.remove_actor)
obj.remove_actor(actor);
else
obj.remove_child(actor);
}
export const CancellableChild = GObject.registerClass({ export const CancellableChild = GObject.registerClass({
Properties: { Properties: {
'parent': GObject.ParamSpec.object( 'parent': GObject.ParamSpec.object(

View File

@ -11,11 +11,10 @@
"original-author": "mi-jan-sena@proton.me", "original-author": "mi-jan-sena@proton.me",
"settings-schema": "org.gnome.shell.extensions.auto-activities", "settings-schema": "org.gnome.shell.extensions.auto-activities",
"shell-version": [ "shell-version": [
"45", "47"
"46"
], ],
"url": "https://github.com/CleoMenezesJr/auto-activities", "url": "https://github.com/CleoMenezesJr/auto-activities",
"uuid": "auto-activities@CleoMenezesJr.github.io", "uuid": "auto-activities@CleoMenezesJr.github.io",
"version": 13, "version": 14,
"version-name": "46.1" "version-name": "47.0"
} }

View File

@ -1,11 +1,22 @@
import Shell from 'gi://Shell'; import Shell from 'gi://Shell';
import Clutter from 'gi://Clutter'; import Clutter from 'gi://Clutter';
import Cogl from 'gi://Cogl';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { adjustAnimationTime } from 'resource:///org/gnome/shell/misc/animationUtils.js';
import { PaintSignals } from '../effects/paint_signals.js'; import { PaintSignals } from '../conveniences/paint_signals.js';
// TODO drop Tweener in favour of Clutter's `ease` (will need to extend the blur effect for it)
const Tweener = imports.tweener.tweener; const Tweener = imports.tweener.tweener;
const transparent = Clutter.Color.from_pixel(0x00000000); // TODO: Drop GNOME 46 backwards compatibility
const transparent = Clutter.Color ?
Clutter.Color.from_pixel(0x00000000) :
new Cogl.Color({
red: 0,
green: 0,
blue: 0,
alpha: 0
});
const FOLDER_DIALOG_ANIMATION_TIME = 200; const FOLDER_DIALOG_ANIMATION_TIME = 200;
const DIALOGS_STYLES = [ const DIALOGS_STYLES = [
@ -43,7 +54,7 @@ let _zoomAndFadeIn = function () {
{ {
radius: sigma * 2, radius: sigma * 2,
brightness: brightness, brightness: brightness,
time: FOLDER_DIALOG_ANIMATION_TIME / 1000, time: adjustAnimationTime(FOLDER_DIALOG_ANIMATION_TIME / 1000),
transition: 'easeOutQuad' transition: 'easeOutQuad'
} }
); );
@ -87,7 +98,7 @@ let _zoomAndFadeOut = function () {
{ {
radius: 0, radius: 0,
brightness: 1.0, brightness: 1.0,
time: FOLDER_DIALOG_ANIMATION_TIME / 1000, time: adjustAnimationTime(FOLDER_DIALOG_ANIMATION_TIME / 1000),
transition: 'easeInQuad' transition: 'easeInQuad'
} }
); );
@ -120,6 +131,8 @@ let _zoomAndFadeOut = function () {
export const AppFoldersBlur = class AppFoldersBlur { export const AppFoldersBlur = class AppFoldersBlur {
// we do not use the effects manager and dummy pipelines here because we
// really want to manage our sigma value ourself during the transition
constructor(connections, settings, _) { constructor(connections, settings, _) {
this.connections = connections; this.connections = connections;
this.paint_signals = new PaintSignals(connections); this.paint_signals = new PaintSignals(connections);
@ -129,12 +142,8 @@ export const AppFoldersBlur = class AppFoldersBlur {
enable() { enable() {
this._log("blurring appfolders"); this._log("blurring appfolders");
brightness = this.settings.appfolder.CUSTOMIZE brightness = this.settings.appfolder.BRIGHTNESS;
? this.settings.appfolder.BRIGHTNESS sigma = this.settings.appfolder.SIGMA;
: this.settings.BRIGHTNESS;
sigma = this.settings.appfolder.CUSTOMIZE
? this.settings.appfolder.SIGMA
: this.settings.SIGMA;
let appDisplay = Main.overview._overview.controls._appDisplay; let appDisplay = Main.overview._overview.controls._appDisplay;
@ -143,15 +152,15 @@ export const AppFoldersBlur = class AppFoldersBlur {
} }
this.connections.connect( this.connections.connect(
appDisplay, 'view-loaded', this.blur_appfolders.bind(this) appDisplay, 'view-loaded', _ => this.blur_appfolders()
); );
} }
blur_appfolders() { blur_appfolders() {
let appDisplay = Main.overview._overview.controls._appDisplay; let appDisplay = Main.overview._overview.controls._appDisplay;
if (this.settings.HACKS_LEVEL === 1 || this.settings.HACKS_LEVEL === 2) if (this.settings.HACKS_LEVEL === 1)
this._log(`appfolders hack level ${this.settings.HACKS_LEVEL}`); this._log("appfolders hack level 1");
appDisplay._folderIcons.forEach(icon => { appDisplay._folderIcons.forEach(icon => {
icon._ensureFolderDialog(); icon._ensureFolderDialog();
@ -197,7 +206,7 @@ export const AppFoldersBlur = class AppFoldersBlur {
// //
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857 // [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
if (this.settings.HACKS_LEVEL === 1 || this.settings.HACKS_LEVEL === 2) { if (this.settings.HACKS_LEVEL === 1) {
this.paint_signals.disconnect_all_for_actor(icon._dialog); this.paint_signals.disconnect_all_for_actor(icon._dialog);
this.paint_signals.connect(icon._dialog, blur_effect); this.paint_signals.connect(icon._dialog, blur_effect);
} else { } else {
@ -218,11 +227,6 @@ export const AppFoldersBlur = class AppFoldersBlur {
this.blur_appfolders(); this.blur_appfolders();
} }
// not implemented for dynamic blur
set_color(c) { }
set_noise_amount(n) { }
set_noise_lightness(l) { }
disable() { disable() {
this._log("removing blur from appfolders"); this._log("removing blur from appfolders");

View File

@ -1,24 +1,20 @@
import Shell from 'gi://Shell';
import Clutter from 'gi://Clutter';
import Meta from 'gi://Meta'; import Meta from 'gi://Meta';
import Gio from 'gi://Gio'; import Gio from 'gi://Gio';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { PaintSignals } from '../effects/paint_signals.js';
import { ApplicationsService } from '../dbus/services.js'; import { ApplicationsService } from '../dbus/services.js';
import { PaintSignals } from '../conveniences/paint_signals.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.js';
export const ApplicationsBlur = class ApplicationsBlur { export const ApplicationsBlur = class ApplicationsBlur {
constructor(connections, settings, _) { constructor(connections, settings, effects_manager) {
this.connections = connections; this.connections = connections;
this.settings = settings; this.settings = settings;
this.effects_manager = effects_manager;
this.paint_signals = new PaintSignals(connections); this.paint_signals = new PaintSignals(connections);
this.mutter_gsettings = new Gio.Settings({ schema: 'org.gnome.mutter' }); // stores every blurred meta window
this.meta_window_map = new Map();
// stores every blurred window
this.window_map = new Map();
// stores every blur actor
this.blur_actor_map = new Map();
} }
enable() { enable() {
@ -28,6 +24,8 @@ export const ApplicationsBlur = class ApplicationsBlur {
this.service = new ApplicationsService; this.service = new ApplicationsService;
this.service.export(); this.service.export();
this.mutter_gsettings = new Gio.Settings({ schema: 'org.gnome.mutter' });
// blur already existing windows // blur already existing windows
this.update_all_windows(); this.update_all_windows();
@ -38,16 +36,42 @@ export const ApplicationsBlur = class ApplicationsBlur {
(_meta_display, meta_window) => { (_meta_display, meta_window) => {
this._log("window created"); this._log("window created");
if (meta_window) { if (meta_window)
let window_actor = meta_window.get_compositor_private(); this.track_new(meta_window);
this.track_new(window_actor, meta_window); }
} );
// update window blur when focus is changed
this.focused_window_pid = null;
this.init_dynamic_opacity();
this.connections.connect(
global.display,
'focus-window',
(_meta_display, meta_window, _p0) => {
if (meta_window && meta_window.bms_pid != this.focused_window_pid)
this.set_focus_for_window(meta_window);
else if (!meta_window)
this.set_focus_for_window(null);
} }
); );
this.connect_to_overview(); this.connect_to_overview();
} }
/// Initializes the dynamic opacity for windows, without touching to the connections.
/// This is used both when enabling the component, and when changing the dynamic-opacity pref.
init_dynamic_opacity() {
if (this.settings.applications.DYNAMIC_OPACITY) {
// make the currently focused window solid
if (global.display.focus_window)
this.set_focus_for_window(global.display.focus_window);
} else {
// remove old focused window if the pref was changed
if (this.focused_window_pid)
this.set_focus_for_window(null);
}
}
/// Connect to the overview being opened/closed to force the blur being /// Connect to the overview being opened/closed to force the blur being
/// shown on every window of the workspaces viewer. /// shown on every window of the workspaces viewer.
connect_to_overview() { connect_to_overview() {
@ -58,9 +82,9 @@ export const ApplicationsBlur = class ApplicationsBlur {
// allows the blur to be shown too) // allows the blur to be shown too)
this.connections.connect( this.connections.connect(
Main.overview, 'showing', Main.overview, 'showing',
_ => this.window_map.forEach((meta_window, _pid) => { _ => this.meta_window_map.forEach((meta_window, _pid) => {
let window_actor = meta_window.get_compositor_private(); let window_actor = meta_window.get_compositor_private();
window_actor.show(); window_actor?.show();
}) })
); );
@ -69,7 +93,7 @@ export const ApplicationsBlur = class ApplicationsBlur {
this.connections.connect( this.connections.connect(
Main.overview, 'hidden', Main.overview, 'hidden',
_ => { _ => {
this.window_map.forEach((meta_window, _pid) => { this.meta_window_map.forEach((meta_window, _pid) => {
let window_actor = meta_window.get_compositor_private(); let window_actor = meta_window.get_compositor_private();
if ( if (
@ -86,7 +110,7 @@ export const ApplicationsBlur = class ApplicationsBlur {
update_all_windows() { update_all_windows() {
// remove all previously blurred windows, in the case where the // remove all previously blurred windows, in the case where the
// whitelist was changed // whitelist was changed
this.window_map.forEach(((_meta_window, pid) => { this.meta_window_map.forEach(((_meta_window, pid) => {
this.remove_blur(pid); this.remove_blur(pid);
})); }));
@ -98,82 +122,81 @@ export const ApplicationsBlur = class ApplicationsBlur {
let workspace = global.workspace_manager.get_workspace_by_index(i); let workspace = global.workspace_manager.get_workspace_by_index(i);
let windows = workspace.list_windows(); let windows = workspace.list_windows();
windows.forEach(meta_window => { windows.forEach(meta_window => this.track_new(meta_window));
let window_actor = meta_window.get_compositor_private();
// disconnect previous signals
this.connections.disconnect_all_for(window_actor);
this.track_new(window_actor, meta_window);
});
} }
} }
/// Adds the needed signals to every new tracked window, and adds blur if /// Adds the needed signals to every new tracked window, and adds blur if
/// needed. /// needed.
track_new(window_actor, meta_window) { /// Accepts only untracked meta windows (i.e no `bms_pid` set)
let pid = ("" + Math.random()).slice(2, 16); track_new(meta_window) {
// create a pid that will follow the window during its whole life
const pid = ("" + Math.random()).slice(2, 16);
meta_window.bms_pid = pid;
window_actor['blur_provider_pid'] = pid; this._log(`new window tracked, pid: ${pid}`);
meta_window['blur_provider_pid'] = pid;
// remove the blur when the window is destroyed // register the blurred window
this.connections.connect(window_actor, 'destroy', window_actor => { this.meta_window_map.set(pid, meta_window);
let pid = window_actor.blur_provider_pid;
if (this.blur_actor_map.has(pid)) {
this.remove_blur(pid);
}
this.window_map.delete(pid);
});
// update the blur when mutter-hint or wm-class is changed // update the blur when wm-class is changed
for (const prop of ['mutter-hints', 'wm-class']) { this.connections.connect(
this.connections.connect( meta_window, 'notify::wm-class',
meta_window, _ => this.check_blur(meta_window)
`notify::${prop}`, );
_ => {
let pid = meta_window.blur_provider_pid;
this._log(`${prop} changed for pid ${pid}`);
let window_actor = meta_window.get_compositor_private();
this.check_blur(pid, window_actor, meta_window);
}
);
}
// update the position and size when the window size changes // update the position and size when the window size changes
this.connections.connect(meta_window, 'size-changed', () => { this.connections.connect(
if (this.blur_actor_map.has(pid)) { meta_window, 'size-changed',
let allocation = this.compute_allocation(meta_window); _ => this.update_size(pid)
let blur_actor = this.blur_actor_map.get(pid); );
// remove the blur when the window is unmanaged
this.connections.connect(
meta_window, 'unmanaging',
_ => this.untrack_meta_window(pid)
);
this.check_blur(meta_window);
}
/// Updates the size of the blur actor associated to a meta window from its pid.
/// Accepts only tracked meta window (i.e `bms_pid` set), be it blurred or not.
update_size(pid) {
if (this.meta_window_map.has(pid)) {
const meta_window = this.meta_window_map.get(pid);
const blur_actor = meta_window.blur_actor;
if (blur_actor) {
const allocation = this.compute_allocation(meta_window);
blur_actor.x = allocation.x; blur_actor.x = allocation.x;
blur_actor.y = allocation.y; blur_actor.y = allocation.y;
blur_actor.width = allocation.width; blur_actor.width = allocation.width;
blur_actor.height = allocation.height; blur_actor.height = allocation.height;
} }
}); } else
// the pid was visibly not removed
this.check_blur(pid, window_actor, meta_window); this.untrack_meta_window(pid);
} }
/// Checks if the given actor needs to be blurred. /// Checks if the given actor needs to be blurred.
/// Accepts only tracked meta window, be it blurred or not.
/// ///
/// In order to be blurred, a window either: /// In order to be blurred, a window either:
/// - is whitelisted in the user preferences if not enable-all /// - is whitelisted in the user preferences if not enable-all
/// - is not blacklisted if enable-all /// - is not blacklisted if enable-all
/// - has a correct mutter hint, set to `blur-provider=sigma_value` check_blur(meta_window) {
check_blur(pid, window_actor, meta_window) { const window_wm_class = meta_window.get_wm_class();
let mutter_hint = meta_window.get_mutter_hints(); const enable_all = this.settings.applications.ENABLE_ALL;
let window_wm_class = meta_window.get_wm_class(); const whitelist = this.settings.applications.WHITELIST;
const blacklist = this.settings.applications.BLACKLIST;
if (window_wm_class)
this._log(`pid ${meta_window.bms_pid} associated to wm class name ${window_wm_class}`);
let enable_all = this.settings.applications.ENABLE_ALL;
let whitelist = this.settings.applications.WHITELIST;
let blacklist = this.settings.applications.BLACKLIST;
this._log(`checking blur for ${pid}`); // if we are in blacklist mode and the window is not blacklisted
// or if we are in whitelist mode and the window is whitelisted
// either the window is included in whitelist if (
if (window_wm_class !== "" window_wm_class !== ""
&& ((enable_all && !blacklist.includes(window_wm_class)) && ((enable_all && !blacklist.includes(window_wm_class))
|| (!enable_all && whitelist.includes(window_wm_class)) || (!enable_all && whitelist.includes(window_wm_class))
) )
@ -183,209 +206,114 @@ export const ApplicationsBlur = class ApplicationsBlur {
Meta.FrameType.MODAL_DIALOG Meta.FrameType.MODAL_DIALOG
].includes(meta_window.get_frame_type()) ].includes(meta_window.get_frame_type())
) { ) {
this._log(`application ${pid} listed, blurring it`); // only blur the window if it is not already done
if (!meta_window.blur_actor)
// get blur effect parameters this.create_blur_effect(meta_window);
let brightness, sigma;
if (this.settings.applications.CUSTOMIZE) {
brightness = this.settings.applications.BRIGHTNESS;
sigma = this.settings.applications.SIGMA;
} else {
brightness = this.settings.BRIGHTNESS;
sigma = this.settings.SIGMA;
}
this.update_blur(pid, window_actor, meta_window, brightness, sigma);
} }
// or blur is asked by window itself // remove blur it is not explicitly whitelisted or un-blacklisted
else if ( else if (meta_window.blur_actor)
mutter_hint != null && this.remove_blur(meta_window.bms_pid);
mutter_hint.includes("blur-provider")
) {
this._log(`application ${pid} has hint ${mutter_hint}, parsing`);
// get blur effect parameters
let [brightness, sigma] = this.parse_xprop(mutter_hint);
this.update_blur(pid, window_actor, meta_window, brightness, sigma);
}
// remove blur if the mutter hint is no longer valid, and the window
// is not explicitly whitelisted or un-blacklisted
else if (this.blur_actor_map.has(pid)) {
this.remove_blur(pid);
}
}
/// When given the xprop property, returns the brightness and sigma values
/// matching. If one of the two values is invalid, or missing, then it uses
/// default values.
///
/// An xprop property is valid if it is in one of the following formats:
///
/// blur-provider=sigma:60,brightness:0.9
/// blur-provider=s:10,brightness:0.492
/// blur-provider=b:1.0,s:16
///
/// Brightness is a floating-point between 0.0 and 1.0 included.
/// Sigma is an integer between 0 and 999 included.
///
/// If sigma is set to 0, then the blur is removed.
/// Setting "default" instead of the two values will make the
/// extension use its default value.
///
/// Note that no space can be inserted.
///
parse_xprop(property) {
// set brightness and sigma to default values
let brightness, sigma;
if (this.settings.applications.CUSTOMIZE) {
brightness = this.settings.applications.BRIGHTNESS;
sigma = this.settings.applications.SIGMA;
} else {
brightness = this.settings.BRIGHTNESS;
sigma = this.settings.SIGMA;
}
// get the argument of the property
let arg = property.match("blur-provider=(.*)");
this._log(`argument = ${arg}`);
// if argument is valid, parse it
if (arg != null) {
// verify if there is only one value: in this case, this is sigma
let maybe_sigma = parseInt(arg[1]);
if (
!isNaN(maybe_sigma) &&
maybe_sigma >= 0 &&
maybe_sigma <= 999
) {
sigma = maybe_sigma;
} else {
// perform pattern matching
let res_b = arg[1].match("(brightness|b):(default|0?1?\.[0-9]*)");
let res_s = arg[1].match("(sigma|s):(default|\\d{1,3})");
// if values are valid and not default, change them to the xprop one
if (
res_b != null && res_b[2] !== 'default'
) {
brightness = parseFloat(res_b[2]);
}
if (
res_s != null && res_s[2] !== 'default'
) {
sigma = parseInt(res_s[2]);
}
}
}
this._log(`brightness = ${brightness}, sigma = ${sigma}`);
return [brightness, sigma];
}
/// Updates the blur on a window which needs to be blurred.
update_blur(pid, window_actor, meta_window, brightness, sigma) {
// the window is already blurred, update its blur effect
if (this.blur_actor_map.has(pid)) {
// window is already blurred, but sigma is null: remove the blur
if (sigma === 0) {
this.remove_blur(pid);
}
// window is already blurred and sigma is non-null: update it
else {
this.update_blur_effect(
this.blur_actor_map.get(pid),
brightness,
sigma
);
}
}
// the window is not blurred, and sigma is a non-null value: blur it
else if (sigma !== 0) {
// window is not blurred, blur it
this.create_blur_effect(
pid,
window_actor,
meta_window,
brightness,
sigma
);
}
} }
/// Add the blur effect to the window. /// Add the blur effect to the window.
create_blur_effect(pid, window_actor, meta_window, brightness, sigma) { /// Accepts only tracked meta window that is NOT already blurred.
let blur_effect = new Shell.BlurEffect({ create_blur_effect(meta_window) {
radius: sigma * 2, const pid = meta_window.bms_pid;
brightness: brightness, const window_actor = meta_window.get_compositor_private();
mode: Shell.BlurMode.BACKGROUND
});
let blur_actor = this.create_blur_actor( const pipeline = new DummyPipeline(this.effects_manager, this.settings.applications);
meta_window, let [blur_actor, bg_manager] = pipeline.create_background_with_effect(
window_actor, window_actor, 'bms-application-blurred-widget'
blur_effect
); );
meta_window.blur_actor = blur_actor;
meta_window.bg_manager = bg_manager;
// if hacks are selected, force to repaint the window // if hacks are selected, force to repaint the window
if (this.settings.HACKS_LEVEL === 1 || this.settings.HACKS_LEVEL === 2) { if (this.settings.HACKS_LEVEL === 1) {
this._log("applications hack level 1 or 2"); this._log("hack level 1");
this.paint_signals.disconnect_all(); this.paint_signals.disconnect_all_for_actor(blur_actor);
this.paint_signals.connect(blur_actor, blur_effect); this.paint_signals.connect(blur_actor, pipeline.effect);
} else { } else {
this.paint_signals.disconnect_all(); this.paint_signals.disconnect_all_for_actor(blur_actor);
} }
// insert the blurred widget
window_actor.insert_child_at_index(blur_actor, 0);
// make sure window is blurred in overview // make sure window is blurred in overview
if (this.settings.applications.BLUR_ON_OVERVIEW) if (this.settings.applications.BLUR_ON_OVERVIEW)
this.enforce_window_visibility_on_overview_for(window_actor); this.enforce_window_visibility_on_overview_for(window_actor);
// update the size
this.update_size(pid);
// set the window actor's opacity // set the window actor's opacity
this.set_window_opacity(window_actor, this.settings.applications.OPACITY); this.set_window_opacity(window_actor, this.settings.applications.OPACITY);
// now set up the signals, for the window actor only: they are disconnected
// in `remove_blur`, whereas the signals for the meta window are disconnected
// only when the whole component is disabled
// update the window opacity when it changes, else we don't control it fully
this.connections.connect( this.connections.connect(
window_actor, window_actor, 'notify::opacity',
'notify::opacity', _ => {
_ => this.set_window_opacity(window_actor, this.settings.applications.OPACITY) if (this.focused_window_pid != pid)
this.set_window_opacity(window_actor, this.settings.applications.OPACITY);
}
); );
// register the blur actor/effect
blur_actor['blur_provider_pid'] = pid;
this.blur_actor_map.set(pid, blur_actor);
this.window_map.set(pid, meta_window);
// hide the blur if window is invisible
if (!window_actor.visible) {
blur_actor.hide();
}
// hide the blur if window becomes invisible // hide the blur if window becomes invisible
if (!window_actor.visible)
blur_actor.hide();
this.connections.connect( this.connections.connect(
window_actor, window_actor,
'notify::visible', 'notify::visible',
window_actor => { window_actor => {
let pid = window_actor.blur_provider_pid; if (window_actor.visible)
if (window_actor.visible) { meta_window.blur_actor.show();
this.blur_actor_map.get(pid).show(); else
} else { meta_window.blur_actor.hide();
this.blur_actor_map.get(pid).hide();
}
} }
); );
} }
/// With `focus=true`, tells us we are focused on said window (which can be null if
/// we are not focused anymore). It automatically removes the ancient focus.
/// With `focus=false`, just remove the focus from said window (which can still be null).
set_focus_for_window(meta_window, focus = true) {
let blur_actor = null;
let window_actor = null;
let new_pid = null;
if (meta_window) {
blur_actor = meta_window.blur_actor;
window_actor = meta_window.get_compositor_private();
new_pid = meta_window.bms_pid;
}
if (focus) {
// remove old focused window if any
if (this.focused_window_pid) {
const old_focused_window = this.meta_window_map.get(this.focused_window_pid);
if (old_focused_window)
this.set_focus_for_window(old_focused_window, false);
}
// set new focused window pid
this.focused_window_pid = new_pid;
// if we have blur, hide it and make the window opaque
if (this.settings.applications.DYNAMIC_OPACITY && blur_actor) {
blur_actor.hide();
this.set_window_opacity(window_actor, 255);
}
}
// if we remove the focus and have blur, show it and make the window transparent
else if (blur_actor) {
blur_actor.show();
this.set_window_opacity(window_actor, this.settings.applications.OPACITY);
}
}
/// Makes sure that, when the overview is visible, the window actor will /// Makes sure that, when the overview is visible, the window actor will
/// stay visible no matter what. /// stay visible no matter what.
/// We can instead hide the last child of the window actor, which will /// We can instead hide the last child of the window actor, which will
@ -412,12 +340,24 @@ export const ApplicationsBlur = class ApplicationsBlur {
/// Set the opacity of the window actor that sits on top of the blur effect. /// Set the opacity of the window actor that sits on top of the blur effect.
set_window_opacity(window_actor, opacity) { set_window_opacity(window_actor, opacity) {
window_actor.get_children().forEach(child => { window_actor?.get_children().forEach(child => {
if (child.name !== "blur-actor" && child.opacity != opacity) if (child.name !== "blur-actor" && child.opacity != opacity)
child.opacity = opacity; child.opacity = opacity;
}); });
} }
/// Update the opacity of all window actors.
set_opacity() {
let opacity = this.settings.applications.OPACITY;
this.meta_window_map.forEach(((meta_window, pid) => {
if (pid != this.focused_window_pid && meta_window.blur_actor) {
let window_actor = meta_window.get_compositor_private();
this.set_window_opacity(window_actor, opacity);
}
}));
}
/// Compute the size and position for a blur actor. /// Compute the size and position for a blur actor.
/// If `scale-monitor-framebuffer` experimental feature if on, we don't need to manage scaling. /// If `scale-monitor-framebuffer` experimental feature if on, we don't need to manage scaling.
/// Else, on wayland, we need to divide by the scale to get the correct result. /// Else, on wayland, we need to divide by the scale to get the correct result.
@ -442,107 +382,68 @@ export const ApplicationsBlur = class ApplicationsBlur {
}; };
} }
/// Returns a new already blurred widget, configured to follow the size and /// Removes the blur actor to make a blurred window become normal again.
/// position of its target window. /// It however does not untrack the meta window itself.
create_blur_actor(meta_window, window_actor, blur_effect) { /// Accepts a pid corresponding (or not) to a blurred (or not) meta window.
// compute the size and position
let allocation = this.compute_allocation(meta_window);
// create the actor
let blur_actor = new Clutter.Actor({
x: allocation.x,
y: allocation.y,
width: allocation.width,
height: allocation.height
});
// add the effect
blur_actor.add_effect_with_name('blur-effect', blur_effect);
return blur_actor;
}
/// Updates the blur effect by overwriting its sigma and brightness values.
update_blur_effect(blur_actor, brightness, sigma) {
let effect = blur_actor.get_effect('blur-effect');
effect.radius = sigma * 2;
effect.brightness = brightness;
}
/// Removes the blur actor from the shell and unregister it.
remove_blur(pid) { remove_blur(pid) {
this._log(`removing blur for pid ${pid}`); this._log(`removing blur for pid ${pid}`);
let meta_window = this.window_map.get(pid); let meta_window = this.meta_window_map.get(pid);
// disconnect needed signals and untrack window
if (meta_window) { if (meta_window) {
this.window_map.delete(pid);
let window_actor = meta_window.get_compositor_private(); let window_actor = meta_window.get_compositor_private();
let blur_actor = meta_window.blur_actor;
let bg_manager = meta_window.bg_manager;
let blur_actor = this.blur_actor_map.get(pid); if (blur_actor && window_actor) {
if (blur_actor) { // reset the opacity
this.blur_actor_map.delete(pid); this.set_window_opacity(window_actor, 255);
if (window_actor) { // remove the blurred actor
// reset the opacity window_actor.remove_child(blur_actor);
this.set_window_opacity(window_actor, 255); bg_manager._bms_pipeline.destroy();
bg_manager.destroy();
blur_actor.destroy();
// remove the blurred actor // kinda untrack the blurred actor, as its presence is how we know
window_actor.remove_child(blur_actor); // whether we are blurred or not
delete meta_window.blur_actor;
delete meta_window.bg_manager;
// disconnect the signals about overview animation etc // disconnect the signals of the window actor
this.connections.disconnect_all_for(window_actor); this.paint_signals.disconnect_all_for_actor(blur_actor);
} this.connections.disconnect_all_for(window_actor);
} }
} }
} }
/// Kinda the same as `remove_blur`, but better: it also untracks the window.
/// This needs to be called when the component is being disabled, else it
/// would cause havoc by having untracked windows during normal operations,
/// which is not the point at all!
/// Accepts a pid corresponding (or not) to a blurred (or not) meta window.
untrack_meta_window(pid) {
this.remove_blur(pid);
let meta_window = this.meta_window_map.get(pid);
if (meta_window) {
this.connections.disconnect_all_for(meta_window);
this.meta_window_map.delete(pid);
}
}
disable() { disable() {
this._log("removing blur from applications..."); this._log("removing blur from applications...");
this.service?.unexport(); this.service?.unexport();
delete this.mutter_gsettings; delete this.mutter_gsettings;
this.blur_actor_map.forEach(((_blur_actor, pid) => { this.meta_window_map.forEach((_meta_window, pid) => {
this.remove_blur(pid); this.untrack_meta_window(pid);
})); });
this.connections.disconnect_all(); this.connections.disconnect_all();
this.paint_signals.disconnect_all(); this.paint_signals.disconnect_all();
} }
/// Update the opacity of all window actors.
set_opacity() {
let opacity = this.settings.applications.OPACITY;
this.window_map.forEach(((meta_window, _pid) => {
let window_actor = meta_window.get_compositor_private();
this.set_window_opacity(window_actor, opacity);
}));
}
/// Updates each blur effect to use new sigma value
// FIXME set_sigma and set_brightness are called when the extension is
// loaded and when sigma is changed, and do not respect the per-app
// xprop behaviour
set_sigma(s) {
this.blur_actor_map.forEach((actor, _) => {
actor.get_effect('blur-effect').set_sigma(s);
});
}
/// Updates each blur effect to use new brightness value
set_brightness(b) {
this.blur_actor_map.forEach((actor, _) => {
actor.get_effect('blur-effect').set_brightness(b);
});
}
// not implemented for dynamic blur
set_color(c) { }
set_noise_amount(n) { }
set_noise_lightness(l) { }
_log(str) { _log(str) {
if (this.settings.DEBUG) if (this.settings.DEBUG)
console.log(`[Blur my Shell > applications] ${str}`); console.log(`[Blur my Shell > applications] ${str}`);

View File

@ -0,0 +1,91 @@
import * as Main from "resource:///org/gnome/shell/ui/main.js";
import { PaintSignals } from "../conveniences/paint_signals.js";
import { Pipeline } from "../conveniences/pipeline.js";
export const CoverflowAltTabBlur = class CoverflowAltTabBlur {
constructor(connections, settings, effects_manager) {
this.connections = connections;
this.settings = settings;
this.paint_signals = new PaintSignals(connections);
this.effects_manager = effects_manager;
this.background_actors = [];
this.background_managers = [];
}
enable() {
this._log("blurring coverflow alt-tab");
this.update_backgrounds();
this.connections.connect(
Main.layoutManager.uiGroup,
"child-added",
(_, child) => this.try_blur(child)
);
this.connections.connect(Main.layoutManager, "monitors-changed", (_) => {
this.update_backgrounds();
});
}
update_backgrounds() {
this.remove_background_actors();
Main.layoutManager.uiGroup
.get_children()
.forEach((child) => this.try_blur(child));
}
try_blur(actor) {
if (
actor.constructor.name !== "Meta_BackgroundGroup" ||
actor.get_name() !== "coverflow-alt-tab-background-group"
) {
return;
}
this._log("found coverflow alt-tab to blur");
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
const pipeline = new Pipeline(
this.effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.coverflow_alt_tab.PIPELINE
);
const background_actor = pipeline.create_background_with_effects(
i,
this.background_managers,
actor,
"bms-coverflow-alt-tab-blurred-widget"
);
this.background_actors.push(background_actor);
}
}
remove_background_actors() {
this.background_actors.forEach((actor) => actor.destroy);
this.background_actors = [];
this.background_managers.forEach((background_manager) => {
background_manager._bms_pipeline.destroy();
background_manager.destroy();
});
this.background_managers = [];
}
disable() {
this._log("removing blur from coverflow alt-tab");
this.remove_background_actors();
this.connections.disconnect_all();
}
_log(str) {
if (this.settings.DEBUG) {
console.log(`[Blur my Shell > coverflow alt-tab] ${str}`);
}
}
};

View File

@ -1,12 +1,11 @@
import St from 'gi://St';
import Shell from 'gi://Shell';
import Meta from 'gi://Meta'; import Meta from 'gi://Meta';
import Mtk from 'gi://Mtk';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const Signals = imports.signals; import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
import { PaintSignals } from '../effects/paint_signals.js'; import { PaintSignals } from '../conveniences/paint_signals.js';
import { BlurEffect } from '../effects/blur_effect.js';
import { Pipeline } from '../conveniences/pipeline.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.js';
const DASH_STYLES = [ const DASH_STYLES = [
"transparent-dash", "transparent-dash",
@ -15,150 +14,65 @@ const DASH_STYLES = [
]; ];
// An helper function to find the monitor in which an actor is situated,
/// there might be a pre-existing function in GLib already
function find_monitor_for(actor) {
let extents = actor.get_transformed_extents();
let rect = new Mtk.Rectangle({
x: extents.get_x(),
y: extents.get_y(),
width: extents.get_width(),
height: extents.get_height(),
});
let index = global.display.get_monitor_index_for_rect(rect);
return Main.layoutManager.monitors[index];
}
/// This type of object is created for every dash found, and talks to the main /// This type of object is created for every dash found, and talks to the main
/// DashBlur thanks to signals. /// DashBlur thanks to signals.
/// ///
/// This allows to dynamically track the created dashes for each screen. /// This allows to dynamically track the created dashes for each screen.
class DashInfos { class DashInfos {
constructor(dash_blur, dash, dash_container, dash_background, background, background_parent, effect) { constructor(
dash_blur, dash, dash_container, dash_background,
background, background_group, bg_manager
) {
// the parent DashBlur object, to communicate // the parent DashBlur object, to communicate
this.dash_blur = dash_blur; this.dash_blur = dash_blur;
this.dash_container = dash_container;
// the blurred dash
this.dash = dash; this.dash = dash;
this.dash_container = dash_container;
this.dash_background = dash_background; this.dash_background = dash_background;
this.background_parent = background_parent;
this.background = background; this.background = background;
this.effect = effect; this.background_group = background_group;
this.bg_manager = bg_manager;
this.settings = dash_blur.settings; this.settings = dash_blur.settings;
this.old_style = this.dash._background.style; this.old_style = this.dash._background.style;
dash_blur.connections.connect(dash_blur, 'remove-dashes', () => { this.dash_destroy_id = dash.connect('destroy', () => this.remove_dash_blur(false));
this._log("removing blur from dash"); this.dash_blur_connections_ids = [];
this.dash.get_parent().remove_child(this.background_parent); this.dash_blur_connections_ids.push(
this.remove_style(); this.dash_blur.connect('remove-dashes', () => this.remove_dash_blur()),
}); this.dash_blur.connect('override-style', () => this.override_style()),
this.dash_blur.connect('remove-style', () => this.remove_style()),
this.dash_blur.connect('show', () => this.background_group.show()),
this.dash_blur.connect('hide', () => this.background_group.hide()),
this.dash_blur.connect('update-size', () => this.update_size()),
this.dash_blur.connect('change-blur-type', () => this.change_blur_type()),
this.dash_blur.connect('update-pipeline', () => this.update_pipeline())
);
}
dash_blur.connections.connect(dash_blur, 'update-sigma', () => { // IMPORTANT: do never call this in a mutable `this.dash_blur.forEach`
if (this.dash_blur.is_static) { remove_dash_blur(dash_not_already_destroyed = true) {
this.dash_blur.update_size(); // remove the style and destroy the effects
} this.remove_style();
this.effect.radius = 2 * this.dash_blur.sigma * this.effect.scale; this.destroy_dash(dash_not_already_destroyed);
});
dash_blur.connections.connect(dash_blur, 'update-brightness', () => { // remove the dash infos from their list
this.effect.brightness = this.dash_blur.brightness; const dash_infos_index = this.dash_blur.dashes.indexOf(this);
}); if (dash_infos_index >= 0)
this.dash_blur.dashes.splice(dash_infos_index, 1);
dash_blur.connections.connect(dash_blur, 'update-corner-radius', () => { // disconnect everything
if (this.dash_blur.is_static) { this.dash_blur_connections_ids.forEach(id => { if (id) this.dash_blur.disconnect(id); });
let monitor = find_monitor_for(this.dash); this.dash_blur_connections_ids = [];
let corner_radius = this.dash_blur.corner_radius * monitor.geometry_scale; if (this.dash_destroy_id)
this.effect.corner_radius = Math.min( this.dash.disconnect(this.dash_destroy_id);
corner_radius, this.effect.width / 2, this.effect.height / 2 this.dash_destroy_id = null;
); }
}
});
dash_blur.connections.connect(dash_blur, 'override-background', () => { override_style() {
this.remove_style(); this.remove_style();
this.dash.set_style_class_name( this.dash.set_style_class_name(
DASH_STYLES[this.settings.dash_to_dock.STYLE_DASH_TO_DOCK] DASH_STYLES[this.settings.dash_to_dock.STYLE_DASH_TO_DOCK]
); );
});
dash_blur.connections.connect(dash_blur, 'reset-background', () => this.remove_style());
dash_blur.connections.connect(dash_blur, 'show', () => {
if (this.dash_blur.is_static)
this.background_parent.show();
else
this.effect.radius = this.dash_blur.sigma * 2 * this.effect.scale;
});
dash_blur.connections.connect(dash_blur, 'hide', () => {
if (this.dash_blur.is_static)
this.background_parent.hide();
else
this.effect.radius = 0;
});
dash_blur.connections.connect(dash_blur, 'update-wallpaper', () => {
if (this.dash_blur.is_static) {
let bg = Main.layoutManager._backgroundGroup.get_child_at_index(
Main.layoutManager.monitors.length
- find_monitor_for(this.dash).index - 1
);
if (bg && bg.get_content()) {
this.background.content.set({
background: bg.get_content().background
});
this._log('wallpaper updated');
} else {
this._warn("could not get background for dash-to-dock");
}
}
});
dash_blur.connections.connect(dash_blur, 'update-size', () => {
if (this.dash_blur.is_static) {
let [x, y] = this.get_dash_position(this.dash_container, this.dash_background);
this.background.x = -x;
this.background.y = -y;
this.effect.width = this.dash_background.width;
this.effect.height = this.dash_background.height;
this.dash_blur.set_corner_radius(this.dash_blur.corner_radius);
if (dash_container.get_style_class_name().includes("top")) {
this.background.set_clip(x, y + this.dash.y + this.dash_background.y, this.dash_background.width, this.dash_background.height);
} else if (dash_container.get_style_class_name().includes("bottom")) {
this.background.set_clip(x, y + this.dash.y + this.dash_background.y, this.dash_background.width, this.dash_background.height);
} else if (dash_container.get_style_class_name().includes("left")) {
this.background.set_clip(x + this.dash.x + this.dash_background.x, y + this.dash.y + this.dash_background.y, this.dash_background.width, this.dash_background.height);
} else if (dash_container.get_style_class_name().includes("right")) {
this.background.set_clip(x + this.dash.x + this.dash_background.x, y + this.dash.y + this.dash_background.y, this.dash_background.width, this.dash_background.height);
}
} else {
this.background.width = this.dash_background.width;
this.background.height = this.dash_background.height;
this.background.x = this.dash_background.x;
this.background.y = this.dash_background.y + this.dash.y;
}
});
dash_blur.connections.connect(dash_blur, 'change-blur-type', () => {
this.background_parent.remove_child(this.background);
if (this.effect.chained_effect)
this.effect.get_actor()?.remove_effect(this.effect.chained_effect);
this.effect.get_actor()?.remove_effect(this.effect);
let [background, effect] = this.dash_blur.add_blur(this.dash, this.dash_background, this.dash_container);
this.background = background;
this.effect = effect;
this.background_parent.add_child(this.background);
});
} }
remove_style() { remove_style() {
@ -169,10 +83,88 @@ class DashInfos {
); );
} }
destroy_dash(dash_not_already_destroyed = true) {
if (!dash_not_already_destroyed)
this.bg_manager.backgroundActor = null;
this.paint_signals?.disconnect_all();
this.dash.get_parent().remove_child(this.background_group);
this.bg_manager._bms_pipeline.destroy();
this.bg_manager.destroy();
this.background_group.destroy();
}
change_blur_type() {
this.destroy_dash();
let [
background, background_group, bg_manager, paint_signals
] = this.dash_blur.add_blur(this.dash);
this.background = background;
this.background_group = background_group;
this.bg_manager = bg_manager;
this.paint_signals = paint_signals;
this.dash.get_parent().insert_child_at_index(this.background_group, 0);
this.update_size();
}
update_pipeline() {
this.bg_manager._bms_pipeline.change_pipeline_to(
this.settings.dash_to_dock.PIPELINE
);
}
update_size() {
if (this.dash_blur.is_static) {
let [x, y] = this.get_dash_position(this.dash_container, this.dash_background);
this.background.x = -x;
this.background.y = -y;
if (this.dash_container.get_style_class_name().includes("top"))
this.background.set_clip(
x,
y + this.dash.y + this.dash_background.y,
this.dash_background.width,
this.dash_background.height
);
else if (this.dash_container.get_style_class_name().includes("bottom"))
this.background.set_clip(
x,
y + this.dash.y + this.dash_background.y,
this.dash_background.width,
this.dash_background.height
);
else if (this.dash_container.get_style_class_name().includes("left"))
this.background.set_clip(
x + this.dash.x + this.dash_background.x,
y + this.dash.y + this.dash_background.y,
this.dash_background.width,
this.dash_background.height
);
else if (this.dash_container.get_style_class_name().includes("right"))
this.background.set_clip(
x + this.dash.x + this.dash_background.x,
y + this.dash.y + this.dash_background.y,
this.dash_background.width,
this.dash_background.height
);
} else {
this.background.width = this.dash_background.width;
this.background.height = this.dash_background.height;
this.background.x = this.dash_background.x;
this.background.y = this.dash_background.y + this.dash.y;
}
}
get_dash_position(dash_container, dash_background) { get_dash_position(dash_container, dash_background) {
var x, y; var x, y;
let monitor = find_monitor_for(dash_container); let monitor = Main.layoutManager.findMonitorForActor(dash_container);
let dash_box = dash_container._slider.get_child(); let dash_box = dash_container._slider.get_child();
if (dash_container.get_style_class_name().includes("top")) { if (dash_container.get_style_class_name().includes("top")) {
@ -202,19 +194,13 @@ class DashInfos {
} }
} }
export const DashBlur = class DashBlur { export const DashBlur = class DashBlur extends Signals.EventEmitter {
constructor(connections, settings, _) { constructor(connections, settings, _) {
super();
this.dashes = []; this.dashes = [];
this.connections = connections; this.connections = connections;
this.settings = settings; this.settings = settings;
this.paint_signals = new PaintSignals(connections); this.paint_signals = new PaintSignals(connections);
this.sigma = this.settings.dash_to_dock.CUSTOMIZE
? this.settings.dash_to_dock.SIGMA
: this.settings.SIGMA;
this.brightness = this.settings.dash_to_dock.CUSTOMIZE
? this.settings.dash_to_dock.BRIGHTNESS
: this.settings.BRIGHTNESS;
this.corner_radius = this.settings.dash_to_dock.CORNER_RADIUS;
this.is_static = this.settings.dash_to_dock.STATIC_BLUR; this.is_static = this.settings.dash_to_dock.STATIC_BLUR;
this.enabled = false; this.enabled = false;
} }
@ -231,7 +217,6 @@ export const DashBlur = class DashBlur {
this.blur_existing_dashes(); this.blur_existing_dashes();
this.connect_to_overview(); this.connect_to_overview();
this.update_wallpaper();
this.update_size(); this.update_size();
this.enabled = true; this.enabled = true;
@ -246,7 +231,7 @@ export const DashBlur = class DashBlur {
Main.uiGroup.get_children().filter((child) => { Main.uiGroup.get_children().filter((child) => {
return (child.get_name() === "dashtodockContainer") && return (child.get_name() === "dashtodockContainer") &&
(child.constructor.name === 'DashToDock'); (child.constructor.name === 'DashToDock');
}).forEach(this.try_blur.bind(this)); }).forEach(dash_container => this.try_blur(dash_container));
} }
// Tries to blur the dash contained in the given actor // Tries to blur the dash contained in the given actor
@ -254,9 +239,9 @@ export const DashBlur = class DashBlur {
let dash_box = dash_container._slider.get_child(); let dash_box = dash_container._slider.get_child();
// verify that we did not already blur that dash // verify that we did not already blur that dash
if (!dash_box.get_children().some((child) => { if (!dash_box.get_children().some(child =>
return child.get_name() === "dash-blurred-background-parent"; child.get_name() === "bms-dash-backgroundgroup"
})) { )) {
this._log("dash to dock found, blurring it"); this._log("dash to dock found, blurring it");
// finally blur the dash // finally blur the dash
@ -270,48 +255,26 @@ export const DashBlur = class DashBlur {
// Blurs the dash and returns a `DashInfos` containing its information // Blurs the dash and returns a `DashInfos` containing its information
blur_dash_from(dash, dash_container) { blur_dash_from(dash, dash_container) {
// dash background parent, not visible let [background, background_group, bg_manager, paint_signals] = this.add_blur(dash);
let background_parent = new St.Widget({
name: 'dash-blurred-background-parent',
style_class: 'dash-blurred-background-parent',
width: 0,
height: 0
});
// finally blur the dash // insert the background group to the right element
let dash_background = dash.get_children().find(child => { dash.get_parent().insert_child_at_index(background_group, 0);
return child.get_style_class_name() === 'dash-background';
});
let [background, effect] = this.add_blur(dash, dash_background, dash_container);
this.update_wallpaper();
this.update_size();
// updates size and position on change // updates size and position on change
this.connections.connect(dash, 'notify::width', _ => { this.connections.connect(
this.update_size(); dash,
}); ['notify::width', 'notify::height'],
this.connections.connect(dash, 'notify::height', _ => { _ => this.update_size()
this.update_size(); );
}); this.connections.connect(
this.connections.connect(dash_container, 'notify::width', _ => { dash_container,
this.update_size(); ['notify::width', 'notify::height', 'notify::y', 'notify::x'],
}); _ => this.update_size()
this.connections.connect(dash_container, 'notify::height', _ => { );
this.update_size();
});
this.connections.connect(dash_container, 'notify::y', _ => {
this.update_wallpaper();
this.update_size();
});
this.connections.connect(dash_container, 'notify::x', _ => {
this.update_wallpaper();
this.update_size();
});
background_parent.add_child(background); const dash_background = dash.get_children().find(child => {
dash.get_parent().insert_child_at_index(background_parent, 0); return child.get_style_class_name() === 'dash-background';
});
// create infos // create infos
let infos = new DashInfos( let infos = new DashInfos(
@ -320,137 +283,81 @@ export const DashBlur = class DashBlur {
dash_container, dash_container,
dash_background, dash_background,
background, background,
background_parent, background_group,
effect bg_manager,
paint_signals
); );
// update the background this.update_size();
this.update_background(); this.update_background();
// returns infos // returns infos
return infos; return infos;
} }
add_blur(dash, dash_background, dash_container) { add_blur(dash) {
let monitor = find_monitor_for(dash); const monitor = Main.layoutManager.findMonitorForActor(dash);
if (!monitor)
return;
// dash background widget const background_group = new Meta.BackgroundGroup({
let background = this.is_static name: 'bms-dash-backgroundgroup', width: 0, height: 0
? new Meta.BackgroundActor({ });
meta_display: global.display,
monitor: monitor.index,
})
: new St.Widget({
name: 'dash-blurred-background',
style_class: 'dash-blurred-background',
x: dash_background.x,
y: dash_background.y + dash.y,
width: dash_background.width,
height: dash_background.height,
});
// the effect to be applied let background, bg_manager, paint_signals;
let effect; let static_blur = this.settings.dash_to_dock.STATIC_BLUR;
if (this.is_static) { if (static_blur) {
let corner_radius = this.corner_radius * monitor.geometry_scale; let bg_manager_list = [];
corner_radius = Math.min(corner_radius, dash_background.width / 2, dash_background.height / 2); const pipeline = new Pipeline(
global.blur_my_shell._effects_manager,
effect = new BlurEffect({ global.blur_my_shell._pipelines_manager,
radius: 2 * this.sigma * monitor.geometry_scale, this.settings.dash_to_dock.PIPELINE
brightness: this.brightness,
width: dash_background.width,
height: dash_background.height,
corner_radius: corner_radius
});
// connect to every background change (even without changing image)
// FIXME this signal is fired very often, so we should find another one
// fired only when necessary (but that still catches all cases)
this.connections.connect(
Main.layoutManager._backgroundGroup,
'notify',
_ => this.update_wallpaper()
); );
} else { background = pipeline.create_background_with_effects(
effect = new Shell.BlurEffect({ monitor.index, bg_manager_list,
brightness: this.brightness, background_group, 'bms-dash-blurred-widget'
radius: this.sigma * 2 * monitor.geometry_scale, );
mode: Shell.BlurMode.BACKGROUND bg_manager = bg_manager_list[0];
}); }
else {
const pipeline = new DummyPipeline(
global.blur_my_shell._effects_manager,
this.settings.dash_to_dock
);
[background, bg_manager] = pipeline.create_background_with_effect(
background_group, 'bms-dash-blurred-widget'
);
paint_signals = new PaintSignals(this.connections);
// HACK // HACK
// //
//`Shell.BlurEffect` does not repaint when shadows are under it. [1] //`Shell.BlurEffect` does not repaint when shadows are under it. [1]
// //
// This does not entirely fix this bug (shadows caused by windows // This does not entirely fix this bug (shadows caused by windows
// still cause artifacts), but it prevents the shadows of the panel // still cause artifacts), but it prevents the shadows of the dash
// buttons to cause artifacts on the panel itself // buttons to cause artifacts on the dash itself
// //
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857 // [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
if (this.settings.HACKS_LEVEL === 1) { if (this.settings.HACKS_LEVEL === 1) {
this._log("dash hack level 1"); this._log("hack level 1");
this.paint_signals.disconnect_all();
let rp = () => { paint_signals.disconnect_all();
effect.queue_repaint(); paint_signals.connect(background, pipeline.effect);
};
dash._box.get_children().forEach((icon) => {
try {
let zone = icon.get_child_at_index(0);
this.connections.connect(zone, [
'enter-event', 'leave-event', 'button-press-event'
], rp);
} catch (e) {
this._warn(`${e}, continuing`);
}
});
this.connections.connect(dash._box, 'actor-added', (_, actor) => {
try {
let zone = actor.get_child_at_index(0);
this.connections.connect(zone, [
'enter-event', 'leave-event', 'button-press-event'
], rp);
} catch (e) {
this._warn(`${e}, continuing`);
}
});
let show_apps = dash._showAppsIcon;
this.connections.connect(show_apps, [
'enter-event', 'leave-event', 'button-press-event'
], rp);
this.connections.connect(dash, 'leave-event', rp);
} else if (this.settings.HACKS_LEVEL === 2) {
this._log("dash hack level 2");
this.paint_signals.connect(background, effect);
} else { } else {
this.paint_signals.disconnect_all(); paint_signals.disconnect_all();
} }
} }
// store the scale in the effect in order to retrieve it in set_sigma return [background, background_group, bg_manager, paint_signals];
effect.scale = monitor.geometry_scale;
background.add_effect(effect);
return [background, effect];
} }
change_blur_type() { change_blur_type() {
this.is_static = this.settings.dash_to_dock.STATIC_BLUR; this.is_static = this.settings.dash_to_dock.STATIC_BLUR;
this.emit('change-blur-type', true); this.emit('change-blur-type');
this.update_wallpaper();
this.update_background(); this.update_background();
this.update_size();
} }
/// Connect when overview if opened/closed to hide/show the blur accordingly /// Connect when overview if opened/closed to hide/show the blur accordingly
@ -459,10 +366,10 @@ export const DashBlur = class DashBlur {
if (this.settings.dash_to_dock.UNBLUR_IN_OVERVIEW) { if (this.settings.dash_to_dock.UNBLUR_IN_OVERVIEW) {
this.connections.connect( this.connections.connect(
Main.overview, 'showing', this.hide.bind(this) Main.overview, 'showing', _ => this.hide()
); );
this.connections.connect( this.connections.connect(
Main.overview, 'hidden', this.show.bind(this) Main.overview, 'hidden', _ => this.show()
); );
} }
}; };
@ -470,45 +377,32 @@ export const DashBlur = class DashBlur {
/// Updates the background to either remove it or not, according to the /// Updates the background to either remove it or not, according to the
/// user preferences. /// user preferences.
update_background() { update_background() {
this._log("updating background");
if (this.settings.dash_to_dock.OVERRIDE_BACKGROUND) if (this.settings.dash_to_dock.OVERRIDE_BACKGROUND)
this.emit('override-background', true); this.emit('override-style');
else else
this.emit('reset-background', true); this.emit('remove-style');
} }
update_wallpaper() { update_pipeline() {
if (this.is_static) this.emit('update-pipeline');
this.emit('update-wallpaper', true);
} }
update_size() { update_size() {
this.emit('update-size', true); this.emit('update-size');
} }
set_sigma(sigma) { show() {
this.sigma = sigma; this.emit('show');
this.emit('update-sigma', true);
} }
hide() {
set_brightness(brightness) { this.emit('hide');
this.brightness = brightness;
this.emit('update-brightness', true);
} }
set_corner_radius(radius) {
this.corner_radius = radius;
this.emit('update-corner-radius', true);
}
// not implemented for dynamic blur
set_color(c) { }
set_noise_amount(n) { }
set_noise_lightness(l) { }
disable() { disable() {
this._log("removing blur from dashes"); this._log("removing blur from dashes");
this.emit('remove-dashes', true); this.emit('remove-dashes');
this.dashes = []; this.dashes = [];
this.connections.disconnect_all(); this.connections.disconnect_all();
@ -516,13 +410,6 @@ export const DashBlur = class DashBlur {
this.enabled = false; this.enabled = false;
} }
show() {
this.emit('show', true);
}
hide() {
this.emit('hide', true);
}
_log(str) { _log(str) {
if (this.settings.DEBUG) if (this.settings.DEBUG)
console.log(`[Blur my Shell > dash manager] ${str}`); console.log(`[Blur my Shell > dash manager] ${str}`);
@ -531,6 +418,4 @@ export const DashBlur = class DashBlur {
_warn(str) { _warn(str) {
console.warn(`[Blur my Shell > dash manager] ${str}`); console.warn(`[Blur my Shell > dash manager] ${str}`);
} }
}; };
Signals.addSignalMethods(DashBlur.prototype);

View File

@ -1,19 +1,14 @@
import St from 'gi://St';
import Shell from 'gi://Shell';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Background from 'resource:///org/gnome/shell/ui/background.js';
import { UnlockDialog } from 'resource:///org/gnome/shell/ui/unlockDialog.js'; import { UnlockDialog } from 'resource:///org/gnome/shell/ui/unlockDialog.js';
let sigma; import { Pipeline } from '../conveniences/pipeline.js';
let brightness;
let color;
let noise;
let lightness;
const original_createBackground = const original_createBackground =
UnlockDialog.prototype._createBackground; UnlockDialog.prototype._createBackground;
const original_updateBackgroundEffects = const original_updateBackgroundEffects =
UnlockDialog.prototype._updateBackgroundEffects; UnlockDialog.prototype._updateBackgroundEffects;
const original_updateBackgrounds =
UnlockDialog.prototype._updateBackgrounds;
export const LockscreenBlur = class LockscreenBlur { export const LockscreenBlur = class LockscreenBlur {
@ -27,22 +22,6 @@ export const LockscreenBlur = class LockscreenBlur {
enable() { enable() {
this._log("blurring lockscreen"); this._log("blurring lockscreen");
brightness = this.settings.lockscreen.CUSTOMIZE
? this.settings.lockscreen.BRIGHTNESS
: this.settings.BRIGHTNESS;
sigma = this.settings.lockscreen.CUSTOMIZE
? this.settings.lockscreen.SIGMA
: this.settings.SIGMA;
color = this.settings.lockscreen.CUSTOMIZE
? this.settings.lockscreen.COLOR
: this.settings.COLOR;
noise = this.settings.lockscreen.CUSTOMIZE
? this.settings.lockscreen.NOISE_AMOUNT
: this.settings.NOISE_AMOUNT;
lightness = this.settings.lockscreen.CUSTOMIZE
? this.settings.lockscreen.NOISE_LIGHTNESS
: this.settings.NOISE_LIGHTNESS;
this.update_lockscreen(); this.update_lockscreen();
this.enabled = true; this.enabled = true;
@ -53,103 +32,39 @@ export const LockscreenBlur = class LockscreenBlur {
this._createBackground; this._createBackground;
UnlockDialog.prototype._updateBackgroundEffects = UnlockDialog.prototype._updateBackgroundEffects =
this._updateBackgroundEffects; this._updateBackgroundEffects;
UnlockDialog.prototype._updateBackgrounds =
this._updateBackgrounds;
} }
_createBackground(monitorIndex) { _createBackground(monitor_index) {
let monitor = Main.layoutManager.monitors[monitorIndex]; let pipeline = new Pipeline(
let widget = new St.Widget({ global.blur_my_shell._effects_manager, global.blur_my_shell._pipelines_manager,
style_class: "screen-shield-background", global.blur_my_shell._settings.lockscreen.PIPELINE
x: monitor.x, );
y: monitor.y,
width: monitor.width,
height: monitor.height,
});
let blur_effect = new Shell.BlurEffect({ pipeline.create_background_with_effects(
name: 'blur', monitor_index,
radius: sigma * 2, this._bgManagers,
brightness: brightness this._backgroundGroup,
}); "screen-shield-background"
);
// store the scale in the effect in order to retrieve later
blur_effect.scale = monitor.geometry_scale;
let color_effect = global.blur_my_shell._lockscreen_blur.effects_manager.new_color_effect({
name: 'color',
color: color
}, this.settings);
let noise_effect = global.blur_my_shell._lockscreen_blur.effects_manager.new_noise_effect({
name: 'noise',
noise: noise,
lightness: lightness
}, this.settings);
widget.add_effect(color_effect);
widget.add_effect(noise_effect);
widget.add_effect(blur_effect);
let bgManager = new Background.BackgroundManager({
container: widget,
monitorIndex,
controlPosition: false,
});
this._bgManagers.push(bgManager);
this._backgroundGroup.add_child(widget);
} }
_updateBackgroundEffects() { _updateBackgroundEffects() {
for (const widget of this._backgroundGroup) { this._updateBackgrounds();
const color_effect = widget.get_effect('color'); }
const noise_effect = widget.get_effect('noise');
const blur_effect = widget.get_effect('blur');
if (color_effect) _updateBackgrounds() {
color_effect.set({ for (let i = 0; i < this._bgManagers.length; i++) {
color: color this._bgManagers[i]._bms_pipeline.destroy();
}); this._bgManagers[i].destroy();
if (noise_effect) {
noise_effect.set({
noise: noise,
lightness: lightness,
});
}
if (blur_effect) {
blur_effect.set({
brightness: brightness,
radius: sigma * 2 * blur_effect.scale,
});
}
} }
}
set_sigma(s) { this._bgManagers = [];
sigma = s; this._backgroundGroup.destroy_all_children();
this.update_lockscreen();
}
set_brightness(b) { for (let i = 0; i < Main.layoutManager.monitors.length; i++)
brightness = b; this._createBackground(i);
this.update_lockscreen();
}
set_color(c) {
color = c;
this.update_lockscreen();
}
set_noise_amount(n) {
noise = n;
this.update_lockscreen();
}
set_noise_lightness(l) {
lightness = l;
this.update_lockscreen();
} }
disable() { disable() {
@ -159,6 +74,8 @@ export const LockscreenBlur = class LockscreenBlur {
original_createBackground; original_createBackground;
UnlockDialog.prototype._updateBackgroundEffects = UnlockDialog.prototype._updateBackgroundEffects =
original_updateBackgroundEffects; original_updateBackgroundEffects;
UnlockDialog.prototype._updateBackgrounds =
original_updateBackgrounds;
this.connections.disconnect_all(); this.connections.disconnect_all();

View File

@ -1,10 +1,11 @@
import Shell from 'gi://Shell';
import Meta from 'gi://Meta'; import Meta from 'gi://Meta';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { WorkspaceAnimationController } from 'resource:///org/gnome/shell/ui/workspaceAnimation.js'; import { WorkspaceAnimationController } from 'resource:///org/gnome/shell/ui/workspaceAnimation.js';
const wac_proto = WorkspaceAnimationController.prototype; const wac_proto = WorkspaceAnimationController.prototype;
import { Pipeline } from '../conveniences/pipeline.js';
const OVERVIEW_COMPONENTS_STYLE = [ const OVERVIEW_COMPONENTS_STYLE = [
"overview-components-light", "overview-components-light",
"overview-components-dark", "overview-components-dark",
@ -15,40 +16,23 @@ const OVERVIEW_COMPONENTS_STYLE = [
export const OverviewBlur = class OverviewBlur { export const OverviewBlur = class OverviewBlur {
constructor(connections, settings, effects_manager) { constructor(connections, settings, effects_manager) {
this.connections = connections; this.connections = connections;
this.effects = [];
this.settings = settings; this.settings = settings;
this.effects_manager = effects_manager; this.effects_manager = effects_manager;
this._workspace_switch_bg_actors = []; this.overview_background_managers = [];
this.overview_background_group = new Meta.BackgroundGroup(
{ name: 'bms-overview-backgroundgroup' }
);
this.animation_background_managers = [];
this.animation_background_group = new Meta.BackgroundGroup(
{ name: 'bms-animation-backgroundgroup' }
);
this.enabled = false; this.enabled = false;
this.proto_patched = false;
} }
enable() { enable() {
this._log("blurring overview"); this._log("blurring overview");
// connect to every background change (even without changing image)
// FIXME this signal is fired very often, so we should find another one
// fired only when necessary (but that still catches all cases)
this.connections.connect(
Main.layoutManager._backgroundGroup,
'notify',
_ => {
this._log("updated background");
this.update_backgrounds();
}
);
// connect to monitors change
this.connections.connect(
Main.layoutManager,
'monitors-changed',
_ => {
if (Main.screenShield && !Main.screenShield.locked) {
this._log("changed monitors");
this.update_backgrounds();
}
}
);
// add css class name for workspace-switch background // add css class name for workspace-switch background
Main.uiGroup.add_style_class_name("blurred-overview"); Main.uiGroup.add_style_class_name("blurred-overview");
@ -58,12 +42,16 @@ export const OverviewBlur = class OverviewBlur {
// update backgrounds when the component is enabled // update backgrounds when the component is enabled
this.update_backgrounds(); this.update_backgrounds();
// connect to monitors change
this.connections.connect(Main.layoutManager, 'monitors-changed',
_ => this.update_backgrounds()
);
// part for the workspace animation switch // part for the workspace animation switch
// make sure not to do this part if the extension was enabled prior, as // make sure not to do this part if the functions were patched prior, as
// the functions would call themselves and cause infinite recursion // the functions would call themselves and cause infinite recursion
if (!this.enabled) { if (!this.proto_patched) {
// store original workspace switching methods for restoring them on // store original workspace switching methods for restoring them on
// disable() // disable()
this._original_PrepareSwitch = wac_proto._prepareWorkspaceSwitch; this._original_PrepareSwitch = wac_proto._prepareWorkspaceSwitch;
@ -91,25 +79,20 @@ export const OverviewBlur = class OverviewBlur {
); );
} }
Main.layoutManager.monitors.forEach(monitor => { Main.uiGroup.insert_child_above(
if ( outer_this.animation_background_group,
!( global.window_group
);
outer_this.animation_background_managers.forEach(bg_manager => {
if (bg_manager._bms_pipeline.actor)
if (
Meta.prefs_get_workspaces_only_on_primary() && Meta.prefs_get_workspaces_only_on_primary() &&
(monitor !== Main.layoutManager.primaryMonitor) bg_manager._monitorIndex !== Main.layoutManager.primaryMonitor.index
) )
) { bg_manager._bms_pipeline.actor.visible = false;
const bg_actor = outer_this.create_background_actor( else
monitor, true bg_manager._bms_pipeline.actor.visible = true;
);
Main.uiGroup.insert_child_above(
bg_actor,
global.window_group
);
// store the actors so that we can delete them later
outer_this._workspace_switch_bg_actors.push(bg_actor);
}
}); });
}; };
@ -129,15 +112,10 @@ export const OverviewBlur = class OverviewBlur {
); );
} }
outer_this.effects = outer_this.effects.filter( Main.uiGroup.remove_child(outer_this.animation_background_group);
effects_group => !effects_group.is_transition
);
outer_this._workspace_switch_bg_actors.forEach(actor => {
actor.destroy();
});
outer_this._workspace_switch_bg_actors = [];
}; };
this.proto_patched = true;
} }
this.enabled = true; this.enabled = true;
@ -146,78 +124,30 @@ export const OverviewBlur = class OverviewBlur {
update_backgrounds() { update_backgrounds() {
// remove every old background // remove every old background
this.remove_background_actors(); this.remove_background_actors();
// create new backgrounds for the overview and the animation
// add new backgrounds for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
Main.layoutManager.monitors.forEach(monitor => { const pipeline_overview = new Pipeline(
const bg_actor = this.create_background_actor(monitor, false); this.effects_manager,
global.blur_my_shell._pipelines_manager,
Main.layoutManager.overviewGroup.insert_child_at_index( this.settings.overview.PIPELINE
bg_actor, );
monitor.index pipeline_overview.create_background_with_effects(
i, this.overview_background_managers,
this.overview_background_group, 'bms-overview-blurred-widget'
); );
});
}
create_background_actor(monitor, is_transition) { const pipeline_animation = new Pipeline(
let bg_actor = new Meta.BackgroundActor({ this.effects_manager,
name: "blur-my-shell_background_actor", global.blur_my_shell._pipelines_manager,
meta_display: global.display, this.settings.overview.PIPELINE
monitor: monitor.index );
}); pipeline_animation.create_background_with_effects(
let background_group = Main.layoutManager._backgroundGroup i, this.animation_background_managers,
.get_children() this.animation_background_group, 'bms-animation-blurred-widget'
.filter((child) => child instanceof Meta.BackgroundActor); );
let background =
background_group[
Main.layoutManager.monitors.length - monitor.index - 1
];
if (!background) {
this._warn("could not get background for overview");
return bg_actor;
} }
// add the container widget for the overview only to the overview group
bg_actor.content.set({ Main.layoutManager.overviewGroup.insert_child_at_index(this.overview_background_group, 0);
background: background.get_content().background
});
let blur_effect = new Shell.BlurEffect({
brightness: this.settings.overview.CUSTOMIZE
? this.settings.overview.BRIGHTNESS
: this.settings.BRIGHTNESS,
radius: (this.settings.overview.CUSTOMIZE
? this.settings.overview.SIGMA
: this.settings.SIGMA) * 2 * monitor.geometry_scale,
mode: Shell.BlurMode.ACTOR
});
// store the scale in the effect in order to retrieve it in set_sigma
blur_effect.scale = monitor.geometry_scale;
let color_effect = this.effects_manager.new_color_effect({
color: this.settings.overview.CUSTOMIZE
? this.settings.overview.COLOR
: this.settings.COLOR
}, this.settings);
let noise_effect = this.effects_manager.new_noise_effect({
noise: this.settings.overview.CUSTOMIZE
? this.settings.overview.NOISE_AMOUNT
: this.settings.NOISE_AMOUNT,
lightness: this.settings.overview.CUSTOMIZE
? this.settings.overview.NOISE_LIGHTNESS
: this.settings.NOISE_LIGHTNESS
}, this.settings);
bg_actor.add_effect(color_effect);
bg_actor.add_effect(noise_effect);
bg_actor.add_effect(blur_effect);
this.effects.push({ blur_effect, color_effect, noise_effect, is_transition });
bg_actor.set_x(monitor.x);
bg_actor.set_y(monitor.y);
return bg_actor;
} }
/// Updates the classname to style overview components with semi-transparent /// Updates the classname to style overview components with semi-transparent
@ -233,71 +163,43 @@ export const OverviewBlur = class OverviewBlur {
); );
} }
set_sigma(s) {
this.effects.forEach(effect => {
effect.blur_effect.radius = s * 2 * effect.blur_effect.scale;
});
}
set_brightness(b) {
this.effects.forEach(effect => {
effect.blur_effect.brightness = b;
});
}
set_color(c) {
this.effects.forEach(effect => {
effect.color_effect.color = c;
});
}
set_noise_amount(n) {
this.effects.forEach(effect => {
effect.noise_effect.noise = n;
});
}
set_noise_lightness(l) {
this.effects.forEach(effect => {
effect.noise_effect.lightness = l;
});
}
remove_background_actors() { remove_background_actors() {
Main.layoutManager.overviewGroup.get_children().forEach(child => { this.overview_background_group.remove_all_children();
if (child instanceof Meta.BackgroundActor this.animation_background_group.remove_all_children();
&& child.get_name() == "blur-my-shell_background_actor" this.overview_background_managers.forEach(background_manager => {
) { background_manager._bms_pipeline.destroy();
child.get_effects().forEach(effect => { background_manager.destroy();
this.effects_manager.remove(effect);
});
Main.layoutManager.overviewGroup.remove_child(child);
child.destroy();
}
}); });
this.effects = []; this.animation_background_managers.forEach(background_manager => {
background_manager._bms_pipeline.destroy();
background_manager.destroy();
});
this.overview_background_managers = [];
this.animation_background_managers = [];
} }
disable() { disable() {
this._log("removing blur from overview"); this._log("removing blur from overview");
this.remove_background_actors(); this.remove_background_actors();
Main.uiGroup.remove_style_class_name("blurred-overview"); Main.uiGroup.remove_style_class_name("blurred-overview");
OVERVIEW_COMPONENTS_STYLE.forEach( OVERVIEW_COMPONENTS_STYLE.forEach(
style => Main.uiGroup.remove_style_class_name(style) style => Main.uiGroup.remove_style_class_name(style)
); );
// make sure to absolutely not do this if the component was not enabled this.connections.disconnect_all();
// prior, as this would cause infinite recursion this.enabled = false;
if (this.enabled) { }
// restore original behavior
restore_patched_proto() {
if (this.proto_patched) {
if (this._original_PrepareSwitch) if (this._original_PrepareSwitch)
wac_proto._prepareWorkspaceSwitch = this._original_PrepareSwitch; wac_proto._prepareWorkspaceSwitch = this._original_PrepareSwitch;
if (this._original_FinishSwitch) if (this._original_FinishSwitch)
wac_proto._finishWorkspaceSwitch = this._original_FinishSwitch; wac_proto._finishWorkspaceSwitch = this._original_FinishSwitch;
}
this.connections.disconnect_all(); this.proto_patched = false;
this.enabled = false; }
} }
_log(str) { _log(str) {

View File

@ -1,10 +1,11 @@
import St from 'gi://St'; import St from 'gi://St';
import Shell from 'gi://Shell';
import Meta from 'gi://Meta'; import Meta from 'gi://Meta';
import Mtk from 'gi://Mtk';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { PaintSignals } from '../effects/paint_signals.js'; import { PaintSignals } from '../conveniences/paint_signals.js';
import { Pipeline } from '../conveniences/pipeline.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.js';
const DASH_TO_PANEL_UUID = 'dash-to-panel@jderose9.github.com'; const DASH_TO_PANEL_UUID = 'dash-to-panel@jderose9.github.com';
const PANEL_STYLES = [ const PANEL_STYLES = [
@ -56,26 +57,9 @@ export const PanelBlur = class PanelBlur {
// update the classname if the panel to have or have not light text // update the classname if the panel to have or have not light text
this.update_light_text_classname(); this.update_light_text_classname();
// connect to every background change (even without changing image) // connect to workareas change
// FIXME this signal is fired very often, so we should find another one this.connections.connect(global.display, 'workareas-changed',
// fired only when necessary (but that still catches all cases) _ => this.reset()
this.connections.connect(
Main.layoutManager._backgroundGroup,
'notify',
_ => this.actors_list.forEach(actors =>
this.update_wallpaper(actors)
)
);
// connect to monitors change
this.connections.connect(
Main.layoutManager,
'monitors-changed',
_ => {
if (Main.screenShield && !Main.screenShield.locked) {
this.reset();
}
}
); );
this.enabled = true; this.enabled = true;
@ -131,9 +115,6 @@ export const PanelBlur = class PanelBlur {
if (!actors) if (!actors)
// if the actors is not blurred, blur it // if the actors is not blurred, blur it
this.blur_panel(panel); this.blur_panel(panel);
else
// if it is blurred, update the blur anyway
this.change_blur_type(actors);
} }
/// Blur a panel /// Blur a panel
@ -145,71 +126,74 @@ export const PanelBlur = class PanelBlur {
panel_box = panel_box.get_parent(); panel_box = panel_box.get_parent();
} }
let monitor = this.find_monitor_for(panel); let monitor = Main.layoutManager.findMonitorForActor(panel);
if (!monitor) if (!monitor)
return; return;
let background_parent = new St.Widget({ let background_group = new Meta.BackgroundGroup(
name: 'topbar-blurred-background-parent', { name: 'bms-panel-backgroundgroup', width: 0, height: 0 }
x: 0, y: 0, width: 0, height: 0 );
});
let background = this.settings.panel.STATIC_BLUR let background, bg_manager;
? new Meta.BackgroundActor({ let static_blur = this.settings.panel.STATIC_BLUR;
meta_display: global.display, if (static_blur) {
monitor: monitor.index let bg_manager_list = [];
}) const pipeline = new Pipeline(
: new St.Widget; this.effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.panel.PIPELINE
);
background = pipeline.create_background_with_effects(
monitor.index, bg_manager_list,
background_group, 'bms-panel-blurred-widget'
);
bg_manager = bg_manager_list[0];
}
else {
const pipeline = new DummyPipeline(this.effects_manager, this.settings.panel);
[background, bg_manager] = pipeline.create_background_with_effect(
background_group, 'bms-panel-blurred-widget'
);
background_parent.add_child(background); let paint_signals = new PaintSignals(this.connections);
// insert background parent // HACK
panel_box.insert_child_at_index(background_parent, 0); //
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
//
// This does not entirely fix this bug (shadows caused by windows
// still cause artifacts), but it prevents the shadows of the panel
// buttons to cause artifacts on the panel itself
//
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
let blur = new Shell.BlurEffect({ {
brightness: this.settings.panel.CUSTOMIZE if (this.settings.HACKS_LEVEL === 1) {
? this.settings.panel.BRIGHTNESS this._log("panel hack level 1");
: this.settings.BRIGHTNESS,
radius: (this.settings.panel.CUSTOMIZE
? this.settings.panel.SIGMA
: this.settings.SIGMA) * 2 * monitor.geometry_scale,
mode: this.settings.panel.STATIC_BLUR
? Shell.BlurMode.ACTOR
: Shell.BlurMode.BACKGROUND
});
// store the scale in the effect in order to retrieve it in set_sigma paint_signals.disconnect_all();
blur.scale = monitor.geometry_scale; paint_signals.connect(background, pipeline.effect);
} else {
paint_signals.disconnect_all();
}
}
}
let color = this.effects_manager.new_color_effect({ // insert the background group to the panel box
color: this.settings.panel.CUSTOMIZE panel_box.insert_child_at_index(background_group, 0);
? this.settings.panel.COLOR
: this.settings.COLOR
}, this.settings);
let noise = this.effects_manager.new_noise_effect({
noise: this.settings.panel.CUSTOMIZE
? this.settings.panel.NOISE_AMOUNT
: this.settings.NOISE_AMOUNT,
lightness: this.settings.panel.CUSTOMIZE
? this.settings.panel.NOISE_LIGHTNESS
: this.settings.NOISE_LIGHTNESS
}, this.settings);
let paint_signals = new PaintSignals(this.connections);
// the object that is used to remembering each elements that is linked to the blur effect
let actors = { let actors = {
widgets: { panel, panel_box, background, background_parent }, widgets: { panel, panel_box, background, background_group },
effects: { blur, color, noise }, static_blur,
paint_signals,
monitor, monitor,
bg_manager,
is_dtp_panel is_dtp_panel
}; };
this.actors_list.push(actors); this.actors_list.push(actors);
// perform updates // update the size of the actor
this.change_blur_type(actors); this.update_size(actors);
// connect to panel, panel_box and its parent position or size change // connect to panel, panel_box and its parent position or size change
// this should fire update_size every time one of its params change // this should fire update_size every time one of its params change
@ -228,157 +212,47 @@ export const PanelBlur = class PanelBlur {
'notify::position', 'notify::position',
_ => this.update_size(actors) _ => this.update_size(actors)
); );
}
update_all_blur_type() { // connect to the panel getting destroyed
this.actors_list.forEach(actors => this.change_blur_type(actors)); this.connections.connect(
} panel,
'destroy',
change_blur_type(actors) { _ => this.destroy_blur(actors, true)
let is_static = this.settings.panel.STATIC_BLUR; );
// reset widgets to right state
actors.widgets.background_parent.remove_child(actors.widgets.background);
this.effects_manager.remove(actors.effects.blur);
this.effects_manager.remove(actors.effects.color);
this.effects_manager.remove(actors.effects.noise);
// create new background actor
actors.widgets.background = is_static
? new Meta.BackgroundActor({
meta_display: global.display,
monitor: this.find_monitor_for(actors.widgets.panel).index
})
: new St.Widget;
// change blur mode
actors.effects.blur.set_mode(is_static ? 0 : 1);
// disable other effects if the blur is dynamic, as they makes it opaque
actors.effects.color._static = is_static;
actors.effects.noise._static = is_static;
actors.effects.color.update_enabled();
actors.effects.noise.update_enabled();
// add the effects in order
actors.widgets.background.add_effect(actors.effects.color);
actors.widgets.background.add_effect(actors.effects.noise);
actors.widgets.background.add_effect(actors.effects.blur);
// add the background actor behing the panel
actors.widgets.background_parent.add_child(actors.widgets.background);
// perform updates
this.update_wallpaper(actors);
this.update_size(actors);
// HACK
//
//`Shell.BlurEffect` does not repaint when shadows are under it. [1]
//
// This does not entirely fix this bug (shadows caused by windows
// still cause artifacts), but it prevents the shadows of the panel
// buttons to cause artifacts on the panel itself
//
// [1]: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2857
if (!is_static) {
if (this.settings.HACKS_LEVEL === 1) {
this._log("panel hack level 1");
actors.paint_signals.disconnect_all();
let rp = () => { actors.effects.blur.queue_repaint(); };
this.connections.connect(actors.widgets.panel, [
'enter-event', 'leave-event', 'button-press-event'
], rp);
actors.widgets.panel.get_children().forEach(child => {
this.connections.connect(child, [
'enter-event', 'leave-event', 'button-press-event'
], rp);
});
} else if (this.settings.HACKS_LEVEL === 2) {
this._log("panel hack level 2");
actors.paint_signals.disconnect_all();
actors.paint_signals.connect(
actors.widgets.background, actors.effects.blur
);
} else {
actors.paint_signals.disconnect_all();
}
}
}
update_wallpaper(actors) {
// if static blur, get right wallpaper and update blur with it
if (this.settings.panel.STATIC_BLUR) {
let bg = Main.layoutManager._backgroundGroup.get_child_at_index(
Main.layoutManager.monitors.length
- this.find_monitor_for(actors.widgets.panel).index - 1
);
if (bg && bg.get_content())
actors.widgets.background.content.set({
background: bg.get_content().background
});
else
this._warn("could not get background for panel");
}
} }
update_size(actors) { update_size(actors) {
let panel = actors.widgets.panel; let panel = actors.widgets.panel;
let panel_box = actors.widgets.panel_box; let panel_box = actors.widgets.panel_box;
let background = actors.widgets.background; let background = actors.widgets.background;
let monitor = this.find_monitor_for(panel);
if (!monitor)
return;
let [width, height] = panel_box.get_size(); let [width, height] = panel_box.get_size();
background.width = width;
background.height = height;
// if static blur, need to clip the background // if static blur, need to clip the background
if (this.settings.panel.STATIC_BLUR) { if (actors.static_blur) {
let monitor = Main.layoutManager.findMonitorForActor(panel);
if (!monitor)
return;
// an alternative to panel.get_transformed_position, because it // an alternative to panel.get_transformed_position, because it
// sometimes yields NaN (probably when the actor is not fully // sometimes yields NaN (probably when the actor is not fully
// positionned yet) // positionned yet)
let [p_x, p_y] = panel_box.get_position(); let [p_x, p_y] = panel_box.get_position();
let [p_p_x, p_p_y] = panel_box.get_parent().get_position(); let [p_p_x, p_p_y] = panel_box.get_parent().get_position();
let x = p_x + p_p_x - monitor.x; let x = p_x + p_p_x - monitor.x + (width - panel.width) / 2;
let y = p_y + p_p_y - monitor.y; let y = p_y + p_p_y - monitor.y + (height - panel.height) / 2;
background.set_clip(x, y, width, height); background.set_clip(x, y, panel.width, panel.height);
background.x = -x; background.x = (width - panel.width) / 2 - x;
background.y = -y; background.y = .5 + (height - panel.height) / 2 - y;
// fixes a bug where the blur is washed away when changing the sigma
this.invalidate_blur(actors);
} else { } else {
background.x = panel.x; background.x = panel.x;
background.y = panel.y; background.y = panel.y;
background.width = panel.width;
background.height = panel.height;
} }
// update the monitor panel is on // update the monitor panel is on
actors.monitor = this.find_monitor_for(panel); actors.monitor = Main.layoutManager.findMonitorForActor(panel);
}
/// An helper function to find the monitor in which an actor is situated,
/// there might be a pre-existing function in GLib already
find_monitor_for(actor) {
let extents = actor.get_transformed_extents();
let rect = new Mtk.Rectangle({
x: extents.get_x(),
y: extents.get_y(),
width: extents.get_width(),
height: extents.get_height(),
});
let index = global.display.get_monitor_index_for_rect(rect);
return Main.layoutManager.monitors[index];
} }
/// Connect when overview if opened/closed to hide/show the blur accordingly /// Connect when overview if opened/closed to hide/show the blur accordingly
@ -396,22 +270,22 @@ export const PanelBlur = class PanelBlur {
) { ) {
if (!this.settings.hidetopbar.COMPATIBILITY) { if (!this.settings.hidetopbar.COMPATIBILITY) {
this.connections.connect( this.connections.connect(
Main.overview, 'showing', this.hide.bind(this) Main.overview, 'showing', _ => this.hide()
); );
this.connections.connect( this.connections.connect(
Main.overview, 'hidden', this.show.bind(this) Main.overview, 'hidden', _ => this.show()
); );
} else { } else {
let appDisplay = Main.overview._overview._controls._appDisplay; let appDisplay = Main.overview._overview._controls._appDisplay;
this.connections.connect( this.connections.connect(
appDisplay, 'show', this.hide.bind(this) appDisplay, 'show', _ => this.hide()
); );
this.connections.connect( this.connections.connect(
appDisplay, 'hide', this.show.bind(this) appDisplay, 'hide', _ => this.show()
); );
this.connections.connect( this.connections.connect(
Main.overview, 'hidden', this.show.bind(this) Main.overview, 'hidden', _ => this.show()
); );
} }
@ -425,12 +299,12 @@ export const PanelBlur = class PanelBlur {
) { ) {
// connect to overview opening/closing // connect to overview opening/closing
this.connections.connect(Main.overview, ['showing', 'hiding'], this.connections.connect(Main.overview, ['showing', 'hiding'],
this.update_visibility.bind(this) _ => this.update_visibility()
); );
// connect to session mode update // connect to session mode update
this.connections.connect(Main.sessionMode, 'updated', this.connections.connect(Main.sessionMode, 'updated',
this.update_visibility.bind(this) _ => this.update_visibility()
); );
// manage already-existing windows // manage already-existing windows
@ -450,7 +324,7 @@ export const PanelBlur = class PanelBlur {
// connect to a workspace change // connect to a workspace change
this.connections.connect(global.window_manager, 'switch-workspace', this.connections.connect(global.window_manager, 'switch-workspace',
this.update_visibility.bind(this) _ => this.update_visibility()
); );
// perform early update // perform early update
@ -600,75 +474,45 @@ export const PanelBlur = class PanelBlur {
); );
} }
/// Fixes a bug where the blur is washed away when changing the sigma, or update_pipeline() {
/// enabling/disabling other effects. this.actors_list.forEach(actors =>
invalidate_blur(actors) { actors.bg_manager._bms_pipeline.change_pipeline_to(
if (this.settings.panel.STATIC_BLUR && actors.widgets.background) this.settings.panel.PIPELINE
actors.widgets.background.get_content()?.invalidate(); )
} );
invalidate_all_blur() {
this.actors_list.forEach(actors => this.invalidate_blur(actors));
}
set_sigma(s) {
this.actors_list.forEach(actors => {
actors.effects.blur.radius = s * 2 * actors.effects.blur.scale;
this.invalidate_blur(actors);
});
}
set_brightness(b) {
this.actors_list.forEach(actors => {
actors.effects.blur.brightness = b;
});
}
set_color(c) {
this.actors_list.forEach(actors => {
actors.effects.color.color = c;
});
}
set_noise_amount(n) {
this.actors_list.forEach(actors => {
actors.effects.noise.noise = n;
});
}
set_noise_lightness(l) {
this.actors_list.forEach(actors => {
actors.effects.noise.lightness = l;
});
} }
show() { show() {
this.actors_list.forEach(actors => { this.actors_list.forEach(actors => {
actors.widgets.background_parent.show(); actors.widgets.background.show();
}); });
} }
hide() { hide() {
this.actors_list.forEach(actors => { this.actors_list.forEach(actors => {
actors.widgets.background_parent.hide(); actors.widgets.background.hide();
}); });
} }
// destroy every blurred background left, necessary after sleep // IMPORTANT: do never call this in a mutable `this.actors_list.forEach`
destroy_blur_effects() { destroy_blur(actors, panel_already_destroyed) {
Main.panel?.get_parent()?.get_children().forEach( this.set_should_override_panel(actors, false);
child => {
if (child.name === 'topbar-blurred-background-parent') { actors.bg_manager._bms_pipeline.destroy();
child.get_children().forEach(meta_background_actor => {
meta_background_actor.get_effects().forEach(effect => { if (panel_already_destroyed)
this.effects_manager.remove(effect); actors.bg_manager.backgroundActor = null;
}); actors.bg_manager.destroy();
});
child.destroy_all_children(); if (!panel_already_destroyed) {
child.destroy(); actors.widgets.panel_box.remove_child(actors.widgets.background_group);
} actors.widgets.background_group.destroy_all_children();
} actors.widgets.background_group.destroy();
); }
let index = this.actors_list.indexOf(actors);
if (index >= 0)
this.actors_list.splice(index, 1);
} }
disable() { disable() {
@ -678,21 +522,8 @@ export const PanelBlur = class PanelBlur {
this.update_light_text_classname(true); this.update_light_text_classname(true);
this.actors_list.forEach(actors => { const immutable_actors_list = [...this.actors_list];
this.set_should_override_panel(actors, false); immutable_actors_list.forEach(actors => this.destroy_blur(actors, false));
this.effects_manager.remove(actors.effects.noise);
this.effects_manager.remove(actors.effects.color);
this.effects_manager.remove(actors.effects.blur);
try {
actors.widgets.panel_box.remove_child(
actors.widgets.background_parent
);
} catch (e) { }
actors.widgets.background_parent?.destroy();
});
this.destroy_blur_effects();
this.actors_list = []; this.actors_list = [];
this.connections.disconnect_all(); this.connections.disconnect_all();

View File

@ -1,41 +1,21 @@
import Shell from 'gi://Shell';
import Meta from 'gi://Meta';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { Pipeline } from '../conveniences/pipeline.js';
export const ScreenshotBlur = class ScreenshotBlur { export const ScreenshotBlur = class ScreenshotBlur {
constructor(connections, settings, effects_manager) { constructor(connections, settings, effects_manager) {
this.connections = connections; this.connections = connections;
this.effects = [];
this.settings = settings; this.settings = settings;
this.screenshot_background_managers = [];
this.effects_manager = effects_manager; this.effects_manager = effects_manager;
} }
enable() { enable() {
this._log("blurring screenshot's window selector"); this._log("blurring screenshot's window selector");
// connect to every background change (even without changing image)
// FIXME this signal is fired very often, so we should find another one
// fired only when necessary (but that still catches all cases)
this.connections.connect(
Main.layoutManager._backgroundGroup,
'notify',
_ => {
this._log("updated background for screenshot's window selector");
this.update_backgrounds();
}
);
// connect to monitors change // connect to monitors change
this.connections.connect( this.connections.connect(Main.layoutManager, 'monitors-changed',
Main.layoutManager, _ => this.update_backgrounds()
'monitors-changed',
_ => {
if (Main.screenShield && !Main.screenShield.locked) {
this._log("changed monitors for screenshot's window selector");
this.update_backgrounds();
}
}
); );
// update backgrounds when the component is enabled // update backgrounds when the component is enabled
@ -44,121 +24,78 @@ export const ScreenshotBlur = class ScreenshotBlur {
update_backgrounds() { update_backgrounds() {
// remove every old background // remove every old background
this.remove(); this.remove_background_actors();
// create new backgrounds for the screenshot window selector
// add new backgrounds
for (let i = 0; i < Main.screenshotUI._windowSelectors.length; i++) { for (let i = 0; i < Main.screenshotUI._windowSelectors.length; i++) {
const actor = Main.screenshotUI._windowSelectors[i]; const window_selector = Main.screenshotUI._windowSelectors[i];
const monitor = Main.layoutManager.monitors[i]; const pipeline = new Pipeline(
this.effects_manager,
global.blur_my_shell._pipelines_manager,
this.settings.screenshot.PIPELINE
);
pipeline.create_background_with_effects(
window_selector._monitorIndex, this.screenshot_background_managers,
window_selector, 'bms-screenshot-blurred-widget', false
);
if (!monitor) // prevent old `BackgroundActor` from being accessed, which creates a whole bug of logs
continue; this.connections.connect(window_selector.get_parent(), 'destroy', _ => {
this.screenshot_background_managers.forEach(background_manager => {
if (background_manager.backgroundActor) {
let widget = background_manager.backgroundActor.get_parent();
let parent = widget?.get_parent();
const bg_actor = this.create_background_actor(monitor); if (parent == window_selector) {
actor.insert_child_at_index(bg_actor, 0); background_manager._bms_pipeline.destroy();
actor._blur_actor = bg_actor; parent.remove_child(widget);
}
}
background_manager.destroy();
});
window_selector.get_children().forEach(child => {
if (child.get_name() == 'bms-screenshot-blurred-widget')
window_selector.remove_child(child);
});
let index = this.screenshot_background_managers.indexOf(window_selector);
this.screenshot_background_managers.splice(index, 1);
});
} }
} }
create_background_actor(monitor) { update_pipeline() {
let bg_actor = new Meta.BackgroundActor({ this.screenshot_background_managers.forEach(background_manager =>
meta_display: global.display, background_manager._bms_pipeline.change_pipeline_to(
monitor: monitor.index this.settings.screenshot.PIPELINE
)
);
}
remove_background_actors() {
this.screenshot_background_managers.forEach(background_manager => {
background_manager._bms_pipeline.destroy();
if (background_manager.backgroundActor) {
let widget = background_manager.backgroundActor.get_parent();
widget?.get_parent()?.remove_child(widget);
}
background_manager.destroy();
}); });
let background = Main.layoutManager._backgroundGroup.get_child_at_index(
Main.layoutManager.monitors.length - monitor.index - 1 Main.screenshotUI._windowSelectors.forEach(window_selector =>
window_selector.get_children().forEach(child => {
if (child.get_name() == 'bms-screenshot-blurred-widget')
window_selector.remove_child(child);
})
); );
if (!background) { this.screenshot_background_managers = [];
this._warn("could not get background for screenshot's window selector");
return bg_actor;
}
bg_actor.content.set({
background: background.get_content().background
});
let blur_effect = new Shell.BlurEffect({
brightness: this.settings.screenshot.CUSTOMIZE
? this.settings.screenshot.BRIGHTNESS
: this.settings.BRIGHTNESS,
radius: (this.settings.screenshot.CUSTOMIZE
? this.settings.screenshot.SIGMA
: this.settings.SIGMA) * 2 * monitor.geometry_scale,
mode: Shell.BlurMode.ACTOR
});
// store the scale in the effect in order to retrieve it in set_sigma
blur_effect.scale = monitor.geometry_scale;
let color_effect = this.effects_manager.new_color_effect({
color: this.settings.screenshot.CUSTOMIZE
? this.settings.screenshot.COLOR
: this.settings.COLOR
}, this.settings);
let noise_effect = this.effects_manager.new_noise_effect({
noise: this.settings.screenshot.CUSTOMIZE
? this.settings.screenshot.NOISE_AMOUNT
: this.settings.NOISE_AMOUNT,
lightness: this.settings.screenshot.CUSTOMIZE
? this.settings.screenshot.NOISE_LIGHTNESS
: this.settings.NOISE_LIGHTNESS
}, this.settings);
bg_actor.add_effect(color_effect);
bg_actor.add_effect(noise_effect);
bg_actor.add_effect(blur_effect);
this.effects.push({ blur_effect, color_effect, noise_effect });
return bg_actor;
}
set_sigma(s) {
this.effects.forEach(effect => {
effect.blur_effect.radius = s * 2 * effect.blur_effect;
});
}
set_brightness(b) {
this.effects.forEach(effect => {
effect.blur_effect.brightness = b;
});
}
set_color(c) {
this.effects.forEach(effect => {
effect.color_effect.color = c;
});
}
set_noise_amount(n) {
this.effects.forEach(effect => {
effect.noise_effect.noise = n;
});
}
set_noise_lightness(l) {
this.effects.forEach(effect => {
effect.noise_effect.lightness = l;
});
}
remove() {
Main.screenshotUI._windowSelectors.forEach(actor => {
if (actor._blur_actor) {
actor.remove_child(actor._blur_actor);
actor._blur_actor.destroy();
delete actor._blur_actor;
}
});
this.effects = [];
} }
disable() { disable() {
this._log("removing blur from screenshot's window selector"); this._log("removing blur from screenshot's window selector");
this.remove(); this.remove_background_actors();
this.connections.disconnect_all(); this.connections.disconnect_all();
} }

View File

@ -1,15 +1,16 @@
import Shell from 'gi://Shell';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { PaintSignals } from '../effects/paint_signals.js'; import { PaintSignals } from '../conveniences/paint_signals.js';
import { DummyPipeline } from '../conveniences/dummy_pipeline.js';
export const WindowListBlur = class WindowListBlur { export const WindowListBlur = class WindowListBlur {
constructor(connections, settings, _) { constructor(connections, settings, effects_manager) {
this.connections = connections; this.connections = connections;
this.settings = settings; this.settings = settings;
this.paint_signals = new PaintSignals(connections); this.paint_signals = new PaintSignals(connections);
this.effects = []; this.effects_manager = effects_manager;
this.pipelines = [];
} }
enable() { enable() {
@ -37,36 +38,35 @@ export const WindowListBlur = class WindowListBlur {
}); });
} }
try_blur(child) { try_blur(actor) {
if ( if (
child.constructor.name === "WindowList" && actor.constructor.name === "WindowList" &&
child.style !== "background:transparent;" actor.style !== "background:transparent;"
) { ) {
this._log("found window list to blur"); this._log("found window list to blur");
let blur_effect = new Shell.BlurEffect({ const pipeline = new DummyPipeline(
name: 'window-list-blur', this.effects_manager, this.settings.window_list
radius: (this.settings.window_list.CUSTOMIZE );
? this.settings.window_list.SIGMA pipeline.attach_effect_to_actor(actor);
: this.settings.SIGMA) * 2, this.pipelines.push(pipeline);
brightness: this.settings.window_list.CUSTOMIZE
? this.settings.window_list.BRIGHTNESS
: this.settings.BRIGHTNESS,
mode: Shell.BlurMode.BACKGROUND
});
child.set_style("background:transparent;"); actor.set_style("background:transparent;");
child.add_effect(blur_effect);
this.effects.push({ blur_effect });
child._windowList.get_children().forEach( actor._windowList.get_children().forEach(
window => this.blur_window_button(window) window => this.style_window_button(window)
); );
this.connections.connect( this.connections.connect(
child._windowList, actor._windowList,
'child-added', 'child-added',
(_, window) => this.blur_window_button(window) (_, window) => this.style_window_button(window)
);
this.connections.connect(
actor,
'destroy',
_ => this.destroy_blur(pipeline, true)
); );
@ -83,75 +83,61 @@ export const WindowListBlur = class WindowListBlur {
if (this.settings.HACKS_LEVEL === 1) { if (this.settings.HACKS_LEVEL === 1) {
this._log("window list hack level 1"); this._log("window list hack level 1");
this.paint_signals.connect(child, blur_effect); this.paint_signals.disconnect_all_for_actor(actor);
this.paint_signals.connect(actor, pipeline.effect);
} else if (this.settings.HACKS_LEVEL === 2) {
this._log("window list hack level 2");
this.paint_signals.connect(child, blur_effect);
} else { } else {
this.paint_signals.disconnect_all(); this.paint_signals.disconnect_all_for_actor(actor);
} }
} }
} }
blur_window_button(window) { style_window_button(window) {
window.get_child_at_index(0).set_style( window.get_child_at_index(0).set_style(
"box-shadow:none; background-color:rgba(0,0,0,0.2); border-radius:5px;" "box-shadow:none; background-color:rgba(0,0,0,0.2); border-radius:5px;"
); );
} }
try_remove_blur(child) { // IMPORTANT: do never call this in a mutable `this.pipelines.forEach`
if ( destroy_blur(pipeline, actor_destroyed = false) {
child.constructor.name === "WindowList" && if (!actor_destroyed) {
child.style === "background:transparent;" this.remove_style(pipeline.actor);
) { this.paint_signals.disconnect_all_for_actor(pipeline.actor);
child.style = null; }
child.remove_effect_by_name('window-list-blur');
child._windowList.get_children().forEach( pipeline.destroy();
let index = this.pipelines.indexOf(pipeline);
if (index >= 0)
this.pipelines.splice(pipeline, 1);
}
remove_style(actor) {
if (
actor.constructor.name === "WindowList" &&
actor.style === "background:transparent;"
) {
actor.style = null;
actor._windowList.get_children().forEach(
child => child.get_child_at_index(0).set_style(null) child => child.get_child_at_index(0).set_style(null)
); );
} }
} }
set_sigma(s) {
this.effects.forEach(effect => {
effect.blur_effect.radius = s * 2;
});
}
set_brightness(b) {
this.effects.forEach(effect => {
effect.blur_effect.brightness = b;
});
}
// not implemented for dynamic blur
set_color(c) { }
set_noise_amount(n) { }
set_noise_lightness(l) { }
hide() { hide() {
this.set_sigma(0); this.pipelines.forEach(pipeline => pipeline.effect?.set_enabled(false));
} }
show() { show() {
this.set_sigma( this.pipelines.forEach(pipeline => pipeline.effect?.set_enabled(true));
this.settings.window_list.CUSTOMIZE
? this.settings.window_list.SIGMA
: this.settings.SIGMA
);
} }
disable() { disable() {
this._log("removing blur from window list"); this._log("removing blur from window list");
Main.layoutManager.uiGroup.get_children().forEach( const immutable_pipelines_list = [...this.pipelines];
child => this.try_remove_blur(child) immutable_pipelines_list.forEach(pipeline => this.destroy_blur(pipeline));
);
this.effects = []; this.pipelines = [];
this.connections.disconnect_all(); this.connections.disconnect_all();
} }

View File

@ -53,6 +53,7 @@ export const Connections = class Connections {
this.buffer.splice(index, 1); this.buffer.splice(index, 1);
} }
}); });
infos.destroy_id = destroy_id;
} }
this.buffer.push(infos); this.buffer.push(infos);
@ -66,10 +67,12 @@ export const Connections = class Connections {
); );
// remove each of them // remove each of them
actor_connections.forEach((connection) => { actor_connections.forEach(connection => {
// disconnect // disconnect
try { try {
connection.actor.disconnect(connection.id); connection.actor.disconnect(connection.id);
if ('destroy_id' in connection)
connection.actor.disconnect(connection.destroy_id);
} catch (e) { } catch (e) {
this._warn(`error removing connection: ${e}; continuing`); this._warn(`error removing connection: ${e}; continuing`);
} }
@ -82,10 +85,12 @@ export const Connections = class Connections {
/// Disconnect every connection for each actor. /// Disconnect every connection for each actor.
disconnect_all() { disconnect_all() {
this.buffer.forEach((connection) => { this.buffer.forEach(connection => {
// disconnect // disconnect
try { try {
connection.actor.disconnect(connection.id); connection.actor.disconnect(connection.id);
if ('destroy_id' in connection)
connection.actor.disconnect(connection.destroy_id);
} catch (e) { } catch (e) {
this._warn(`error removing connection: ${e}; continuing`); this._warn(`error removing connection: ${e}; continuing`);
} }

View File

@ -0,0 +1,112 @@
import St from 'gi://St';
import Clutter from 'gi://Clutter';
/// A dummy `Pipeline`, for dynamic blur only.
/// Instead of a pipeline id, we take the settings of the component we want to blur.
export const DummyPipeline = class DummyPipeline {
constructor(effects_manager, settings, actor = null) {
this.effects_manager = effects_manager;
this.settings = settings;
this.effect = null;
this.attach_effect_to_actor(actor);
}
create_background_with_effect(
background_group,
widget_name
) {
// create the new actor
this.actor = new St.Widget({ name: widget_name });
this.attach_effect_to_actor(this.actor);
// a dummy `BackgroundManager`, just to access the pipeline easily
let bg_manager = new Clutter.Actor;
bg_manager.backgroundActor = this.actor;
bg_manager._bms_pipeline = this;
background_group.insert_child_at_index(this.actor, 0);
return [this.actor, bg_manager];
};
attach_effect_to_actor(actor) {
// set the actor
if (actor)
this.actor = actor;
else {
this.remove_pipeline_from_actor();
return;
}
// build the new effect to be added
this.build_effect({
unscaled_radius: 2 * this.settings.SIGMA,
brightness: this.settings.BRIGHTNESS,
});
this.actor_destroy_id = this.actor.connect(
"destroy", () => this.remove_pipeline_from_actor()
);
// add the effect to the actor
if (this.actor)
this.actor.add_effect(this.effect);
else
this._warn(`could not add effect to actor, actor does not exist anymore`);
}
remove_pipeline_from_actor() {
this.remove_effect();
if (this.actor && this.actor_destroy_id)
this.actor.disconnect(this.actor_destroy_id);
this.actor_destroy_id = null;
this.actor = null;
}
build_effect(params) {
// create the effect
this.effect = this.effects_manager.new_native_dynamic_gaussian_blur_effect(params);
// connect to settings changes, using the true gsettings object
this._sigma_changed_id = this.settings.settings.connect(
'changed::sigma', () => this.effect.unscaled_radius = 2 * this.settings.SIGMA
);
this._brightness_changed_id = this.settings.settings.connect(
'changed::brightness', () => this.effect.brightness = this.settings.BRIGHTNESS
);
}
repaint_effect() {
this.effect?.queue_repaint();
}
/// Remove every effect from the actor it is attached to. Please note that they are not
/// destroyed, but rather stored (thanks to the `EffectManager` class) to be reused later.
remove_effect() {
if (this.effect)
this.effects_manager.remove(this.effect);
this.effect = null;
if (this._sigma_changed_id)
this.settings.settings.disconnect(this._sigma_changed_id);
if (this._brightness_changed_id)
this.settings.settings.disconnect(this._brightness_changed_id);
delete this._sigma_changed_id;
delete this._brightness_changed_id;
}
/// Do nothing for this dummy pipeline.
/// Note: exposed to public API.
change_pipeline_to() { return; }
/// Note: exposed to public API.
destroy() {
this.remove_effect();
this.remove_pipeline_from_actor();
}
_warn(str) {
console.warn(`[Blur my Shell > dummy pip] ${str}`);
}
};

View File

@ -1,21 +1,41 @@
import { ColorEffect } from '../effects/color_effect.js'; import { get_supported_effects } from '../effects/effects.js';
import { NoiseEffect } from '../effects/noise_effect.js';
/// An object to manage effects (by not destroying them all the time) /// An object to manage effects (by not destroying them all the time)
export const EffectsManager = class EffectsManager { export const EffectsManager = class EffectsManager {
constructor(connections) { constructor(connections) {
this.connections = connections; this.connections = connections;
this.used = []; this.used = [];
this.color_effects = []; this.SUPPORTED_EFFECTS = get_supported_effects();
this.noise_effects = [];
Object.keys(this.SUPPORTED_EFFECTS).forEach(effect_name => {
// init the arrays containing each unused effect
this[effect_name + '_effects'] = [];
// init the functions for each effect
this['new_' + effect_name + '_effect'] = function (params) {
let effect;
if (this[effect_name + '_effects'].length > 0) {
effect = this[effect_name + '_effects'].splice(0, 1)[0];
effect.set({
...this.SUPPORTED_EFFECTS[effect_name].class.default_params, ...params
});
} else
effect = new this.SUPPORTED_EFFECTS[effect_name].class({
...this.SUPPORTED_EFFECTS[effect_name].class.default_params, ...params
});
this.used.push(effect);
this.connect_to_destroy(effect);
return effect;
};
});
} }
connect_to_destroy(effect) { connect_to_destroy(effect) {
effect.old_actor = effect.get_actor(); effect.old_actor = effect.get_actor();
if (effect.old_actor) if (effect.old_actor)
effect.old_actor_id = effect.old_actor.connect('destroy', _ => { effect.old_actor_id = effect.old_actor.connect('destroy', _ => {
this.remove(effect); this.remove(effect, true);
}); });
this.connections.connect(effect, 'notify::actor', _ => { this.connections.connect(effect, 'notify::actor', _ => {
@ -24,42 +44,22 @@ export const EffectsManager = class EffectsManager {
if (effect.old_actor && actor != effect.old_actor) if (effect.old_actor && actor != effect.old_actor)
effect.old_actor.disconnect(effect.old_actor_id); effect.old_actor.disconnect(effect.old_actor_id);
if (actor) { if (actor && actor != effect.old_actor) {
effect.old_actor_id = actor.connect('destroy', _ => { effect.old_actor_id = actor.connect('destroy', _ => {
this.remove(effect); this.remove(effect, true);
}); });
} }
}); });
} }
new_color_effect(params, settings) { // IMPORTANT: do never call this in a mutable `this.used.forEach`
let effect; remove(effect, actor_already_destroyed = false) {
if (this.color_effects.length > 0) { if (!actor_already_destroyed)
effect = this.color_effects.splice(0, 1)[0]; try {
effect.set(params); effect.get_actor()?.remove_effect(effect);
} else } catch (e) {
effect = new ColorEffect(params, settings); this._warn(`could not remove the effect, continuing: ${e}`);
}
this.used.push(effect);
this.connect_to_destroy(effect);
return effect;
}
new_noise_effect(params, settings) {
let effect;
if (this.noise_effects.length > 0) {
effect = this.noise_effects.splice(0, 1)[0];
effect.set(params);
} else
effect = new NoiseEffect(params, settings);
this.used.push(effect);
this.connect_to_destroy(effect);
return effect;
}
remove(effect) {
effect.get_actor()?.remove_effect(effect);
if (effect.old_actor) if (effect.old_actor)
effect.old_actor.disconnect(effect.old_actor_id); effect.old_actor.disconnect(effect.old_actor_id);
delete effect.old_actor; delete effect.old_actor;
@ -69,21 +69,22 @@ export const EffectsManager = class EffectsManager {
if (index >= 0) { if (index >= 0) {
this.used.splice(index, 1); this.used.splice(index, 1);
if (effect instanceof ColorEffect) Object.keys(this.SUPPORTED_EFFECTS).forEach(effect_name => {
this.color_effects.push(effect); if (effect instanceof this.SUPPORTED_EFFECTS[effect_name].class)
else if (effect instanceof NoiseEffect) this[effect_name + '_effects'].push(effect);
this.noise_effects.push(effect); });
} }
} }
destroy_all() { destroy_all() {
this.used.forEach(effect => { this.remove(effect); }); const immutable_used_list = [...this.used];
[ immutable_used_list.forEach(effect => this.remove(effect));
this.used, Object.keys(this.SUPPORTED_EFFECTS).forEach(effect_name => {
this.color_effects, this[effect_name + '_effects'].splice(0, this[effect_name + '_effects'].length);
this.noise_effects
].forEach(array => {
array.splice(0, array.length);
}); });
} }
_warn(str) {
console.warn(`[Blur my Shell > effects mng] ${str}`);
}
}; };

View File

@ -1,15 +1,10 @@
import { Type } from './settings.js'; import { Type } from './settings.js';
// This lists the preferences keys // This lists the preferences keys
export const Keys = [ export const KEYS = [
{ {
component: "general", schemas: [ component: "general", schemas: [
{ type: Type.I, name: "sigma" }, { type: Type.PIPELINES, name: "pipelines" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.B, name: "color-and-noise" },
{ type: Type.I, name: "hacks-level" }, { type: Type.I, name: "hacks-level" },
{ type: Type.B, name: "debug" }, { type: Type.B, name: "debug" },
] ]
@ -17,37 +12,25 @@ export const Keys = [
{ {
component: "overview", schemas: [ component: "overview", schemas: [
{ type: Type.B, name: "blur" }, { type: Type.B, name: "blur" },
{ type: Type.B, name: "customize" }, { type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.I, name: "style-components" }, { type: Type.I, name: "style-components" },
] ]
}, },
{ {
component: "appfolder", schemas: [ component: "appfolder", schemas: [
{ type: Type.B, name: "blur" }, { type: Type.B, name: "blur" },
{ type: Type.B, name: "customize" },
{ type: Type.I, name: "sigma" }, { type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" }, { type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.I, name: "style-dialogs" }, { type: Type.I, name: "style-dialogs" },
] ]
}, },
{ {
component: "panel", schemas: [ component: "panel", schemas: [
{ type: Type.B, name: "blur" }, { type: Type.B, name: "blur" },
{ type: Type.B, name: "customize" }, { type: Type.B, name: "static-blur" },
{ type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" }, { type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" }, { type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.B, name: "static-blur" },
{ type: Type.B, name: "unblur-in-overview" }, { type: Type.B, name: "unblur-in-overview" },
{ type: Type.B, name: "force-light-text" }, { type: Type.B, name: "force-light-text" },
{ type: Type.B, name: "override-background" }, { type: Type.B, name: "override-background" },
@ -58,29 +41,22 @@ export const Keys = [
{ {
component: "dash-to-dock", schemas: [ component: "dash-to-dock", schemas: [
{ type: Type.B, name: "blur" }, { type: Type.B, name: "blur" },
{ type: Type.B, name: "customize" }, { type: Type.B, name: "static-blur" },
{ type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" }, { type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" }, { type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.B, name: "static-blur" },
{ type: Type.B, name: "unblur-in-overview" }, { type: Type.B, name: "unblur-in-overview" },
{ type: Type.B, name: "override-background" }, { type: Type.B, name: "override-background" },
{ type: Type.I, name: "style-dash-to-dock" }, { type: Type.I, name: "style-dash-to-dock" },
{ type: Type.I, name: "corner-radius" },
] ]
}, },
{ {
component: "applications", schemas: [ component: "applications", schemas: [
{ type: Type.B, name: "blur" }, { type: Type.B, name: "blur" },
{ type: Type.B, name: "customize" },
{ type: Type.I, name: "sigma" }, { type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" }, { type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.I, name: "opacity" }, { type: Type.I, name: "opacity" },
{ type: Type.B, name: "dynamic-opacity" },
{ type: Type.B, name: "blur-on-overview" }, { type: Type.B, name: "blur-on-overview" },
{ type: Type.B, name: "enable-all" }, { type: Type.B, name: "enable-all" },
{ type: Type.AS, name: "whitelist" }, { type: Type.AS, name: "whitelist" },
@ -90,34 +66,27 @@ export const Keys = [
{ {
component: "lockscreen", schemas: [ component: "lockscreen", schemas: [
{ type: Type.B, name: "blur" }, { type: Type.B, name: "blur" },
{ type: Type.B, name: "customize" }, { type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
] ]
}, },
{ {
component: "window-list", schemas: [ component: "window-list", schemas: [
{ type: Type.B, name: "blur" }, { type: Type.B, name: "blur" },
{ type: Type.B, name: "customize" }, { type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" }, { type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" }, { type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" }, ]
{ type: Type.D, name: "noise-amount" }, },
{ type: Type.D, name: "noise-lightness" }, {
component: "coverflow-alt-tab", schemas: [
{ type: Type.B, name: "blur" },
{ type: Type.S, name: "pipeline" },
] ]
}, },
{ {
component: "screenshot", schemas: [ component: "screenshot", schemas: [
{ type: Type.B, name: "blur" }, { type: Type.B, name: "blur" },
{ type: Type.B, name: "customize" }, { type: Type.S, name: "pipeline" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
] ]
}, },
{ {
@ -130,4 +99,90 @@ export const Keys = [
{ type: Type.B, name: "blur-original-panel" }, { type: Type.B, name: "blur-original-panel" },
] ]
}, },
];
// This lists the deprecated preferences keys
export const DEPRECATED_KEYS = [
{
component: "general", schemas: [
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.B, name: "color-and-noise" },
]
},
{
component: "overview", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "appfolder", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "panel", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "dash-to-dock", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
{ type: Type.I, name: "corner-radius" },
]
},
{
component: "applications", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "lockscreen", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "window-list", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
{
component: "screenshot", schemas: [
{ type: Type.B, name: "customize" },
{ type: Type.I, name: "sigma" },
{ type: Type.D, name: "brightness" },
{ type: Type.C, name: "color" },
{ type: Type.D, name: "noise-amount" },
{ type: Type.D, name: "noise-lightness" },
]
},
]; ];

View File

@ -37,7 +37,8 @@ export const PaintSignals = class PaintSignals {
) )
) )
this.connections.connect(actor, 'destroy', () => { this.connections.connect(actor, 'destroy', () => {
this.buffer.forEach(infos => { const immutable_buffer = [...this.buffer];
immutable_buffer.forEach(infos => {
if (infos.actor === actor) { if (infos.actor === actor) {
// remove from buffer // remove from buffer
let index = this.buffer.indexOf(infos); let index = this.buffer.indexOf(infos);
@ -50,7 +51,8 @@ export const PaintSignals = class PaintSignals {
} }
disconnect_all_for_actor(actor) { disconnect_all_for_actor(actor) {
this.buffer.forEach(infos => { const immutable_buffer = [...this.buffer];
immutable_buffer.forEach(infos => {
if (infos.actor === actor) { if (infos.actor === actor) {
this.connections.disconnect_all_for(infos.paint_effect); this.connections.disconnect_all_for(infos.paint_effect);
infos.actor.remove_effect(infos.paint_effect); infos.actor.remove_effect(infos.paint_effect);

View File

@ -0,0 +1,223 @@
import St from 'gi://St';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Background from 'resource:///org/gnome/shell/ui/background.js';
/// A `Pipeline` object is a handy way to manage the effects attached to an actor. It only manages
/// one actor at a time (so blurring multiple widgets will need multiple `Pipeline`), and is
/// linked to a `pipeline_id` that has been (hopefully) defined in the settings.
///
/// It communicates with the settings through the `PipelinesManager` object, and receives different
/// signals (with `pipeline_id` being an unique string):
/// - `'pipeline_id'::pipeline-updated`, handing a new pipeline descriptor object, when the pipeline
/// has been changed enough that it needs to rebuild the effects configuration
/// - `'pipeline_id'::pipeline-destroyed`, when the pipeline has been destroyed; thus making the
/// `Pipeline` change its id to `pipeline_default`
///
/// And each effect, with an unique `id`, is connected to the `PipelinesManager` for the signals:
/// - `'pipeline_id'::effect-'id'-key-removed`, handing the key that was removed
/// - `'pipeline_id'::effect-'id'-key-updated`, handing the key that was changed and its new value
/// - `'pipeline_id'::effect-'id'-key-added`, handing the key that was added and its value
export const Pipeline = class Pipeline {
constructor(effects_manager, pipelines_manager, pipeline_id, actor = null) {
this.effects_manager = effects_manager;
this.pipelines_manager = pipelines_manager;
this.effects = [];
this.set_pipeline_id(pipeline_id);
this.attach_pipeline_to_actor(actor);
}
/// Create a background linked to the monitor with index `monitor_index`, with a
/// `BackgroundManager` that is appended to the list `background_managers`. The background actor
/// will be given the name `widget_name` and inserted into the given `background_group`.
/// If `use_absolute_position` is false, then the position used is at (0,0); useful when the
/// positioning is relative.
/// Note: exposed to public API.
create_background_with_effects(
monitor_index,
background_managers,
background_group,
widget_name,
use_absolute_position = true
) {
let monitor = Main.layoutManager.monitors[monitor_index];
// create the new actor
this.actor = new St.Widget({
name: widget_name,
x: use_absolute_position ? monitor.x : 0,
y: .5 + (use_absolute_position ? monitor.y : 0), // add 1 to correct z-position
z_position: 1, // seems to fix the multi-monitor glitch
width: monitor.width,
height: monitor.height
});
// remove the effects, wether or not we attach the pipeline to the actor: if they are fired
// while the actor has changed, this could go bad
this.remove_all_effects();
if (this.pipeline_id)
this.attach_pipeline_to_actor(this.actor);
let bg_manager = new Background.BackgroundManager({
container: this.actor,
monitorIndex: monitor_index,
controlPosition: false,
});
bg_manager._bms_pipeline = this;
background_managers.push(bg_manager);
background_group.insert_child_at_index(this.actor, 0);
return this.actor;
};
/// Set the pipeline id, correctly connecting the `Pipeline` object to listen the pipelines
/// manager for pipeline-wide changes. This does not update the effects in consequence, call
/// `change_pipeline_to` instead if you want to reconstruct the effects too.
set_pipeline_id(pipeline_id) {
// disconnect ancient signals
this.remove_connections();
// change the id
this.pipeline_id = pipeline_id;
// connect to settings changes
this._pipeline_changed_id = this.pipelines_manager.connect(
this.pipeline_id + '::pipeline-updated',
(_, new_pipeline) => this.update_effects_from_pipeline(new_pipeline)
);
this._pipeline_destroyed_id = this.pipelines_manager.connect(
this.pipeline_id + '::pipeline-destroyed',
_ => this.change_pipeline_to("pipeline_default")
);
}
/// Disconnect the signals for the pipeline changes. Please note that the signals related to the
/// effects are stored with them and removed with `remove_all_effects`.
remove_connections() {
if (this._pipeline_changed_id)
this.pipelines_manager.disconnect(this._pipeline_changed_id);
if (this._pipeline_destroyed_id)
this.pipelines_manager.disconnect(this._pipeline_destroyed_id);
this._pipeline_changed_id = null;
this._pipeline_destroyed_id = null;
}
/// Attach a Pipeline object with `pipeline_id` already set to an actor.
attach_pipeline_to_actor(actor) {
// set the actor
if (actor)
this.actor = actor;
else {
this.remove_pipeline_from_actor();
return;
}
// attach the pipeline
let pipeline = this.pipelines_manager.pipelines[this.pipeline_id];
if (!pipeline) {
this._warn(`could not attach pipeline to actor, pipeline "${this.pipeline_id}" not found`);
// do not recurse...
if ("pipeline_default" in this.pipelines_manager.pipelines) {
this.set_pipeline_id("pipeline_default");
pipeline = this.pipelines_manager.pipelines["pipeline_default"];
} else
return;
}
this.actor_destroy_id = this.actor.connect(
"destroy", () => this.remove_pipeline_from_actor()
);
// update the effects
this.update_effects_from_pipeline(pipeline);
}
remove_pipeline_from_actor() {
this.remove_all_effects();
if (this.actor && this.actor_destroy_id)
this.actor.disconnect(this.actor_destroy_id);
this.actor_destroy_id = null;
this.actor = null;
}
/// Update the effects from the given pipeline object, the hard way.
update_effects_from_pipeline(pipeline) {
// remove all effects
this.remove_all_effects();
// build the new effects to be added
pipeline.effects.forEach(effect => {
if ('new_' + effect.type + '_effect' in this.effects_manager)
this.build_effect(effect);
else
this._warn(`could not add effect to actor, effect "${effect.type}" not found`);
});
this.effects.reverse();
// add the effects to the actor
if (this.actor)
this.effects.forEach(effect => this.actor.add_effect(effect));
else
this._warn(`could not add effect to actor, actor does not exist anymore`);
}
/// Given an `effect_infos` object containing the effect type, id and params, build an effect
/// and append it to the effects list
build_effect(effect_infos) {
let effect = this.effects_manager['new_' + effect_infos.type + '_effect'](effect_infos.params);
this.effects.push(effect);
// connect to settings changes
effect._effect_key_removed_id = this.pipelines_manager.connect(
this.pipeline_id + '::effect-' + effect_infos.id + '-key-removed',
(_, key) => effect[key] = effect.constructor.default_params[key]
);
effect._effect_key_updated_id = this.pipelines_manager.connect(
this.pipeline_id + '::effect-' + effect_infos.id + '-key-updated',
(_, key, value) => effect[key] = value
);
effect._effect_key_added_id = this.pipelines_manager.connect(
this.pipeline_id + '::effect-' + effect_infos.id + '-key-added',
(_, key, value) => effect[key] = value
);
}
/// Remove every effect from the actor it is attached to. Please note that they are not
/// destroyed, but rather stored (thanks to the `EffectManager` class) to be reused later.
remove_all_effects() {
this.effects.forEach(effect => {
this.effects_manager.remove(effect);
[
effect._effect_key_removed_id,
effect._effect_key_updated_id,
effect._effect_key_added_id
].forEach(
id => { if (id) this.pipelines_manager.disconnect(id); }
);
delete effect._effect_key_removed_id;
delete effect._effect_key_updated_id;
delete effect._effect_key_added_id;
});
this.effects = [];
}
/// Change the pipeline id, and update the effects according to this change.
/// Note: exposed to public API.
change_pipeline_to(pipeline_id) {
this.set_pipeline_id(pipeline_id);
this.attach_pipeline_to_actor(this.actor);
}
/// Resets the `Pipeline` object to a sane state, removing every effect and signal.
/// Note: exposed to public API.
destroy() {
this.remove_all_effects();
this.remove_connections();
this.remove_pipeline_from_actor();
this.pipeline_id = null;
}
_warn(str) {
console.warn(`[Blur my Shell > pipeline] ${str}`);
}
};

View File

@ -0,0 +1,168 @@
const Signals = imports.signals;
/// The `PipelinesManager` object permits to store the list of pipelines and their effects in
/// memory. It is meant to *always* be in sync with the `org.gnome.shell.extensions.blur-my-shell`'s
/// `pipelines` gschema. However, we do not want to re-create every effect each time this schema is
/// changed, so the pipelines manager handles it, and dispatches the updates with targeted signals.
///
/// It is only connected to ONE signal (the pipelines schema being changed), and emits numerous
/// which are connected to by both the different `Pipeline` objects in the extension, and by the
/// different pages of the extension preferences.
/// It emits three different types of signals:
///
/// - general changes to the pipelines list, connected to by the extension preferences:
/// - `pipeline-list-changed`, when the list of pipelines has changed (by creation or deletion)
/// - `pipeline-names-changed`, when the name of a pipeline is changed
///
/// - signals that are targeted towards a given pipeline, with `pipeline_id` being its unique id:
/// - `'pipeline_id'::pipeline-updated`, handing a new pipeline descriptor object, when the
/// pipeline has been changed quite a bit (added/destroyed/reordered the effects)
/// - `'pipeline_id'::pipeline-destroyed`, when the pipeline has been destroyed
/// - `'pipeline_id'::pipeline-renamed`, handing the new name, when the pipeline has been
/// renamed, which is only important for the preferences
///
/// - signals that are targeted towards a given effect, with `effect_id` being its unique id, and
/// `pipeline_id` the unique id of the pipeline it is attached to:
/// - `'pipeline_id'::effect-'effect_id'-key-removed`, handing the key that was removed
/// - `'pipeline_id'::effect-'effect_id'-key-updated`, handing the key that was changed and its
/// new value
/// - `'pipeline_id'::effect-'effect_id'-key-added`, handing the key that was added and its
/// value
export class PipelinesManager {
constructor(settings) {
this.settings = settings;
this.pipelines = this.settings.PIPELINES;
this.settings.PIPELINES_changed(_ => this.on_pipeline_update());
}
create_pipeline(name, effects = []) {
// select a random id for the pipeline
let id = "pipeline_" + ("" + Math.random()).slice(2, 16);
// add a random ID for each effect, to help tracking them
effects.forEach(effect => effect.id = "effect_" + ("" + Math.random()).slice(2, 16));
this.pipelines[id] = { name, effects };
this.settings.PIPELINES = this.pipelines;
this._emit('pipeline-created', id, this.pipelines[id]);
this._emit('pipeline-list-changed');
return id;
}
duplicate_pipeline(id) {
if (!(id in this.pipelines)) {
this._warn(`could not duplicate pipeline, id ${id} does not exist`);
return;
}
const pipeline = this.pipelines[id];
this.create_pipeline(pipeline.name + " - duplicate", [...pipeline.effects]);
this.settings.PIPELINES = this.pipelines;
}
delete_pipeline(id) {
if (!(id in this.pipelines)) {
this._warn(`could not delete pipeline, id ${id} does not exist`);
return;
}
if (id == "pipeline_default") {
this._warn(`could not delete pipeline "pipeline_default" as it is immutable`);
return;
}
delete this.pipelines[id];
this.settings.PIPELINES = this.pipelines;
this._emit(id + '::pipeline-destroyed');
this._emit('pipeline-list-changed');
}
update_pipeline_effects(id, effects, emit_update_signal = true) {
if (!(id in this.pipelines)) {
this._warn(`could not update pipeline effects, id ${id} does not exist`);
return;
}
this.pipelines[id].effects = [...effects];
this.settings.PIPELINES = this.pipelines;
if (emit_update_signal)
this._emit(id + '::pipeline-updated');
}
rename_pipeline(id, name) {
if (!(id in this.pipelines)) {
this._warn(`could not rename pipeline, id ${id} does not exist`);
return;
}
this.pipelines[id].name = name.slice();
this.settings.PIPELINES = this.pipelines;
this._emit(id + '::pipeline-renamed', name);
this._emit('pipeline-names-changed');
}
on_pipeline_update() {
const old_pipelines = this.pipelines;
this.pipelines = this.settings.PIPELINES;
for (var pipeline_id in old_pipelines) {
// if we find a pipeline that does not exist anymore, signal it
if (!(pipeline_id in this.pipelines)) {
this._emit(pipeline_id + '::pipeline-destroyed');
continue;
}
const old_pipeline = old_pipelines[pipeline_id];
const new_pipeline = this.pipelines[pipeline_id];
// verify if both pipelines have effects in the same order
// if they have, then check for their parameters
if (
old_pipeline.effects.length == new_pipeline.effects.length &&
old_pipeline.effects.every((effect, i) => effect.id === new_pipeline.effects[i].id)
) {
for (let i = 0; i < old_pipeline.effects.length; i++) {
const old_effect = old_pipeline.effects[i];
const new_effect = new_pipeline.effects[i];
const id = old_effect.id;
for (let key in old_effect.params) {
// if a key was removed, we emit to tell the effect to use the default value
if (!(key in new_effect.params))
this._emit(
pipeline_id + '::effect-' + id + '-key-removed', key
);
// if a key was updated, we emit to tell the effect to change its value
else if (old_effect.params[key] != new_effect.params[key])
this._emit(
pipeline_id + '::effect-' + id + '-key-updated', key, new_effect.params[key]
);
}
for (let key in new_effect.params) {
// if a key was added, we emit to tell the effect the key and its value
if (!(key in old_effect.params))
this._emit(
pipeline_id + '::effect-' + id + '-key-added', key, new_effect.params[key]
);
}
}
}
// if either the order has changed, or there are new effects, then rebuild it
else
this._emit(pipeline_id + '::pipeline-updated', new_pipeline);
}
}
destroy() {
this.settings.PIPELINES_disconnect();
}
_emit(signal, ...args) {
this.emit(signal, ...args);
this._log(`signal: '${signal}', arguments: ${args}`);
}
_log(str) {
if (this.settings.DEBUG)
console.log(`[Blur my Shell > pipelines] ${str}`);
}
_warn(str) {
console.warn(`[Blur my Shell > pipelines] ${str}`);
}
}
Signals.addSignalMethods(PipelinesManager.prototype);

View File

@ -1,5 +1,4 @@
import GLib from 'gi://GLib'; import GLib from 'gi://GLib';
const Signals = imports.signals; const Signals = imports.signals;
/// An enum non-extensively describing the type of gsettings key. /// An enum non-extensively describing the type of gsettings key.
@ -9,7 +8,8 @@ export const Type = {
D: 'Double', D: 'Double',
S: 'String', S: 'String',
C: 'Color', C: 'Color',
AS: 'StringArray' AS: 'StringArray',
PIPELINES: 'Pipelines'
}; };
/// An object to get and manage the gsettings preferences. /// An object to get and manage the gsettings preferences.
@ -118,20 +118,197 @@ export const Settings = class Settings {
} }
}); });
break; break;
case Type.PIPELINES:
Object.defineProperty(component, property_name, {
get() {
let pips = component_settings.get_value(key.name).deep_unpack();
Object.keys(pips).forEach(pipeline_id => {
let pipeline = pips[pipeline_id];
if (!('name' in pipeline)) {
this._warn('impossible to get pipelines, pipeline has not name, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let name = pipeline.name.deep_unpack();
if (typeof name !== 'string') {
this._warn('impossible to get pipelines, pipeline name is not a string, resetting');
component[property_name + '_reset']();
return component[property_name];
}
if (!('effects' in pipeline)) {
this._warn('impossible to get pipelines, pipeline has not effects, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let effects = pipeline.effects.deep_unpack();
if (!Array.isArray(effects)) {
this._warn('impossible to get pipelines, pipeline effects is not an array, resetting');
component[property_name + '_reset']();
return component[property_name];
}
effects = effects.map(effect => effect.deep_unpack());
effects.forEach(effect => {
if (!('type' in effect)) {
this._warn('impossible to get pipelines, effect has not type, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let type = effect.type.deep_unpack();
if (typeof type !== 'string') {
this._warn('impossible to get pipelines, effect type is not a string, resetting');
component[property_name + '_reset']();
return component[property_name];
}
if (!('id' in effect)) {
this._warn('impossible to get pipelines, effect has not id, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let id = effect.id.deep_unpack();
if (typeof id !== 'string') {
this._warn('impossible to get pipelines, effect id is not a string, resetting');
component[property_name + '_reset']();
return component[property_name];
}
let params = {};
if ('params' in effect)
params = effect.params.deep_unpack();
if (!(params && typeof params === 'object' && params.constructor === Object)) {
this._warn('impossible to get pipelines, effect params is not an object, resetting');
component[property_name + '_reset']();
return component[property_name];
}
Object.keys(params).forEach(param_key => {
params[param_key] = params[param_key].deep_unpack();
});
effect.type = type;
effect.id = id;
effect.params = params;
});
pipeline.name = name;
pipeline.effects = effects;
});
return pips;
},
set(pips) {
let pipelines = {};
Object.keys(pips).forEach(pipeline_id => {
let pipeline = pips[pipeline_id];
if (!(pipeline && typeof pipeline === 'object' && pipeline.constructor === Object)) {
this._warn('impossible to set pipelines, pipeline is not an object');
return;
}
if (!('name' in pipeline)) {
this._warn('impossible to set pipelines, pipeline has no name');
return;
}
if (typeof pipeline.name !== 'string') {
this._warn('impossible to set pipelines, pipeline name is not a string');
return;
}
if (!('effects' in pipeline)) {
this._warn('impossible to set pipelines, pipeline has no effect');
return;
}
if (!Array.isArray(pipeline.effects)) {
this._warn('impossible to set pipelines, effects is not an array');
return;
}
let gvariant_effects = [];
pipeline.effects.forEach(effect => {
if (!(effect instanceof Object)) {
this._warn('impossible to set pipelines, effect is not an object');
return;
}
if (!('type' in effect)) {
this._warn('impossible to set pipelines, effect has not type');
return;
}
if (typeof effect.type !== 'string') {
this._warn('impossible to set pipelines, effect type is not a string');
return;
}
if (!('id' in effect)) {
this._warn('impossible to set pipelines, effect has not id');
return;
}
if (typeof effect.id !== 'string') {
this._warn('impossible to set pipelines, effect id is not a string');
return;
}
let params = {};
if ('params' in effect) {
params = effect.params;
}
let gvariant_params = {};
Object.keys(params).forEach(param_key => {
let param = params[param_key];
if (typeof param === 'boolean')
gvariant_params[param_key] = GLib.Variant.new_boolean(param);
else if (typeof param === 'number') {
if (Number.isInteger(param))
gvariant_params[param_key] = GLib.Variant.new_int32(param);
else
gvariant_params[param_key] = GLib.Variant.new_double(param);
} else if (typeof param === 'string')
gvariant_params[param_key] = GLib.Variant.new_string(param);
else if (Array.isArray(param) && param.length == 4)
gvariant_params[param_key] = new GLib.Variant("(dddd)", param);
else
this._warn('impossible to set pipeline, effect parameter type is unknown');
});
gvariant_effects.push(
new GLib.Variant("a{sv}", {
type: GLib.Variant.new_string(effect.type),
id: GLib.Variant.new_string(effect.id),
params: new GLib.Variant("a{sv}", gvariant_params)
})
);
});
pipelines[pipeline_id] = {
name: GLib.Variant.new_string(pipeline.name),
effects: new GLib.Variant("av", gvariant_effects)
};
});
let val = new GLib.Variant("a{sa{sv}}", pipelines);
component_settings.set_value(key.name, val);
}
});
break;
} }
component[property_name + '_reset'] = function () { component[property_name + '_reset'] = function () {
return component_settings.reset(key.name); return component_settings.reset(key.name);
}; };
component[property_name + '_signal_ids'] = [];
component[property_name + '_changed'] = function (cb) { component[property_name + '_changed'] = function (cb) {
return component_settings.connect('changed::' + key.name, cb); component[property_name + '_signal_ids'].push(
component_settings.connect('changed::' + key.name, cb)
);
}; };
component[property_name + '_disconnect'] = function () { component[property_name + '_disconnect'] = function () {
return component_settings.disconnect.apply( component[property_name + '_signal_ids'].forEach(
component_settings, arguments id => component_settings.disconnect(id)
); );
component[property_name + '_signal_ids'] = [];
}; };
}); });
}); });
@ -177,6 +354,10 @@ export const Settings = class Settings {
}); });
}); });
} }
_warn(str) {
console.warn(`[Blur my Shell > settings] ${str}`);
}
}; };
Signals.addSignalMethods(Settings.prototype); Signals.addSignalMethods(Settings.prototype);

View File

@ -0,0 +1,40 @@
import { Settings } from './settings.js';
import { KEYS, DEPRECATED_KEYS } from './keys.js';
const CURRENT_SETTINGS_VERSION = 2;
export function update_from_old_settings(gsettings) {
const preferences = new Settings(KEYS, gsettings);
const deprecated_preferences = new Settings(DEPRECATED_KEYS, gsettings);
const old_version = preferences.settings.get_int('settings-version');
if (old_version < CURRENT_SETTINGS_VERSION) {
// set artifacts hacks to be 1 at most, as it should be suitable now that most big bugs have
// been resolved (and especially because hack levels to 2 now means disabling clipped
// redraws entirely, which is very much not what we want for users that update)
if (preferences.HACKS_LEVEL > 1)
preferences.HACKS_LEVEL = 1;
// enable dash-to-dock blurring, as most disabled it due to the lack of rounded corners; set
// it to static blur by default too and with transparent background
preferences.dash_to_dock.BLUR = true;
preferences.dash_to_dock.STATIC_BLUR = true;
preferences.dash_to_dock.STYLE_DASH_TO_DOCK = 0;
// 'customize' has been removed: we merge the current used settings
['appfolder', 'panel', 'dash_to_dock', 'applications', 'window_list'].forEach(
component_name => {
const deprecated_component = deprecated_preferences[component_name];
const new_component = preferences[component_name];
if (!deprecated_component.CUSTOMIZE) {
new_component.SIGMA = deprecated_preferences.SIGMA;
new_component.BRIGHTNESS = deprecated_preferences.BRIGHTNESS;
}
});
// remove old preferences in order not to clutter the gsettings
deprecated_preferences.reset();
}
preferences.settings.set_int('settings-version', CURRENT_SETTINGS_VERSION);
}

View File

@ -0,0 +1,38 @@
import GLib from 'gi://GLib';
export const IS_IN_PREFERENCES = typeof global === 'undefined';
// Taken from https://github.com/Schneegans/Burn-My-Windows/blob/main/src/utils.js
// This method can be used to import a module in the GNOME Shell process only. This
// is useful if you want to use a module in extension.js, but not in the preferences
// process. This method returns null if it is called in the preferences process.
export async function import_in_shell_only(module) {
if (IS_IN_PREFERENCES)
return null;
return (await import(module)).default;
}
// In use for the effects, to prevent boilerplate code
export function setup_params(outer_this, params) {
// setup each parameter, either with the given or the default value
for (const params_name in outer_this.constructor.default_params) {
outer_this["_" + params_name] = null;
outer_this[params_name] = params_name in params ?
params[params_name] :
outer_this.constructor.default_params[params_name];
}
};
export const get_shader_source = (Shell, shader_filename, self_uri) => {
if (!Shell)
return;
const shader_path = GLib.filename_from_uri(
GLib.uri_resolve_relative(self_uri, shader_filename, GLib.UriFlags.NONE)
)[0];
try {
return Shell.get_file_contents_utf8_sync(shader_path);
} catch (e) {
console.warn(`[Blur my Shell > effect] error loading shader from ${shader_path}: ${e}`);
return null;
}
};

View File

@ -1,114 +0,0 @@
uniform sampler2D tex;
uniform float sigma;
uniform int dir;
uniform float brightness;
uniform float corner_radius;
uniform float width;
uniform float height;
float circleBounds(vec2 p, vec2 center, float clip_radius) {
vec2 delta = p - center;
float dist_squared = dot(delta, delta);
float outer_radius = clip_radius + 0.5;
if (dist_squared >= (outer_radius * outer_radius))
return 0.0;
float inner_radius = clip_radius - 0.5;
if (dist_squared <= (inner_radius * inner_radius))
return 1.0;
return outer_radius - sqrt(dist_squared);
}
vec4 shapeCorner(vec4 pixel, vec2 p, vec2 center, float clip_radius) {
float alpha = circleBounds(p, center, clip_radius);
return vec4(pixel.rgb * alpha, min(alpha, pixel.a));
}
vec4 getTexture(vec2 uv) {
if (uv.x < 2. / width)
uv.x = 2. / width;
if (uv.y < 2. / height)
uv.y = 2. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
void main(void) {
vec2 uv = cogl_tex_coord_in[0].xy;
vec2 pos = uv * vec2(width, height);
vec2 direction = vec2(dir, (1.0 - dir));
float pixel_step;
if (dir == 0)
pixel_step = 1.0 / height;
else
pixel_step = 1.0 / width;
vec3 gauss_coefficient;
gauss_coefficient.x = 1.0 / (sqrt(2.0 * 3.14159265) * sigma);
gauss_coefficient.y = exp(-0.5 / (sigma * sigma));
gauss_coefficient.z = gauss_coefficient.y * gauss_coefficient.y;
float gauss_coefficient_total = gauss_coefficient.x;
vec4 ret = getTexture(uv) * gauss_coefficient.x;
gauss_coefficient.xy *= gauss_coefficient.yz;
int n_steps = int(ceil(1.5 * sigma)) * 2;
for (int i = 1; i <= n_steps; i += 2) {
float coefficient_subtotal = gauss_coefficient.x;
gauss_coefficient.xy *= gauss_coefficient.yz;
coefficient_subtotal += gauss_coefficient.x;
float gauss_ratio = gauss_coefficient.x / coefficient_subtotal;
float foffset = float(i) + gauss_ratio;
vec2 offset = direction * foffset * pixel_step;
ret += getTexture(uv + offset) * coefficient_subtotal;
ret += getTexture(uv - offset) * coefficient_subtotal;
gauss_coefficient_total += 2.0 * coefficient_subtotal;
gauss_coefficient.xy *= gauss_coefficient.yz;
}
vec4 outColor = ret / gauss_coefficient_total;
// apply brightness and rounding on the second pass (dir==0 comes last)
if (dir == 0) {
// left side
if (pos.x < corner_radius) {
// top left corner
if (pos.y < corner_radius) {
outColor = shapeCorner(outColor, pos, vec2(corner_radius + 2., corner_radius + 2.), corner_radius);
// bottom left corner
} else if (pos.y > height - corner_radius) {
outColor = shapeCorner(outColor, pos, vec2(corner_radius + 2., height - corner_radius - 1.), corner_radius);
}
// right side
} else if (pos.x > width - corner_radius) {
// top right corner
if (pos.y < corner_radius) {
outColor = shapeCorner(outColor, pos, vec2(width - corner_radius - 1., corner_radius + 2.), corner_radius);
// bottom right corner
} else if (pos.y > height - corner_radius) {
outColor = shapeCorner(outColor, pos, vec2(width - corner_radius - 1., height - corner_radius - 1.), corner_radius);
}
}
outColor.rgb *= brightness;
}
cogl_color_out = outColor;
}

View File

@ -1,236 +0,0 @@
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Clutter from 'gi://Clutter';
import Shell from 'gi://Shell';
const SHADER_PATH = GLib.filename_from_uri(GLib.uri_resolve_relative(import.meta.url, 'blur_effect.glsl', GLib.UriFlags.NONE))[0];
const get_shader_source = _ => {
try {
return Shell.get_file_contents_utf8_sync(SHADER_PATH);
} catch (e) {
console.warn(`[Blur my Shell] error loading shader from ${SHADER_PATH}: ${e}`);
return null;
}
};
export const BlurEffect = new GObject.registerClass({
GTypeName: "BlurEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Radius`,
`Blur radius`,
GObject.ParamFlags.READWRITE,
0.0, 2000.0,
200.0,
),
'brightness': GObject.ParamSpec.double(
`brightness`,
`Brightness`,
`Blur brightness`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.6,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'corner_radius': GObject.ParamSpec.double(
`corner_radius`,
`Corner Radius`,
`Corner Radius`,
GObject.ParamFlags.READWRITE,
0, Number.MAX_SAFE_INTEGER,
0,
),
'direction': GObject.ParamSpec.int(
`direction`,
`Direction`,
`Direction`,
GObject.ParamFlags.READWRITE,
0, 1,
0,
),
'chained_effect': GObject.ParamSpec.object(
`chained_effect`,
`Chained Effect`,
`Chained Effect`,
GObject.ParamFlags.READABLE,
GObject.Object,
),
}
}, class BlurEffect extends Clutter.ShaderEffect {
constructor(params, settings) {
super(params);
this._sigma = null;
this._brightness = null;
this._width = null;
this._height = null;
this._corner_radius = null;
this._static = true;
this._settings = settings;
this._direction = 0;
this._chained_effect = null;
if (params.sigma)
this.sigma = params.sigma;
if (params.brightness)
this.brightness = params.brightness;
if (params.width)
this.width = params.width;
if (params.height)
this.height = params.height;
if (params.corner_radius)
this.corner_radius = params.corner_radius;
if (params.direction)
this.direction = params.direction;
// set shader source
this._source = get_shader_source();
if (this._source)
this.set_shader_source(this._source);
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
// like Clutter, we use the assumption radius = 2*sigma
this.set_uniform_value('sigma', parseFloat(this._radius / 2 - 1e-6));
if (this._chained_effect) {
this._chained_effect.radius = value;
}
}
}
get brightness() {
return this._brightness;
}
set brightness(value) {
if (this._brightness !== value) {
this._brightness = value;
this.set_uniform_value('brightness', parseFloat(this._brightness - 1e-6));
if (this._chained_effect) {
this._chained_effect.brightness = value;
}
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
if (this._chained_effect) {
this._chained_effect.width = value;
}
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
if (this._chained_effect) {
this._chained_effect.height = value;
}
}
}
get corner_radius() {
return this._corner_radius;
}
set corner_radius(value) {
if (this._corner_radius !== value) {
this._corner_radius = value;
this.set_uniform_value('corner_radius', parseFloat(this._corner_radius - 1e-6));
if (this._chained_effect) {
this._chained_effect.corner_radius = value;
}
}
}
get direction() {
return this._direction;
}
set direction(value) {
if (this._direction !== value) {
this._direction = value;
}
}
get chained_effect() {
return this._chained_effect;
}
vfunc_set_actor(actor) {
super.vfunc_set_actor(actor);
if (this._direction == 0) {
this._chained_effect = new BlurEffect({
radius: this.radius,
brightness: this.brightness,
width: this.width,
height: this.height,
corner_radius: this.corner_radius,
direction: 1
});
if (actor !== null)
actor.add_effect(this._chained_effect);
}
}
vfunc_paint_target(paint_node = null, paint_context = null) {
this.set_uniform_value("tex", 0);
this.set_uniform_value("dir", this._direction);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,140 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'color.glsl';
const DEFAULT_PARAMS = {
color: [0.0, 0.0, 0.0, 0.0]
};
export const ColorEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "ColorEffect",
Properties: {
'red': GObject.ParamSpec.double(
`red`,
`Red`,
`Red value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'green': GObject.ParamSpec.double(
`green`,
`Green`,
`Green value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'blue': GObject.ParamSpec.double(
`blue`,
`Blue`,
`Blue value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'blend': GObject.ParamSpec.double(
`blend`,
`Blend`,
`Amount of blending between the colors`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
}
}, class ColorEffect extends Clutter.ShaderEffect {
constructor(params) {
// initialize without color as a parameter
const { color, ...parent_params } = params;
super(parent_params);
this._red = null;
this._green = null;
this._blue = null;
this._blend = null;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
// set shader color
this.color = 'color' in params ? color : this.constructor.default_params.color;
}
static get default_params() {
return DEFAULT_PARAMS;
}
get red() {
return this._red;
}
set red(value) {
if (this._red !== value) {
this._red = value;
this.set_uniform_value('red', parseFloat(this._red - 1e-6));
}
}
get green() {
return this._green;
}
set green(value) {
if (this._green !== value) {
this._green = value;
this.set_uniform_value('green', parseFloat(this._green - 1e-6));
}
}
get blue() {
return this._blue;
}
set blue(value) {
if (this._blue !== value) {
this._blue = value;
this.set_uniform_value('blue', parseFloat(this._blue - 1e-6));
}
}
get blend() {
return this._blend;
}
set blend(value) {
if (this._blend !== value) {
this._blend = value;
this.set_uniform_value('blend', parseFloat(this._blend - 1e-6));
this.set_enabled(this.blend > 0);
}
}
set color(rgba) {
let [r, g, b, a] = rgba;
this.red = r;
this.green = g;
this.blue = b;
this.blend = a;
}
get color() {
return [this.red, this.green, this.blue, this.blend];
}
/// False set function, only cares about the color. Too hard to change.
set(params) {
this.color = params.color;
}
});

View File

@ -1,181 +0,0 @@
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Clutter from 'gi://Clutter';
import Shell from 'gi://Shell';
const SHADER_PATH = GLib.filename_from_uri(GLib.uri_resolve_relative(import.meta.url, 'color_effect.glsl', GLib.UriFlags.NONE))[0];
const get_shader_source = _ => {
try {
return Shell.get_file_contents_utf8_sync(SHADER_PATH);
} catch (e) {
console.warn(`[Blur my Shell] error loading shader from ${SHADER_PATH}: ${e}`);
return null;
}
};
/// New Clutter Shader Effect that simply mixes a color in, the class applies
/// the GLSL shader programmed into vfunc_get_static_shader_source and applies
/// it to an Actor.
///
/// Clutter Shader Source Code:
/// https://github.com/GNOME/clutter/blob/master/clutter/clutter-shader-effect.c
///
/// GJS Doc:
/// https://gjs-docs.gnome.org/clutter10~10_api/clutter.shadereffect
export const ColorEffect = new GObject.registerClass({
GTypeName: "ColorEffect",
Properties: {
'red': GObject.ParamSpec.double(
`red`,
`Red`,
`Red value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
'green': GObject.ParamSpec.double(
`green`,
`Green`,
`Green value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
'blue': GObject.ParamSpec.double(
`blue`,
`Blue`,
`Blue value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
'blend': GObject.ParamSpec.double(
`blend`,
`Blend`,
`Amount of blending between the colors`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
}
}, class ColorShader extends Clutter.ShaderEffect {
constructor(params, settings) {
// initialize without color as a parameter
let _color = params.color;
delete params.color;
super(params);
this._red = null;
this._green = null;
this._blue = null;
this._blend = null;
this._static = true;
this._settings = settings;
// set shader source
this._source = get_shader_source();
if (this._source)
this.set_shader_source(this._source);
// set shader color
if (_color)
this.color = _color;
this.update_enabled();
}
get red() {
return this._red;
}
set red(value) {
if (this._red !== value) {
this._red = value;
this.set_uniform_value('red', parseFloat(this._red - 1e-6));
}
}
get green() {
return this._green;
}
set green(value) {
if (this._green !== value) {
this._green = value;
this.set_uniform_value('green', parseFloat(this._green - 1e-6));
}
}
get blue() {
return this._blue;
}
set blue(value) {
if (this._blue !== value) {
this._blue = value;
this.set_uniform_value('blue', parseFloat(this._blue - 1e-6));
}
}
get blend() {
return this._blend;
}
set blend(value) {
if (this._blend !== value) {
this._blend = value;
this.set_uniform_value('blend', parseFloat(this._blend - 1e-6));
}
this.update_enabled();
}
set color(rgba) {
let [r, g, b, a] = rgba;
this.red = r;
this.green = g;
this.blue = b;
this.blend = a;
}
get color() {
return [this.red, this.green, this.blue, this.blend];
}
/// False set function, only cares about the color. Too hard to change.
set(params) {
this.color = params.color;
}
update_enabled() {
// don't anything if this._settings is undefined (when calling super)
if (this._settings === undefined)
return;
this.set_enabled(
this.blend > 0 &&
this._settings.COLOR_AND_NOISE &&
this._static
);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,93 @@
// Heavily based on https://github.com/yilozt/rounded-window-corners
// which is itself based on upstream Mutter code
uniform sampler2D tex;
uniform float radius;
uniform float width;
uniform float height;
uniform bool corners_top;
uniform bool corners_bottom;
uniform float clip_x0;
uniform float clip_y0;
uniform float clip_width;
uniform float clip_height;
float circle_bounds(vec2 p, vec2 center, float clip_radius) {
vec2 delta = p - center;
float dist_squared = dot(delta, delta);
float outer_radius = clip_radius + 0.5;
if (dist_squared >= (outer_radius * outer_radius))
return 0.0;
float inner_radius = clip_radius - 0.5;
if (dist_squared <= (inner_radius * inner_radius))
return 1.0;
return outer_radius - sqrt(dist_squared);
}
vec4 getTexture(vec2 uv) {
if (uv.x < 2. / width)
uv.x = 2. / width;
if (uv.y < 2. / height)
uv.y = 2. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
float rounded_rect_coverage(vec2 p, vec4 bounds, float clip_radius) {
// Outside the bounds
if (p.x < bounds.x || p.x > bounds.z || p.y < bounds.y || p.y > bounds.w) {
return 0.;
}
vec2 center;
float center_left = bounds.x + clip_radius;
float center_right = bounds.z - clip_radius;
if (p.x < center_left)
center.x = center_left + 2.;
else if (p.x > center_right)
center.x = center_right - 1.;
else
return 1.0;
float center_top = bounds.y + clip_radius;
float center_bottom = bounds.w - clip_radius;
if (corners_top && p.y < center_top)
center.y = center_top + 2.;
else if (corners_bottom && p.y > center_bottom)
center.y = center_bottom - 1.;
else
return 1.0;
return circle_bounds(p, center, clip_radius);
}
void main(void) {
vec2 uv = cogl_tex_coord_in[0].xy;
vec2 pos = uv * vec2(width, height);
vec4 c = getTexture(uv);
vec4 bounds;
if (clip_width < 0. || clip_height < 0.) {
bounds = vec4(clip_x0, clip_y0, clip_x0 + width, clip_y0 + height);
} else {
bounds = vec4(clip_x0, clip_y0, clip_x0 + clip_width, clip_y0 + clip_height);
}
float alpha = rounded_rect_coverage(pos, bounds, radius);
cogl_color_out = vec4(c.rgb * alpha, min(alpha, c.a));
}

View File

@ -0,0 +1,211 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'corner.glsl';
const DEFAULT_PARAMS = {
radius: 12, width: 0, height: 0,
corners_top: true, corners_bottom: true,
clip: [0, 0, -1, -1]
};
export const CornerEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "CornerEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Corner Radius`,
`Corner Radius`,
GObject.ParamFlags.READWRITE,
0, Number.MAX_SAFE_INTEGER,
12,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'corners_top': GObject.ParamSpec.boolean(
`corners_top`,
`Round top corners`,
`Round top corners`,
GObject.ParamFlags.READWRITE,
true,
),
'corners_bottom': GObject.ParamSpec.boolean(
`corners_bottom`,
`Round bottom corners`,
`Round bottom corners`,
GObject.ParamFlags.READWRITE,
true,
),
// FIXME this works but it logs an error, because I'm not a double...
// I don't want to fiddle with GVariants again
'clip': GObject.ParamSpec.double(
`clip`,
`Clip`,
`Clip`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
}
}, class CornerEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._clip_x0 = null;
this._clip_y0 = null;
this._clip_width = null;
this._clip_height = null;
utils.setup_params(this, params);
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject('notify::scale-factor', _ => this.update_radius(), this);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
this.update_radius();
}
}
update_radius() {
const theme_context = St.ThemeContext.get_for_stage(global.stage);
let radius = Math.min(
this.radius * theme_context.scale_factor,
this.width / 2, this.height / 2
);
if (this._clip_width >= 0 || this._clip_height >= 0)
radius = Math.min(radius, this._clip_width / 2, this._clip_height / 2);
this.set_uniform_value('radius', parseFloat(radius - 1e-6));
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
this.update_radius();
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
this.update_radius();
}
}
get corners_top() {
return this._corners_top;
}
set corners_top(value) {
if (this._corners_top !== value) {
this._corners_top = value;
this.set_uniform_value('corners_top', this._corners_top ? 1 : 0);
}
}
get corners_bottom() {
return this._corners_bottom;
}
set corners_bottom(value) {
if (this._corners_bottom !== value) {
this._corners_bottom = value;
this.set_uniform_value('corners_bottom', this._corners_bottom ? 1 : 0);
}
}
get clip() {
return [this._clip_x0, this._clip_y0, this._clip_width, this._clip_height];
}
set clip(value) {
[this._clip_x0, this._clip_y0, this._clip_width, this._clip_height] = value;
this.set_uniform_value('clip_x0', parseFloat(this._clip_x0 - 1e-6));
this.set_uniform_value('clip_y0', parseFloat(this._clip_y0 - 1e-6));
this.set_uniform_value('clip_width', parseFloat(this._clip_width + 3 - 1e-6));
this.set_uniform_value('clip_height', parseFloat(this._clip_height + 3 - 1e-6));
this.update_radius();
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (this._actor_connection_clip_rect_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_clip_rect_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
this.clip = actor.has_clip ? actor.get_clip() : [0, 0, -10, -10];
this._actor_connection_clip_rect_id = actor.connect('notify::clip-rect', _ => {
this.clip = actor.has_clip ? actor.get_clip() : [0, 0, -10, -10];
});
}
else {
this._actor_connection_size_id = null;
this._actor_connection_clip_rect_id = null;
}
super.vfunc_set_actor(actor);
}
});

View File

@ -0,0 +1,71 @@
uniform sampler2D tex;
uniform int operation;
uniform float width;
uniform float height;
#define CORRECTION 2.25
#define SIZE_ADDITION 3
vec4 get_texture_at_position(vec2 position) {
vec2 raw_position = position + vec2(CORRECTION, CORRECTION);
vec2 raw_uv = raw_position / vec2(width + SIZE_ADDITION, height + SIZE_ADDITION);
return texture2D(tex, raw_uv);
}
vec4 try_get_texture_at_position(vec2 position, inout int count) {
if (any(greaterThanEqual(position, vec2(width, height))) ||
any(lessThan(position, vec2(0, 0)))) {
return vec4(0);
} else {
count++;
return get_texture_at_position(position);
}
}
ivec2 get_corrected_position() {
vec2 raw_uv = cogl_tex_coord0_in.st;
vec2 raw_position = raw_uv * vec2(width + SIZE_ADDITION, height + SIZE_ADDITION);
return ivec2(raw_position - vec2(CORRECTION, CORRECTION));
}
void main() {
ivec2 corrected_position = get_corrected_position();
// 1-step derivative
if (operation == 0) {
vec4 color = vec4(0);
int c = 0;
color += try_get_texture_at_position(corrected_position + vec2(0, 1), c);
color -= try_get_texture_at_position(corrected_position + vec2(0, 0), c);
color += try_get_texture_at_position(corrected_position + vec2(1, 0), c);
color -= try_get_texture_at_position(corrected_position + vec2(0, 0), c);
if (c < 4) {
color = vec4(0);
}
cogl_color_out = vec4(color.xyz, 1);
} else
// 2-step derivative
if (operation == 1) {
vec4 color = vec4(0);
int c = 0;
color += try_get_texture_at_position(corrected_position + vec2(0, 1), c);
color -= try_get_texture_at_position(corrected_position + vec2(0, -1), c);
color += try_get_texture_at_position(corrected_position + vec2(1, 0), c);
color -= try_get_texture_at_position(corrected_position + vec2(-1, 0), c);
if (c < 4) {
color = vec4(0);
}
cogl_color_out = vec4(color.xyz / 2, 1);
} else
// laplacian
if (operation == 2) {
vec4 color = vec4(0);
color = -4 * get_texture_at_position(corrected_position);
color += get_texture_at_position(corrected_position + vec2(0, 1));
color += get_texture_at_position(corrected_position + vec2(0, -1));
color += get_texture_at_position(corrected_position + vec2(1, 0));
color += get_texture_at_position(corrected_position + vec2(-1, 0));
cogl_color_out = vec4(color.xyz, 1);
}
}

View File

@ -0,0 +1,120 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'derivative.glsl';
const DEFAULT_PARAMS = {
operation: 0, width: 0, height: 0
};
export const DerivativeEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "DerivativeEffect",
Properties: {
'operation': GObject.ParamSpec.int(
`operation`,
`Operation`,
`Operation`,
GObject.ParamFlags.READWRITE,
0, 2,
0,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
)
}
}, class DerivativeEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
utils.setup_params(this, params);
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get operation() {
return this._operation;
}
set operation(value) {
if (this._operation !== value) {
this._operation = value;
this.set_uniform_value('operation', this._operation);
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width - 1e-6));
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height - 1e-6));
}
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
}
vfunc_paint_target(paint_node, paint_context) {
// force setting nearest-neighbour texture filtering
this.get_pipeline().set_layer_filters(0, 9728, 9728);
super.vfunc_paint_target(paint_node, paint_context);
}
});

View File

@ -0,0 +1,76 @@
uniform sampler2D tex;
uniform int divider;
uniform float width;
uniform float height;
uniform int downsampling_mode;
#define CORRECTION 2.25
#define SIZE_ADDITION 3
vec4 get_texture_at_position(vec2 position) {
vec2 raw_position = position + vec2(CORRECTION, CORRECTION);
vec2 raw_uv = raw_position / vec2(width + SIZE_ADDITION, height + SIZE_ADDITION);
return texture2D(tex, raw_uv);
}
vec2 get_corrected_position() {
vec2 raw_uv = cogl_tex_coord0_in.st;
vec2 raw_position = raw_uv * vec2(width + SIZE_ADDITION, height + SIZE_ADDITION);
return raw_position - vec2(CORRECTION, CORRECTION);
}
void main() {
ivec2 corrected_position = ivec2(get_corrected_position());
vec2 multiplied_position = corrected_position * divider;
if (any(greaterThan(multiplied_position, vec2(width, height)))) {
discard;
}
// mode 0: boxcar downsampling
if (downsampling_mode == 0) {
vec4 color = vec4(0.);
int count = 0;
for (int i = 0; i < divider; i++) {
for (int j = 0; j < divider; j++) {
vec2 lookup_position = multiplied_position + vec2(i, j);
if (all(greaterThanEqual(lookup_position, vec2(0, 0))) &&
all(lessThan(lookup_position, vec2(width, height)))) {
color += get_texture_at_position(lookup_position);
count += 1;
}
}
}
cogl_color_out = color / count;
} else
// mode 1: triangular downsampling
if (downsampling_mode == 1) {
vec4 color = vec4(0.);
int count = 0;
int force = 1;
for (int i = 0; i < divider; i++) {
for (int j = 0; j < divider; j++) {
vec2 lookup_position = multiplied_position + vec2(i, j);
if (all(greaterThanEqual(lookup_position, vec2(0, 0))) &&
all(lessThan(lookup_position, vec2(width, height)))) {
force = 1 + divider - int(abs(divider - i - j));
color += get_texture_at_position(lookup_position) * force;
count += force;
}
}
}
cogl_color_out = color / count;
} else
// mode 2: Dirac downsampling
if (downsampling_mode == 2) {
vec2 lookup_position = min(multiplied_position + vec2(divider, divider) / 2, vec2(width - 1, height - 1));
cogl_color_out = get_texture_at_position(lookup_position);
}
}

View File

@ -0,0 +1,140 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'downscale.glsl';
const DEFAULT_PARAMS = {
divider: 8, downsampling_mode: 0, width: 0, height: 0
};
export const DownscaleEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "DownscaleEffect",
Properties: {
'divider': GObject.ParamSpec.int(
`divider`,
`Divider`,
`Divider`,
GObject.ParamFlags.READWRITE,
0, 64,
8,
),
'downsampling_mode': GObject.ParamSpec.int(
`downsampling_mode`,
`Downsampling mode`,
`Downsampling mode`,
GObject.ParamFlags.READWRITE,
0, 2,
0,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
)
}
}, class DownscaleEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
utils.setup_params(this, params);
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get divider() {
return this._divider;
}
set divider(value) {
if (this._divider !== value) {
this._divider = value;
this.set_uniform_value('divider', this._divider);
}
}
get downsampling_mode() {
return this._downsampling_mode;
}
set downsampling_mode(value) {
if (this._downsampling_mode !== value) {
this._downsampling_mode = value;
this.set_uniform_value('downsampling_mode', this._downsampling_mode);
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width - 1e-6));
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height - 1e-6));
}
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
}
vfunc_paint_target(paint_node, paint_context) {
// force setting nearest-neighbour texture filtering
this.get_pipeline().set_layer_filters(0, 9728, 9728);
super.vfunc_paint_target(paint_node, paint_context);
}
});

View File

@ -0,0 +1,332 @@
import { NativeDynamicBlurEffect } from '../effects/native_dynamic_gaussian_blur.js';
import { NativeStaticBlurEffect } from '../effects/native_static_gaussian_blur.js';
import { GaussianBlurEffect } from '../effects/gaussian_blur.js';
import { MonteCarloBlurEffect } from '../effects/monte_carlo_blur.js';
import { ColorEffect } from '../effects/color.js';
import { NoiseEffect } from '../effects/noise.js';
import { CornerEffect } from '../effects/corner.js';
import { DownscaleEffect } from './downscale.js';
import { UpscaleEffect } from './upscale.js';
import { PixelizeEffect } from './pixelize.js';
import { DerivativeEffect } from './derivative.js';
import { RgbToHslEffect } from './rgb_to_hsl.js';
import { HslToRgbEffect } from './hsl_to_rgb.js';
// We do in this way because I've not found another way to store our preferences in a dictionnary
// while calling `gettext` on it while in preferences. Not so pretty, but works.
export function get_effects_groups(_ = _ => "") {
return {
blur_effects: {
name: _("Blur effects"),
contains: [
"native_static_gaussian_blur",
"gaussian_blur",
"monte_carlo_blur"
]
},
texture_effects: {
name: _("Texture effects"),
contains: [
"downscale",
"upscale",
"pixelize",
"derivative",
"noise",
"color",
"rgb_to_hsl",
"hsl_to_rgb"
]
},
shape_effects: {
name: _("Shape effects"),
contains: [
"corner"
]
}
};
};
export function get_supported_effects(_ = () => "") {
return {
native_dynamic_gaussian_blur: {
class: NativeDynamicBlurEffect
},
native_static_gaussian_blur: {
class: NativeStaticBlurEffect,
name: _("Native gaussian blur"),
description: _("An optimized blur effect that smoothly blends pixels within a given radius."),
is_advanced: false,
editable_params: {
unscaled_radius: {
name: _("Radius"),
description: _("The intensity of the blur effect."),
type: "float",
min: 0.,
max: 100.,
increment: 1.0,
big_increment: 10.,
digits: 0
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
}
},
gaussian_blur: {
class: GaussianBlurEffect,
name: _("Gaussian blur (advanced effect)"),
description: _("A blur effect that smoothly blends pixels within a given radius. This effect is more precise, but way less optimized."),
is_advanced: true,
editable_params: {
radius: {
name: _("Radius"),
description: _("The intensity of the blur effect. The bigger it is, the slower it will be."),
type: "float",
min: 0.,
max: 100.,
increment: .1,
big_increment: 10.,
digits: 1
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
}
},
monte_carlo_blur: {
class: MonteCarloBlurEffect,
name: _("Monte Carlo blur"),
description: _("A blur effect that mimics a random walk, by picking pixels further and further away from its origin and mixing them all together."),
is_advanced: false,
editable_params: {
radius: {
name: _("Radius"),
description: _("The maximum travel distance for each step in the random walk. A higher value will make the blur more randomized."),
type: "float",
min: 0.,
max: 10.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
iterations: {
name: _("Iterations"),
description: _("The number of iterations. The more there are, the smoother the blur is."),
type: "integer",
min: 0,
max: 50,
increment: 1
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
use_base_pixel: {
name: _("Use base pixel"),
description: _("Whether or not the original pixel is counted for the blur. If it is, the image will be more legible."),
type: "boolean"
}
}
},
color: {
class: ColorEffect,
name: _("Color"),
description: _("An effect that blends a color into the pipeline."),
is_advanced: false,
// TODO make this RGB + blend
editable_params: {
color: {
name: _("Color"),
description: _("The color to blend in. The blending amount is controled by the opacity of the color."),
type: "rgba"
}
}
},
pixelize: {
class: PixelizeEffect,
name: _("Pixelize"),
description: _("An effect that pixelizes the image."),
is_advanced: false,
editable_params: {
factor: {
name: _("Factor"),
description: _("How much to scale down the image."),
type: "integer",
min: 1,
max: 50,
increment: 1
},
downsampling_mode: {
name: _("Downsampling mode"),
description: _("The downsampling method that is used."),
type: "dropdown",
options: [
_("Boxcar"),
_("Triangular"),
_("Dirac")
]
}
}
},
downscale: {
class: DownscaleEffect,
name: _("Downscale (advanced effect)"),
description: _("An effect that downscales the image and put it on the top-left corner."),
is_advanced: true,
editable_params: {
divider: {
name: _("Factor"),
description: _("How much to scale down the image."),
type: "integer",
min: 1,
max: 50,
increment: 1
},
downsampling_mode: {
name: _("Downsampling mode"),
description: _("The downsampling method that is used."),
type: "dropdown",
options: [
_("Boxcar"),
_("Triangular"),
_("Dirac")
]
}
}
},
upscale: {
class: UpscaleEffect,
name: _("Upscale (advanced effect)"),
description: _("An effect that upscales the image from the top-left corner."),
is_advanced: true,
editable_params: {
factor: {
name: _("Factor"),
description: _("How much to scale up the image."),
type: "integer",
min: 1,
max: 50,
increment: 1
}
}
},
derivative: {
class: DerivativeEffect,
name: _("Derivative"),
description: _("Apply a spatial derivative, or a laplacian."),
is_advanced: false,
editable_params: {
operation: {
name: _("Operation"),
description: _("The mathematical operation to apply."),
type: "dropdown",
options: [
_("1-step derivative"),
_("2-step derivative"),
_("Laplacian")
]
}
}
},
noise: {
class: NoiseEffect,
name: _("Noise"),
description: _("An effect that adds a random noise. Prefer the Monte Carlo blur for a more organic effect if needed."),
is_advanced: false,
editable_params: {
noise: {
name: _("Noise"),
description: _("The amount of noise to add."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
lightness: {
name: _("Lightness"),
description: _("The luminosity of the noise. A setting of '1.0' will make the effect transparent."),
type: "float",
min: 0.,
max: 2.,
increment: 0.01,
big_increment: 0.1,
digits: 2
}
}
},
rgb_to_hsl: {
class: RgbToHslEffect,
name: _("RGB to HSL (advanced effect)"),
description: _("Converts the image from RGBA colorspace to HSLA."),
is_advanced: true,
editable_params: {}
},
hsl_to_rgb: {
class: HslToRgbEffect,
name: _("HSL to RGB (advanced effect)"),
description: _("Converts the image from HSLA colorspace to RGBA."),
is_advanced: true,
editable_params: {}
},
corner: {
class: CornerEffect,
name: _("Corner"),
description: _("An effect that draws corners. Add it last not to have the other effects perturb the corners."),
is_advanced: false,
editable_params: {
radius: {
name: _("Radius"),
description: _("The radius of the corner. GNOME apps use a radius of 12 px by default."),
type: "integer",
min: 0,
max: 50,
increment: 1,
},
corners_top: {
name: _("Top corners"),
description: _("Whether or not to round the top corners."),
type: "boolean"
},
corners_bottom: {
name: _("Bottom corners"),
description: _("Whether or not to round the bottom corners."),
type: "boolean"
}
}
}
};
};

View File

@ -0,0 +1,70 @@
uniform sampler2D tex;
uniform float sigma;
uniform int dir;
uniform float brightness;
uniform float width;
uniform float height;
vec4 getTexture(vec2 uv) {
if (uv.x < 3. / width)
uv.x = 3. / width;
if (uv.y < 3. / height)
uv.y = 3. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
void main(void) {
vec2 uv = cogl_tex_coord_in[0].xy;
vec2 direction = vec2(dir, (1.0 - dir));
float pixel_step;
if (dir == 0)
pixel_step = 1.0 / height;
else
pixel_step = 1.0 / width;
vec3 gauss_coefficient;
gauss_coefficient.x = 1.0 / (sqrt(2.0 * 3.14159265) * sigma);
gauss_coefficient.y = exp(-0.5 / (sigma * sigma));
gauss_coefficient.z = gauss_coefficient.y * gauss_coefficient.y;
float gauss_coefficient_total = gauss_coefficient.x;
vec4 ret = getTexture(uv) * gauss_coefficient.x;
gauss_coefficient.xy *= gauss_coefficient.yz;
int n_steps = int(ceil(1.5 * sigma)) * 2;
for (int i = 1; i <= n_steps; i += 2) {
float coefficient_subtotal = gauss_coefficient.x;
gauss_coefficient.xy *= gauss_coefficient.yz;
coefficient_subtotal += gauss_coefficient.x;
float gauss_ratio = gauss_coefficient.x / coefficient_subtotal;
float foffset = float(i) + gauss_ratio;
vec2 offset = direction * foffset * pixel_step;
ret += getTexture(uv + offset) * coefficient_subtotal;
ret += getTexture(uv - offset) * coefficient_subtotal;
gauss_coefficient_total += 2.0 * coefficient_subtotal;
gauss_coefficient.xy *= gauss_coefficient.yz;
}
vec4 outColor = ret / gauss_coefficient_total;
// apply brightness on the second pass (dir==0 comes last)
if (dir == 0) {
outColor.rgb *= brightness;
}
cogl_color_out = outColor;
}

View File

@ -0,0 +1,213 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'gaussian_blur.glsl';
const DEFAULT_PARAMS = {
radius: 30, brightness: .6,
width: 0, height: 0, direction: 0, chained_effect: null
};
export const GaussianBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "GaussianBlurEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Radius`,
`Blur radius`,
GObject.ParamFlags.READWRITE,
0.0, 2000.0,
30.0,
),
'brightness': GObject.ParamSpec.double(
`brightness`,
`Brightness`,
`Blur brightness`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.6,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'direction': GObject.ParamSpec.int(
`direction`,
`Direction`,
`Direction`,
GObject.ParamFlags.READWRITE,
0, 1,
0,
),
'chained_effect': GObject.ParamSpec.object(
`chained_effect`,
`Chained Effect`,
`Chained Effect`,
GObject.ParamFlags.READWRITE,
GObject.Object,
),
}
}, class GaussianBlurEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
utils.setup_params(this, params);
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject(
'notify::scale-factor', _ =>
this.set_uniform_value('sigma',
parseFloat(this.radius * theme_context.scale_factor / 2 - 1e-6)
),
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
const scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
// like Clutter, we use the assumption radius = 2*sigma
this.set_uniform_value('sigma', parseFloat(this._radius * scale_factor / 2 - 1e-6));
this.set_enabled(this.radius > 0.);
if (this.chained_effect)
this.chained_effect.radius = value;
}
}
get brightness() {
return this._brightness;
}
set brightness(value) {
if (this._brightness !== value) {
this._brightness = value;
this.set_uniform_value('brightness', parseFloat(this._brightness - 1e-6));
if (this.chained_effect)
this.chained_effect.brightness = value;
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
if (this.chained_effect)
this.chained_effect.width = value;
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
if (this.chained_effect)
this.chained_effect.height = value;
}
}
get direction() {
return this._direction;
}
set direction(value) {
if (this._direction !== value)
this._direction = value;
}
get chained_effect() {
return this._chained_effect;
}
set chained_effect(value) {
this._chained_effect = value;
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
if (this.direction == 0) {
if (this.chained_effect)
this.chained_effect.get_actor()?.remove_effect(this.chained_effect);
else
this.chained_effect = new GaussianBlurEffect({
radius: this.radius,
brightness: this.brightness,
width: this.width,
height: this.height,
direction: 1
});
if (actor !== null)
actor.add_effect(this.chained_effect);
}
}
vfunc_paint_target(paint_node, paint_context) {
this.set_uniform_value("dir", this.direction);
super.vfunc_paint_target(paint_node, paint_context);
}
});

View File

@ -0,0 +1,14 @@
uniform sampler2D tex;
vec3 hsl_to_rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main(void) {
vec2 uv = cogl_tex_coord_in[0].xy;
vec4 hsla = texture2D(tex, uv);
vec4 rgba = vec4(hsl_to_rgb(hsla.xyz), hsla.w);
cogl_color_out = rgba;
}

View File

@ -0,0 +1,31 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'hsl_to_rgb.glsl';
const DEFAULT_PARAMS = {};
export const HslToRgbEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "HslToRgbEffect",
Properties: {}
}, class HslToRgbEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
utils.setup_params(this, params);
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
}
static get default_params() {
return DEFAULT_PARAMS;
}
});

View File

@ -0,0 +1,44 @@
uniform sampler2D tex;
uniform float radius;
uniform int iterations;
uniform float brightness;
uniform float width;
uniform float height;
uniform bool use_base_pixel;
float srand(vec2 a) {
return sin(dot(a, vec2(1233.224, 1743.335)));
}
float rand(inout float r) {
r = fract(3712.65 * r + 0.61432);
return (r - 0.5) * 2.0;
}
void main() {
vec2 uv = cogl_tex_coord0_in.st;
vec2 p = 16 * radius / vec2(width, height);
float r = srand(uv);
vec2 rv;
int count = 0;
vec4 c = vec4(0.);
for (int i = 0; i < iterations; i++) {
rv.x = rand(r);
rv.y = rand(r);
vec2 new_uv = uv + rv * p;
if (new_uv.x > 2. / width && new_uv.y > 2. / height && new_uv.x < 1. - 3. / width && new_uv.y < 1. - 3. / height) {
c += texture2D(tex, new_uv);
count += 1;
}
}
if (count == 0 || use_base_pixel) {
c += texture2D(tex, uv);
count += 1;
}
c.xyz *= brightness;
cogl_color_out = c / count;
}

View File

@ -0,0 +1,187 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'monte_carlo_blur.glsl';
const DEFAULT_PARAMS = {
radius: 2., iterations: 5, brightness: .6,
width: 0, height: 0, use_base_pixel: true
};
export const MonteCarloBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "MonteCarloBlurEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Radius`,
`Blur radius`,
GObject.ParamFlags.READWRITE,
0.0, 2000.0,
2.0,
),
'iterations': GObject.ParamSpec.int(
`iterations`,
`Iterations`,
`Blur iterations`,
GObject.ParamFlags.READWRITE,
0, 64,
5,
),
'brightness': GObject.ParamSpec.double(
`brightness`,
`Brightness`,
`Blur brightness`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.6,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'use_base_pixel': GObject.ParamSpec.boolean(
`use_base_pixel`,
`Use base pixel`,
`Use base pixel`,
GObject.ParamFlags.READWRITE,
true,
),
}
}, class MonteCarloBlurEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
utils.setup_params(this, params);
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject(
'notify::scale-factor',
_ => this.set_uniform_value('radius',
parseFloat(this._radius * theme_context.scale_factor - 1e-6)
),
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
const scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
this.set_uniform_value('radius', parseFloat(this._radius * scale_factor - 1e-6));
this.set_enabled(this.radius > 0. && this.iterations > 0);
}
}
get iterations() {
return this._iterations;
}
set iterations(value) {
if (this._iterations !== value) {
this._iterations = value;
this.set_uniform_value('iterations', this._iterations);
this.set_enabled(this.radius > 0. && this.iterations > 0);
}
}
get brightness() {
return this._brightness;
}
set brightness(value) {
if (this._brightness !== value) {
this._brightness = value;
this.set_uniform_value('brightness', parseFloat(this._brightness - 1e-6));
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
}
}
get use_base_pixel() {
return this._use_base_pixel;
}
set use_base_pixel(value) {
if (this._use_base_pixel !== value) {
this._use_base_pixel = value;
this.set_uniform_value('use_base_pixel', this._use_base_pixel ? 1 : 0);
}
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
}
});

Some files were not shown because too many files have changed in this diff Show More