1// This policy is independent of Shell so lifecycle and authorization can be tested without a desktop.
2export const CLIENT_NAMES = [
3 "com.t3tools.T3Code.SnapShot",
4 "com.t3tools.T3Code.Development.SnapShot",
5];
6
7export function isWaylandSession(meta) {
8 // GNOME 50 removed the X11 compositor and this API. GNOME 45–49 still need the check.
9 return typeof meta.is_wayland_compositor !== "function" || meta.is_wayland_compositor();
10}
11
12export class CaptureService {
13 constructor({ getNameOwner, isAvailable, takeSnapshot, getProcessId, beginFeedback }) {
14 this._getNameOwner = getNameOwner;
15 this._isAvailable = isAvailable;
16 this._takeSnapshot = takeSnapshot;
17 this._getProcessId = getProcessId;
18 this._beginFeedback = beginFeedback;
19 this._enabled = true;
20 this._busy = false;
21 }
22
23 disable() {
24 this._enabled = false;
25 }
26
27 _checkSession() {
28 if (!this._enabled || !this._isAvailable()) {
29 throw new Error("SnapShots are unavailable in this session.");
30 }
31 }
32
33 async capture(sender, options) {
34 this._checkSession();
35 if (this._busy) throw new Error("A snapshot is already in progress.");
36 this._busy = true;
37 try {
38 let allowed = false;
39 for (const name of CLIENT_NAMES) {
40 const owner = await this._getNameOwner(name);
41 if (owner === sender) {
42 allowed = true;
43 break;
44 }
45 }
46 if (!allowed) throw new Error("Only The Knife may request a snapshot.");
47 const pid = options ? await this._getProcessId(sender) : undefined;
48 this._checkSession();
49 const snapshot = await this._takeSnapshot(options?.animate ?? false);
50 // Never deliver pixels after locking the session or disabling the extension.
51 this._checkSession();
52 if (options) {
53 return {
54 ...snapshot,
55 animationStarted: this._beginFeedback(sender, pid, snapshot, options),
56 };
57 }
58 return snapshot;
59 } finally {
60 this._busy = false;
61 }
62 }
63}