I know I previously said that I'd follow up my rewriting "night light slider" article with that detailing the write up of the preferences panel,
This article would be followed up by a write-up of the preferences panel rewrite
...but I am a true open source developer that lacks accountability! And lack accountability, I do. My last "Screenshot Locations" update was on the 14th of November 2017, and even that was but a metadata bump to support GNOME 3.26.
# Even the repository had moved since!
#
# remote: This repository moved. Please use the new location:
# remote: git@github.com:kiyui/gnome-shell-screenshotlocations-extension.git
# remote:
The Screenshot Locations extension works (or rather worked) by remapping GNOME's default screenshot keys to calls to GNOME screenshot, which should respect the value set at /org/gnome/gnome-screenshot/auto-save-directory
.
const screenshotKeys = [
{
name: 'area-screenshot',
shortcut: '<Shift>Print',
command: 'gnome-screenshot -a'
},
// etc
{
name: 'window-screenshot-clip',
shortcut: '<Ctrl><Alt>Print',
command: 'gnome-screenshot -w -c'
}
]
The extension does so because curiously, GNOME Shell does not actually use GNOME Screenshot to create screenshots. Instead, referring to the gnome-shell repository, we find a ScreenshotService
defined at js/ui/screenshot.js
. In both GNOME 3.36 and 3.38, a generator function called __resolveRelativeFilename
is used to generate file names for the stream of screenshots that will be created.
*_resolveRelativeFilename(filename) {
filename = filename.replace(/\.png$/, '');
let path = [
GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES),
GLib.get_home_dir(),
].find(p => GLib.file_test(p, GLib.FileTest.EXISTS));
if (!path)
return null;
yield Gio.File.new_for_path(
GLib.build_filenamev([path, `${filename}.png`]));
for (let idx = 1; ; idx++) {
yield Gio.File.new_for_path(
GLib.build_filenamev([path, `${filename}-${idx}.png`]));
}
}
This can be confirmed by running the following code in Looking Glass:
imports.ui.main.shellDBusService._screenshotService._resolveRelativeFilename('screenshot.png')
r(0).next().value.get_path() // Notice how this appends a number after the first invocation
Or visually,
the rewrite
The approach for the extension this time is to override the _resolveRelativeFilename
generator, injecting a user-provided directory instead of defaulting to the hard-coded values set by GNOME. I opted for writing my own schema instead of reusing the value from GNOME Screenshot at /org/gnome/gnome-screenshot/auto-save-directory
.
<schemalist>
<schema id="org.gnome.shell.extensions.screenshotlocations" path="/org/gnome/shell/extensions/screenshotlocations/">
<key name="save-directory" type="s">
<default>''</default>
<summary>Screenshot directory</summary>
<description>Manage where screenshots are saved</description>
</key>
</schema>
</schemalist>
Patching the generator is as simple as adding the save-directory
value to the list of seek-paths in the generator,
let path = [
+ this._preferences.get_string('save-directory'),
GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES),
GLib.get_home_dir(),
].find(p => GLib.file_test(p, GLib.FileTest.EXISTS));
..where the main function of the extension is to just perform this override:
class Extension {
constructor() {
this._preferences = ExtensionUtils.getSettings();
}
enable() {
Main.shellDBusService._screenshotService._original_resolveRelativeFilename = Main.shellDBusService._screenshotService._resolveRelativeFilename;
Main.shellDBusService._screenshotService._resolveRelativeFilename = this._resolveRelativeFilenameOverride.bind(this);
}
disable() {
Main.shellDBusService._screenshotService._resolveRelativeFilename = Main.shellDBusService._screenshotService._original_resolveRelativeFilename;
delete Main.shellDBusService._screenshotService._original_resolveRelativeFilename;
}
*_resolveRelativeFilenameOverride(filename) {
// etc
}
}
Except that did not work.
Digging into the gnome-shell repository, we find that the _initializeUI
function, which in turn initializes all extensions, is called before the shellDBusService
is even initialized.
_initializeUI();
// etc
shellDBusService = new ShellDBus.GnomeShell();
The correct way to detect when shellDBusService
is initialized would to over-engineer a solution, so instead I used a hack that ever JavaScript developer loves, setTimeout.
enable() {
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 0, () => {
Main.shellDBusService._screenshotService._original_resolveRelativeFilename = Main.shellDBusService._screenshotService._resolveRelativeFilename;
Main.shellDBusService._screenshotService._resolveRelativeFilename = this._resolveRelativeFilenameOverride.bind(this);
});
}
The amazing thing here is that because everything is run on a single thread, even a timeout of 0
would suffice to make sure that shellDBusService
is initialized.
done
The result is a much simpler extension that should hopefully be easier to maintain. The entire rewrite is available on GitHub Codeberg, including an updated preferences panel that makes use of libhandy.