Skip to content

Instantly share code, notes, and snippets.

@Maltysen
Last active December 8, 2024 14:41
Show Gist options
  • Save Maltysen/ef86043dcfb9a612074ccb86e70bdac8 to your computer and use it in GitHub Desktop.
Save Maltysen/ef86043dcfb9a612074ccb86e70bdac8 to your computer and use it in GitHub Desktop.
const Gi = imports._gi;
import GObject from 'gi://GObject';
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import {_monkey_vfunc_key_press_event, _monkey_onCaptureButtonClicked}
from './monkey_functions.js';
export default class ScreenshotExtension extends Extension {
// We monkey patch the Main.screenshotUI object to use our
// _monkey_vfunc_key_press_event and _monkey_onCaptureButtonClicked
// functions which decide whether to save to file or not
enable() {
Main.screenshotUI._old_vfunc_key_press_event =
Main.screenshotUI.vfunc_key_press_event;
// According to the first answer in this thread
// https://gitlab.gnome.org/GNOME/gjs/-/issues/470
// this is how you alter vfuncs
Object.getPrototypeOf(Main.screenshotUI)
[Gi.gobject_prototype_symbol][Gi.hook_up_vfunc_symbol]
('key_press_event', _monkey_vfunc_key_press_event);
GObject.signal_handlers_disconnect_matched(
Main.screenshotUI._captureButton,
{signalId: 'clicked'},
);
Main.screenshotUI._captureButton.connect('clicked',
_monkey_onCaptureButtonClicked.bind(Main.screenshotUI)
);
}
disable() {
GObject.signal_handlers_disconnect_matched(
Main.screenshotUI._captureButton,
{signalId: 'clicked'},
);
Main.screenshotUI._captureButton.connect('clicked',
Main.screenshotUI._onCaptureButtonClicked.bind(Main.screenshotUI)
);
Object.getPrototypeOf(Main.screenshotUI)
[Gi.gobject_prototype_symbol][Gi.hook_up_vfunc_symbol]
('key_press_event', Main.screenshotUI._old_vfunc_key_press_event);
delete Main.screenshotUI._old_vfunc_key_press_event;
}
}
{
"uuid": "[email protected]",
"name": "Screenshot Copy Patch",
"description": "Let's you copy screenshots to clipboard without saving them to disk.\n\nEnter/Space/Ctrl-C/click now only copies to clipboard, and Ctrl-S also saves it to the Screenshots directory.",
"shell-version": [ "47" ],
"url": "https://gist.github.com/Maltysen/ef86043dcfb9a612074ccb86e70bdac8"
}
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import St from 'gi://St';
import Shell from 'gi://Shell';
import Clutter from 'gi://Clutter';
import Cogl from 'gi://Cogl';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js';
async function _nofile_saveScreenshot() {
if (this._selectionButton.checked || this._screenButton.checked) {
const content = this._stageScreenshot.get_content();
if (!content)
return; // Failed to capture the screenshot for some reason.
const texture = content.get_texture();
const geometry = this._getSelectedGeometry(true);
let cursorTexture = this._cursor.content?.get_texture();
if (!this._cursor.visible)
cursorTexture = null;
try {
await _nofile_CaptureScreenshot(
texture, geometry, this._scale,
{
texture: cursorTexture ?? null,
x: this._cursor.x * this._scale,
y: this._cursor.y * this._scale,
scale: this._cursorScale,
}
);
} catch (e) {
logError(e, 'Error capturing screenshot');
}
} else if (this._windowButton.checked) {
const window =
this._windowSelectors.flatMap(selector => selector.windows())
.find(win => win.checked);
if (!window)
return;
const content = window.windowContent;
if (!content)
return;
const texture = content.get_texture();
let cursorTexture = window.getCursorTexture()?.get_texture();
if (!this._cursor.visible)
cursorTexture = null;
try {
await _nofile_CaptureScreenshot(
texture,
null,
window.bufferScale,
{
texture: cursorTexture ?? null,
x: window.cursorPoint.x * window.bufferScale,
y: window.cursorPoint.y * window.bufferScale,
scale: this._cursorScale,
}
);
} catch (e) {
logError(e, 'Error capturing screenshot');
}
}
// We *did* take a screenshot, but it wasn't stored
this.emit('screenshot-taken',
Gio.File.new_for_path('/dev/null'));
}
async function _nofile_CaptureScreenshot(texture, geometry, scale, cursor) {
const stream = Gio.MemoryOutputStream.new_resizable();
const [x, y, w, h] = geometry ?? [0, 0, -1, -1];
if (cursor === null)
cursor = {texture: null, x: 0, y: 0, scale: 1};
global.display.get_sound_player().play_from_theme(
'screen-capture', _('Screenshot taken'), null);
const pixbuf = await Shell.Screenshot.composite_to_stream(
texture,
x, y, w, h,
scale,
cursor.texture, cursor.x, cursor.y, cursor.scale,
stream
);
stream.close(null);
_nofile_StoreScreenshot(stream.steal_as_bytes(), pixbuf);
}
function _nofile_StoreScreenshot(bytes, pixbuf) {
const clipboard = St.Clipboard.get_default();
clipboard.set_content(St.ClipboardType.CLIPBOARD, 'image/png', bytes);
const pixels = pixbuf.read_pixel_bytes();
const content =
St.ImageContent.new_with_preferred_size(pixbuf.width, pixbuf.height);
content.set_bytes(
pixels,
Cogl.PixelFormat.RGBA_8888,
pixbuf.width,
pixbuf.height,
pixbuf.rowstride
);
// Show a notification.
const source = new MessageTray.Source({
// Translators: notification source name.
title: _('Screenshot'),
iconName: 'screenshot-recorded-symbolic',
});
const notification = new MessageTray.Notification({
source,
// Translators: notification title.
title: _('Screenshot copied'),
// Translators: notification body when a screenshot was captured.
body: _('You can paste the image from the clipboard.'),
datetime: GLib.DateTime.new_now_local(),
gicon: content,
isTransient: true,
});
Main.messageTray.add(source);
source.addNotification(notification);
}
export function _monkey_onCaptureButtonClicked() {
if (this._shotButton.checked) {
_nofile_saveScreenshot.call(this).catch(logError);
this.close();
} else {
// Screencast closes the UI on its own.
this._startScreencast();
}
}
// All the existing keybinds copy to clipboard, but I've
// added ctrl-S to do the original functionality
export function _monkey_vfunc_key_press_event(event) {
const symbol = event.get_key_symbol();
if (symbol === Clutter.KEY_Return || symbol === Clutter.KEY_space ||
symbol === Clutter.KEY_KP_Enter || symbol === Clutter.KEY_ISO_Enter ||
((event.get_state() & Clutter.ModifierType.CONTROL_MASK) &&
(symbol === Clutter.KEY_c || symbol === Clutter.KEY_C))) {
_monkey_onCaptureButtonClicked.call(this);
return Clutter.EVENT_STOP;
}
if ((event.get_state() & Clutter.ModifierType.CONTROL_MASK) &&
(symbol === Clutter.KEY_s || symbol === Clutter.KEY_S)) {
this._onCaptureButtonClicked();
return Clutter.EVENT_STOP;
}
return this._old_vfunc_key_press_event(event);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment