%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /usr/share/gnome-shell/
Upload File :
Create Path :
Current File : //usr/share/gnome-shell/org.gnome.Shell.Screencast.src.gresource

GVariant�(	

��\	�
v��'��y�vg��<gLtx�QxL��M���v�	Ե����	LgU		v �]��
�L����$0�L��Xj7�L��M	��	�v�j>KP�j>Lp>t>�n��	t>v�>5Fě�c5FvPF[�n�k�	[�
vh���signals.jsimport * as SignalTracker from './signalTracker.js';

const Signals = imports.signals;

export class EventEmitter {
    connectObject(...args) {
        return SignalTracker.connectObject(this, ...args);
    }

    disconnectObject(...args) {
        return SignalTracker.disconnectObject(this, ...args);
    }

    connect_object(...args) {
        return this.connectObject(...args);
    }

    disconnect_object(...args) {
        return this.disconnectObject(...args);
    }
}

Signals.addSignalMethods(EventEmitter.prototype);
(uuay)dbusService.jsOimport Gio from 'gi://Gio';
import GLib from 'gi://GLib';

import {programArgs} from 'system';

import './misc/dbusErrors.js';

const Signals = imports.signals;

const IDLE_SHUTDOWN_TIME = 2; // s

export class ServiceImplementation {
    constructor(info, objectPath) {
        this._objectPath = objectPath;
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(info, this);

        this._injectTracking('return_dbus_error');
        this._injectTracking('return_error_literal');
        this._injectTracking('return_gerror');
        this._injectTracking('return_value');
        this._injectTracking('return_value_with_unix_fd_list');

        this._senders = new Map();
        this._holdCount = 0;

        this._shellName = this._getUniqueShellName();

        this._hasSignals = this._dbusImpl.get_info().signals.length > 0;
        this._shutdownTimeoutId = 0;

        // subclasses may override this to disable automatic shutdown
        this._autoShutdown = true;

        this._queueShutdownCheck();
    }

    // subclasses may override this to own additional names
    register() {
    }

    export() {
        this._dbusImpl.export(Gio.DBus.session, this._objectPath);
    }

    unexport() {
        this._dbusImpl.unexport();
    }

    hold() {
        this._holdCount++;
    }

    release() {
        if (this._holdCount === 0) {
            logError(new Error('Unmatched call to release()'));
            return;
        }

        this._holdCount--;

        if (this._holdCount === 0)
            this._queueShutdownCheck();
    }

    /**
     * Complete @invocation with an appropriate error if @error is set;
     * useful for implementing early returns from method implementations.
     *
     * @param {Gio.DBusMethodInvocation}
     * @param {Error}
     *
     * @returns {bool} - true if @invocation was completed
     */

    _handleError(invocation, error) {
        if (error === null)
            return false;

        if (error instanceof GLib.Error) {
            Gio.DBusError.strip_remote_error(error);
            invocation.return_gerror(error);
        } else {
            let name = error.name;
            if (!name.includes('.')) // likely a normal JS error
                name = `org.gnome.gjs.JSError.${name}`;
            invocation.return_dbus_error(name, error.message);
        }

        return true;
    }

    _maybeShutdown() {
        if (!this._autoShutdown)
            return;

        if (GLib.getenv('SHELL_DBUS_PERSIST'))
            return;

        if (this._holdCount > 0)
            return;

        this.emit('shutdown');
    }

    _queueShutdownCheck() {
        if (this._shutdownTimeoutId)
            GLib.source_remove(this._shutdownTimeoutId);

        this._shutdownTimeoutId = GLib.timeout_add_seconds(
            GLib.PRIORITY_DEFAULT, IDLE_SHUTDOWN_TIME,
            () => {
                this._shutdownTimeoutId = 0;
                this._maybeShutdown();

                return GLib.SOURCE_REMOVE;
            });
    }

    _trackSender(sender) {
        if (this._senders.has(sender))
            return;

        if (sender === this._shellName)
            return; // don't track the shell

        this.hold();
        this._senders.set(sender,
            this._dbusImpl.get_connection().watch_name(
                sender,
                Gio.BusNameWatcherFlags.NONE,
                null,
                () => this._untrackSender(sender)));
    }

    _untrackSender(sender) {
        const id = this._senders.get(sender);

        if (id)
            this._dbusImpl.get_connection().unwatch_name(id);

        if (this._senders.delete(sender))
            this.release();
    }

    _injectTracking(methodName) {
        const {prototype} = Gio.DBusMethodInvocation;
        const origMethod = prototype[methodName];
        const that = this;

        prototype[methodName] = function (...args) {
            origMethod.apply(this, args);

            if (that._hasSignals)
                that._trackSender(this.get_sender());

            that._queueShutdownCheck();
        };
    }

    _getUniqueShellName() {
        try {
            const res = Gio.DBus.session.call_sync(
                'org.freedesktop.DBus',
                '/org/freedesktop/DBus',
                'org.freedesktop.DBus',
                'GetNameOwner',
                new GLib.Variant('(s)', ['org.gnome.Shell']),
                null,
                Gio.DBusCallFlags.NONE,
                -1,
                null);
            const [name] = res.deepUnpack();
            return name;
        } catch (e) {
            console.warn(`Failed to resolve shell name: ${e.message}`);
            return '';
        }
    }
}
Signals.addSignalMethods(ServiceImplementation.prototype);

export class DBusService {
    constructor(name, service) {
        this._name = name;
        this._service = service;
        this._loop = new GLib.MainLoop(null, false);

        this._service.connect('shutdown', () => this._loop.quit());
    }

    async runAsync() {
        // Bail out when not running under gnome-shell
        Gio.DBus.watch_name(Gio.BusType.SESSION,
            'org.gnome.Shell',
            Gio.BusNameWatcherFlags.NONE,
            null,
            () => this._loop.quit());

        this._service.register();

        let flags = Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT;
        if (programArgs.includes('--replace'))
            flags |= Gio.BusNameOwnerFlags.REPLACE;

        Gio.DBus.own_name(Gio.BusType.SESSION,
            this._name,
            flags,
            () => this._service.export(),
            null,
            () => this._loop.quit());

        await this._loop.runAsync();
    }
}
(uuay)Screencast/Shell/main.jsiimport {DBusService} from './dbusService.js';
import {ScreencastService} from './screencastService.js';

/** @returns {void} */
export async function main() {
    if (!ScreencastService.canScreencast())
        return;

    const service = new DBusService(
        'org.gnome.Shell.Screencast',
        new ScreencastService());
    await service.runAsync();
}
(uuay)/config.jse// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const pkg = imports.package;

/* The name of this package (not localized) */
export const PACKAGE_NAME = 'gnome-shell';
/* The version of this package */
export const PACKAGE_VERSION = '46.0';
/* 1 if networkmanager is available, 0 otherwise */
export const HAVE_NETWORKMANAGER = 1;
/* 1 if portal helper is enabled, 0 otherwise */
export const HAVE_PORTAL_HELPER = 0;
/* gettext package */
export const GETTEXT_PACKAGE = 'gnome-shell';
/* locale dir */
export const LOCALEDIR = '/usr/share/locale';
/* other standard directories */
export const LIBEXECDIR = '/usr/libexec';
export const PKGDATADIR = '/usr/share/gnome-shell';
/* g-i package versions */
export const LIBMUTTER_API_VERSION = '14';

export const HAVE_BLUETOOTH = pkg.checkSymbol('GnomeBluetooth', '3.0',
    'Client.default_adapter_state');
(uuay)js/	
gnome/misc/
signalTracker.jszimport GObject from 'gi://GObject';

const destroyableTypes = [];

/**
 * @private
 * @param {object} obj - an object
 * @returns {bool} - true if obj has a 'destroy' GObject signal
 */
function _hasDestroySignal(obj) {
    return destroyableTypes.some(type => obj instanceof type);
}

export const TransientSignalHolder = GObject.registerClass(
class TransientSignalHolder extends GObject.Object {
    static [GObject.signals] = {
        'destroy': {},
    };

    constructor(owner) {
        super();

        if (_hasDestroySignal(owner))
            owner.connectObject('destroy', () => this.destroy(), this);
    }

    destroy() {
        this.emit('destroy');
    }
});
registerDestroyableType(TransientSignalHolder);

class SignalManager {
    /**
     * @returns {SignalManager} - the SignalManager singleton
     */
    static getDefault() {
        if (!this._singleton)
            this._singleton = new SignalManager();
        return this._singleton;
    }

    constructor() {
        this._signalTrackers = new Map();

        global.connect_after('shutdown', () => {
            [...this._signalTrackers.values()].forEach(
                tracker => tracker.destroy());
            this._signalTrackers.clear();
        });
    }

    /**
     * @param {object} obj - object to get signal tracker for
     * @returns {SignalTracker} - the signal tracker for object
     */
    getSignalTracker(obj) {
        let signalTracker = this._signalTrackers.get(obj);
        if (signalTracker === undefined) {
            signalTracker = new SignalTracker(obj);
            this._signalTrackers.set(obj, signalTracker);
        }
        return signalTracker;
    }

    /**
     * @param {object} obj - object to get signal tracker for
     * @returns {?SignalTracker} - the signal tracker for object if it exists
     */
    maybeGetSignalTracker(obj) {
        return this._signalTrackers.get(obj) ?? null;
    }

    /*
     * @param {object} obj - object to remove signal tracker for
     * @returns {void}
     */
    removeSignalTracker(obj) {
        this._signalTrackers.delete(obj);
    }
}

class SignalTracker {
    /**
     * @param {object=} owner - object that owns the tracker
     */
    constructor(owner) {
        if (_hasDestroySignal(owner))
            this._ownerDestroyId = owner.connect_after('destroy', () => this.clear());

        this._owner = owner;
        this._map = new Map();
    }

    /**
     * @typedef SignalData
     * @property {number[]} ownerSignals - a list of handler IDs
     * @property {number} destroyId - destroy handler ID of tracked object
     */

    /**
     * @private
     * @param {object} obj - a tracked object
     * @returns {SignalData} - signal data for object
     */
    _getSignalData(obj) {
        let data = this._map.get(obj);
        if (data === undefined) {
            data = {ownerSignals: [], destroyId: 0};
            this._map.set(obj, data);
        }
        return data;
    }

    /**
     * @private
     * @param {GObject.Object} obj - tracked widget
     */
    _trackDestroy(obj) {
        const signalData = this._getSignalData(obj);
        if (signalData.destroyId)
            return;
        signalData.destroyId = obj.connect_after('destroy', () => this.untrack(obj));
    }

    _disconnectSignalForProto(proto, obj, id) {
        proto['disconnect'].call(obj, id);
    }

    _getObjectProto(obj) {
        return obj instanceof GObject.Object
            ? GObject.Object.prototype
            : Object.getPrototypeOf(obj);
    }

    _disconnectSignal(obj, id) {
        this._disconnectSignalForProto(this._getObjectProto(obj), obj, id);
    }

    _removeTracker() {
        if (this._ownerDestroyId)
            this._disconnectSignal(this._owner, this._ownerDestroyId);

        SignalManager.getDefault().removeSignalTracker(this._owner);

        delete this._ownerDestroyId;
        delete this._owner;
    }

    /**
     * @param {object} obj - tracked object
     * @param {...number} handlerIds - tracked handler IDs
     * @returns {void}
     */
    track(obj, ...handlerIds) {
        if (_hasDestroySignal(obj))
            this._trackDestroy(obj);

        this._getSignalData(obj).ownerSignals.push(...handlerIds);
    }

    /**
     * @param {object} obj - tracked object instance
     * @returns {void}
     */
    untrack(obj) {
        const {ownerSignals, destroyId} = this._getSignalData(obj);
        this._map.delete(obj);

        const ownerProto = this._getObjectProto(this._owner);
        ownerSignals.forEach(id =>
            this._disconnectSignalForProto(ownerProto, this._owner, id));
        if (destroyId)
            this._disconnectSignal(obj, destroyId);

        if (this._map.size === 0)
            this._removeTracker();
    }

    /**
     * @returns {void}
     */
    clear() {
        this._map.forEach((_, obj) => this.untrack(obj));
    }

    /**
     * @returns {void}
     */
    destroy() {
        this.clear();
        this._removeTracker();
    }
}

/**
 * Connect one or more signals, and associate the handlers
 * with a tracked object.
 *
 * All handlers for a particular object can be disconnected
 * by calling disconnectObject(). If object is a {Clutter.widget},
 * this is done automatically when the widget is destroyed.
 *
 * @param {object} thisObj - the emitter object
 * @param {...any} args - a sequence of signal-name/handler pairs
 * with an optional flags value, followed by an object to track
 * @returns {void}
 */
export function connectObject(thisObj, ...args) {
    const getParams = argArray => {
        const [signalName, handler, arg, ...rest] = argArray;
        if (typeof arg !== 'number')
            return [signalName, handler, 0, arg, ...rest];

        const flags = arg;
        let flagsMask = 0;
        Object.values(GObject.ConnectFlags).forEach(v => (flagsMask |= v));
        if (!(flags & flagsMask))
            throw new Error(`Invalid flag value ${flags}`);
        if (flags & GObject.ConnectFlags.SWAPPED)
            throw new Error('Swapped signals are not supported');
        return [signalName, handler, flags, ...rest];
    };

    const connectSignal = (emitter, signalName, handler, flags) => {
        const isGObject = emitter instanceof GObject.Object;
        const func = (flags & GObject.ConnectFlags.AFTER) && isGObject
            ? 'connect_after'
            : 'connect';
        const emitterProto = isGObject
            ? GObject.Object.prototype
            : Object.getPrototypeOf(emitter);
        return emitterProto[func].call(emitter, signalName, handler);
    };

    const signalIds = [];
    while (args.length > 1) {
        const [signalName, handler, flags, ...rest] = getParams(args);
        signalIds.push(connectSignal(thisObj, signalName, handler, flags));
        args = rest;
    }

    const obj = args.at(0) ?? globalThis;
    const tracker = SignalManager.getDefault().getSignalTracker(thisObj);
    tracker.track(obj, ...signalIds);
}

/**
 * Disconnect all signals that were connected for
 * the specified tracked object
 *
 * @param {object} thisObj - the emitter object
 * @param {object} obj - the tracked object
 * @returns {void}
 */
export function disconnectObject(thisObj, obj) {
    SignalManager.getDefault().maybeGetSignalTracker(thisObj)?.untrack(obj);
}

/**
 * Register a GObject type as having a 'destroy' signal
 * that should disconnect all handlers
 *
 * @param {GObject.Type} gtype - a GObject type
 */
export function registerDestroyableType(gtype) {
    if (!GObject.type_is_a(gtype, GObject.Object))
        throw new Error(`${gtype} is not a GObject subclass`);

    if (!GObject.signal_lookup('destroy', gtype))
        throw new Error(`${gtype} does not have a destroy signal`);

    destroyableTypes.push(gtype);
}
(uuay)org/dbusUtils.js�import Gio from 'gi://Gio';
import GLib from 'gi://GLib';

import * as Config from './config.js';

let _ifaceResource = null;

/**
 * @private
 */
function _ensureIfaceResource() {
    if (_ifaceResource)
        return;

    // don't use global.datadir so the method is usable from tests/tools
    let dir = GLib.getenv('GNOME_SHELL_DATADIR') || Config.PKGDATADIR;
    let path = `${dir}/gnome-shell-dbus-interfaces.gresource`;
    _ifaceResource = Gio.Resource.load(path);
    _ifaceResource._register();
}

/**
 * @param {string} iface the interface name
 * @returns {string | null} the XML string or null if it is not found
 */
export function loadInterfaceXML(iface) {
    _ensureIfaceResource();

    let uri = `resource:///org/gnome/shell/dbus-interfaces/${iface}.xml`;
    let f = Gio.File.new_for_uri(uri);

    try {
        let [ok_, bytes] = f.load_contents(null);
        return new TextDecoder().decode(bytes);
    } catch (e) {
        log(`Failed to load D-Bus interface ${iface}`);
    }

    return null;
}

/**
 * @param {string} iface the interface name
 * @param {string} ifaceFile the interface filename
 * @returns {string | null} the XML string or null if it is not found
 */
export function loadSubInterfaceXML(iface, ifaceFile) {
    let xml = loadInterfaceXML(ifaceFile);
    if (!xml)
        return null;

    let ifaceStartTag = `<interface name="${iface}">`;
    let ifaceStopTag = '</interface>';
    let ifaceStartIndex = xml.indexOf(ifaceStartTag);
    let ifaceEndIndex = xml.indexOf(ifaceStopTag, ifaceStartIndex + 1) + ifaceStopTag.length;

    let xmlHeader = '<!DOCTYPE node PUBLIC\n' +
        '\'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\'\n' +
        '\'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\'>\n' +
        '<node>\n';
    let xmlFooter = '</node>';

    return (
        xmlHeader +
        xml.substr(ifaceStartIndex, ifaceEndIndex - ifaceStartIndex) +
        xmlFooter);
}
(uuay)screencastService.js�g// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import Gst from 'gi://Gst?version=1.0';
import Gtk from 'gi://Gtk?version=4.0';

import {ServiceImplementation} from './dbusService.js';

import {ScreencastErrors, ScreencastError} from './misc/dbusErrors.js';
import {loadInterfaceXML, loadSubInterfaceXML} from './misc/dbusUtils.js';
import * as Signals from './misc/signals.js';

const ScreencastIface = loadInterfaceXML('org.gnome.Shell.Screencast');

const IntrospectIface = loadInterfaceXML('org.gnome.Shell.Introspect');
const IntrospectProxy = Gio.DBusProxy.makeProxyWrapper(IntrospectIface);

const ScreenCastIface = loadSubInterfaceXML(
    'org.gnome.Mutter.ScreenCast', 'org.gnome.Mutter.ScreenCast');
const ScreenCastSessionIface = loadSubInterfaceXML(
    'org.gnome.Mutter.ScreenCast.Session', 'org.gnome.Mutter.ScreenCast');
const ScreenCastStreamIface = loadSubInterfaceXML(
    'org.gnome.Mutter.ScreenCast.Stream', 'org.gnome.Mutter.ScreenCast');
const ScreenCastProxy = Gio.DBusProxy.makeProxyWrapper(ScreenCastIface);
const ScreenCastSessionProxy = Gio.DBusProxy.makeProxyWrapper(ScreenCastSessionIface);
const ScreenCastStreamProxy = Gio.DBusProxy.makeProxyWrapper(ScreenCastStreamIface);

const DEFAULT_FRAMERATE = 30;
const DEFAULT_DRAW_CURSOR = true;

const PIPELINE_BLOCKLIST_FILENAME = 'gnome-shell-screencast-pipeline-blocklist';

const PIPELINES = [
    {
        id: 'swenc-dmabuf-h264-openh264',
        fileExtension: 'mp4',
        pipelineString:
            'capsfilter caps=video/x-raw(memory:DMABuf),max-framerate=%F/1 ! \
             glupload ! glcolorconvert ! gldownload ! \
             queue ! \
             openh264enc deblocking=off background-detection=false complexity=low adaptive-quantization=false qp-max=26 qp-min=26 multi-thread=%T slice-mode=auto ! \
             queue ! \
             h264parse ! \
             mp4mux fragment-duration=500 fragment-mode=first-moov-then-finalise',
    },
    {
        id: 'swenc-memfd-h264-openh264',
        fileExtension: 'mp4',
        pipelineString:
            'capsfilter caps=video/x-raw,max-framerate=%F/1 ! \
             videoconvert chroma-mode=none dither=none matrix-mode=output-only n-threads=%T ! \
             queue ! \
             openh264enc deblocking=off background-detection=false complexity=low adaptive-quantization=false qp-max=26 qp-min=26 multi-thread=%T slice-mode=auto ! \
             queue ! \
             h264parse ! \
             mp4mux fragment-duration=500 fragment-mode=first-moov-then-finalise',
    },
    {
        id: 'swenc-dmabuf-vp8-vp8enc',
        fileExtension: 'webm',
        pipelineString:
            'capsfilter caps=video/x-raw(memory:DMABuf),max-framerate=%F/1 ! \
             glupload ! glcolorconvert ! gldownload ! \
             queue ! \
             vp8enc cpu-used=16 max-quantizer=17 deadline=1 keyframe-mode=disabled threads=%T static-threshold=1000 buffer-size=20000 ! \
             queue ! \
             webmmux',
    },
    {
        id: 'swenc-memfd-vp8-vp8enc',
        fileExtension: 'webm',
        pipelineString:
            'capsfilter caps=video/x-raw,max-framerate=%F/1 ! \
             videoconvert chroma-mode=none dither=none matrix-mode=output-only n-threads=%T ! \
             queue ! \
             vp8enc cpu-used=16 max-quantizer=17 deadline=1 keyframe-mode=disabled threads=%T static-threshold=1000 buffer-size=20000 ! \
             queue ! \
             webmmux',
    },
];

const PipelineState = {
    INIT: 'INIT',
    STARTING: 'STARTING',
    PLAYING: 'PLAYING',
    FLUSHING: 'FLUSHING',
    STOPPED: 'STOPPED',
    ERROR: 'ERROR',
};

const SessionState = {
    INIT: 'INIT',
    ACTIVE: 'ACTIVE',
    STOPPED: 'STOPPED',
};

class Recorder extends Signals.EventEmitter {
    constructor(sessionPath, x, y, width, height, filePathStem, options,
        invocation) {
        super();

        this._dbusConnection = invocation.get_connection();

        this._x = x;
        this._y = y;
        this._width = width;
        this._height = height;
        this._filePathStem = filePathStem;

        try {
            const dir = Gio.File.new_for_path(filePathStem).get_parent();
            dir.make_directory_with_parents(null);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS))
                throw e;
        }

        this._pipelineString = null;
        this._framerate = DEFAULT_FRAMERATE;
        this._drawCursor = DEFAULT_DRAW_CURSOR;
        this._blocklistFromPreviousCrashes = [];

        const pipelineBlocklistPath = GLib.build_filenamev(
            [GLib.get_user_runtime_dir(), PIPELINE_BLOCKLIST_FILENAME]);
        this._pipelineBlocklistFile = Gio.File.new_for_path(pipelineBlocklistPath);

        try {
            const [success_, contents] = this._pipelineBlocklistFile.load_contents(null);
            const decoder = new TextDecoder();
            this._blocklistFromPreviousCrashes = JSON.parse(decoder.decode(contents));
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
                throw e;
        }

        this._pipelineState = PipelineState.INIT;
        this._pipeline = null;

        this._applyOptions(options);
        this._watchSender(invocation.get_sender());

        this._sessionState = SessionState.INIT;
        this._initSession(sessionPath);
    }

    _applyOptions(options) {
        for (const option in options)
            options[option] = options[option].deepUnpack();

        if (options['pipeline'] !== undefined)
            this._pipelineString = options['pipeline'];
        if (options['framerate'] !== undefined)
            this._framerate = options['framerate'];
        if ('draw-cursor' in options)
            this._drawCursor = options['draw-cursor'];
    }

    _addRecentItem() {
        const file = Gio.File.new_for_path(this._filePath);
        Gtk.RecentManager.get_default().add_item(file.get_uri());
    }

    _watchSender(sender) {
        this._nameWatchId = this._dbusConnection.watch_name(
            sender,
            Gio.BusNameWatcherFlags.NONE,
            null,
            this._senderVanished.bind(this));
    }

    _unwatchSender() {
        if (this._nameWatchId !== 0) {
            this._dbusConnection.unwatch_name(this._nameWatchId);
            this._nameWatchId = 0;
        }
    }

    _teardownPipeline() {
        if (!this._pipeline)
            return;

        if (this._pipeline.set_state(Gst.State.NULL) !== Gst.StateChangeReturn.SUCCESS)
            log('Failed to set pipeline state to NULL');

        this._pipelineState = PipelineState.STOPPED;
        this._pipeline = null;
    }

    _stopSession() {
        if (this._sessionState === SessionState.ACTIVE) {
            this._sessionState = SessionState.STOPPED;
            this._sessionProxy.StopSync();
        }
    }

    _bailOutOnError(message, errorDomain = ScreencastErrors, errorCode = ScreencastError.RECORDER_ERROR) {
        const error = new GLib.Error(errorDomain, errorCode, message);

        // If it's a PIPELINE_ERROR, we want to leave the failing pipeline on the
        // blocklist for the next time. Other errors are pipeline-independent, so
        // reset the blocklist to allow the pipeline to be tried again next time.
        if (!error.matches(ScreencastErrors, ScreencastError.PIPELINE_ERROR))
            this._updateServiceCrashBlocklist([...this._blocklistFromPreviousCrashes]);

        this._teardownPipeline();
        this._unwatchSender();
        this._stopSession();

        if (this._startRequest) {
            this._startRequest.reject(error);
            delete this._startRequest;
        }

        if (this._stopRequest) {
            this._stopRequest.reject(error);
            delete this._stopRequest;
        }

        this.emit('error', error);
    }

    _handleFatalPipelineError(message, errorDomain, errorCode) {
        this._pipelineState = PipelineState.ERROR;
        this._bailOutOnError(message, errorDomain, errorCode);
    }

    _senderVanished() {
        this._bailOutOnError('Sender has vanished');
    }

    _onSessionClosed() {
        if (this._sessionState === SessionState.STOPPED)
            return; // We closed the session ourselves

        this._sessionState = SessionState.STOPPED;
        this._bailOutOnError('Session closed unexpectedly');
    }

    _initSession(sessionPath) {
        this._sessionProxy = new ScreenCastSessionProxy(Gio.DBus.session,
            'org.gnome.Mutter.ScreenCast',
            sessionPath);
        this._sessionProxy.connectSignal('Closed', this._onSessionClosed.bind(this));
    }

    _updateServiceCrashBlocklist(blocklist) {
        try {
            if (blocklist.length === 0) {
                this._pipelineBlocklistFile.delete(null);
            } else {
                this._pipelineBlocklistFile.replace_contents(
                    JSON.stringify(blocklist), null, false,
                    Gio.FileCreateFlags.NONE, null);
            }
        } catch (e) {
            console.log(`Failed to update pipeline-blocklist file: ${e.message}`);
        }
    }

    _tryNextPipeline() {
        if (this._filePath) {
            GLib.unlink(this._filePath);
            delete this._filePath;
        }

        const {done, value: pipelineConfig} = this._pipelineConfigs.next();
        if (done) {
            this._handleFatalPipelineError('All pipelines failed to start',
                ScreencastErrors, ScreencastError.ALL_PIPELINES_FAILED);
            return;
        }

        if (this._blocklistFromPreviousCrashes.includes(pipelineConfig.id)) {
            console.info(`Skipping pipeline '${pipelineConfig.id}' due to pipeline blocklist`);
            this._tryNextPipeline();
            return;
        }

        if (this._pipeline) {
            if (this._pipeline.set_state(Gst.State.NULL) !== Gst.StateChangeReturn.SUCCESS)
                log('Failed to set pipeline state to NULL');

            this._pipeline = null;
        }

        try {
            this._pipeline = this._createPipeline(this._nodeId, pipelineConfig,
                this._framerate);

            // Add the current pipeline to the blocklist, so it is skipped next
            // time in case we crash; we'll remove it again on success or on
            // non-pipeline-related failures.
            this._updateServiceCrashBlocklist(
                [...this._blocklistFromPreviousCrashes, pipelineConfig.id]);
        } catch (error) {
            this._tryNextPipeline();
            return;
        }

        const bus = this._pipeline.get_bus();
        bus.add_watch(bus, this._onBusMessage.bind(this));

        const retval = this._pipeline.set_state(Gst.State.PLAYING);

        if (retval === Gst.StateChangeReturn.SUCCESS ||
            retval === Gst.StateChangeReturn.ASYNC) {
            // We'll wait for the state change message to PLAYING on the bus
        } else {
            this._tryNextPipeline();
        }
    }

    *_getPipelineConfigs() {
        if (this._pipelineString) {
            yield {
                pipelineString:
                    `capsfilter caps=video/x-raw,max-framerate=%F/1 ! ${this._pipelineString}`,
            };
            return;
        }

        const fallbackSupported =
                Gst.Registry.get().check_feature_version('pipewiresrc', 0, 3, 67);
        if (fallbackSupported)
            yield* PIPELINES;
        else
            yield PIPELINES.at(-1);
    }

    startRecording() {
        return new Promise((resolve, reject) => {
            this._startRequest = {resolve, reject};

            const [streamPath] = this._sessionProxy.RecordAreaSync(
                this._x, this._y,
                this._width, this._height,
                {
                    'is-recording': GLib.Variant.new('b', true),
                    'cursor-mode': GLib.Variant.new('u', this._drawCursor ? 1 : 0),
                });

            this._streamProxy = new ScreenCastStreamProxy(Gio.DBus.session,
                'org.gnome.Mutter.ScreenCast',
                streamPath);

            this._streamProxy.connectSignal('PipeWireStreamAdded',
                (_proxy, _sender, params) => {
                    const [nodeId] = params;
                    this._nodeId = nodeId;

                    this._pipelineState = PipelineState.STARTING;
                    this._pipelineConfigs = this._getPipelineConfigs();
                    this._tryNextPipeline();
                });
            this._sessionProxy.StartSync();
            this._sessionState = SessionState.ACTIVE;
        });
    }

    stopRecording() {
        if (this._startRequest)
            return Promise.reject(new Error('Unable to stop recorder while still starting'));

        return new Promise((resolve, reject) => {
            this._stopRequest = {resolve, reject};

            this._pipelineState = PipelineState.FLUSHING;
            this._pipeline.send_event(Gst.Event.new_eos());
        });
    }

    _onBusMessage(bus, message, _) {
        switch (message.type) {
        case Gst.MessageType.STATE_CHANGED: {
            const [, newState] = message.parse_state_changed();

            if (this._pipelineState === PipelineState.STARTING &&
                message.src === this._pipeline &&
                newState === Gst.State.PLAYING) {
                this._pipelineState = PipelineState.PLAYING;

                this._startRequest.resolve(this._filePath);
                delete this._startRequest;
            }

            break;
        }

        case Gst.MessageType.EOS:
            switch (this._pipelineState) {
            case PipelineState.INIT:
            case PipelineState.STOPPED:
            case PipelineState.ERROR:
                // In these cases there should be no pipeline, so should never happen
                break;

            case PipelineState.STARTING:
                // This is something we can handle, try to switch to the next pipeline
                this._tryNextPipeline();
                break;

            case PipelineState.PLAYING:
                this._addRecentItem();
                this._handleFatalPipelineError('Unexpected EOS message',
                    ScreencastErrors, ScreencastError.PIPELINE_ERROR);
                break;

            case PipelineState.FLUSHING:
                // The pipeline ran successfully and we didn't crash; we can remove it
                // from the blocklist again now.
                this._updateServiceCrashBlocklist([...this._blocklistFromPreviousCrashes]);

                this._addRecentItem();

                this._teardownPipeline();
                this._unwatchSender();
                this._stopSession();

                this._stopRequest.resolve();
                delete this._stopRequest;
                break;
            default:
                break;
            }

            break;

        case Gst.MessageType.ERROR:
            switch (this._pipelineState) {
            case PipelineState.INIT:
            case PipelineState.STOPPED:
            case PipelineState.ERROR:
                // In these cases there should be no pipeline, so should never happen
                break;

            case PipelineState.STARTING:
                // This is something we can handle, try to switch to the next pipeline
                this._tryNextPipeline();
                break;

            case PipelineState.PLAYING:
            case PipelineState.FLUSHING: {
                const [error] = message.parse_error();

                if (error.matches(Gst.ResourceError, Gst.ResourceError.NO_SPACE_LEFT)) {
                    this._handleFatalPipelineError('Out of disk space',
                        ScreencastErrors, ScreencastError.OUT_OF_DISK_SPACE);
                } else {
                    this._handleFatalPipelineError(
                        `GStreamer error while in state ${this._pipelineState}: ${error.message}`,
                        ScreencastErrors, ScreencastError.PIPELINE_ERROR);
                }

                break;
            }

            default:
                break;
            }

            break;

        default:
            break;
        }
        return true;
    }

    _substituteVariables(pipelineDescr, framerate) {
        const numProcessors = GLib.get_num_processors();
        const numThreads = Math.min(Math.max(1, numProcessors), 64);
        return pipelineDescr.replaceAll('%T', numThreads).replaceAll('%F', framerate);
    }

    _createPipeline(nodeId, pipelineConfig, framerate) {
        const {fileExtension, pipelineString} = pipelineConfig;
        const finalPipelineString = this._substituteVariables(pipelineString, framerate);
        this._filePath = `${this._filePathStem}.${fileExtension}`;

        const fullPipeline = `
            pipewiresrc path=${nodeId}
                        do-timestamp=true
                        keepalive-time=1000
                        resend-last=true !
            ${finalPipelineString} !
            filesink location="${this._filePath}"`;

        return Gst.parse_launch_full(fullPipeline, null,
            Gst.ParseFlags.FATAL_ERRORS);
    }
}

export const ScreencastService = class extends ServiceImplementation {
    static canScreencast() {
        if (!Gst.init_check(null))
            return false;

        let elements = [
            'pipewiresrc',
            'filesink',
        ];

        if (elements.some(e => Gst.ElementFactory.find(e) === null))
            return false;

        // The fallback pipeline must be available, the other ones are not
        // guaranteed to work because they depend on hw encoders.
        const fallbackPipeline = PIPELINES.at(-1);

        elements = fallbackPipeline.pipelineString.split('!').map(
            e => e.trim().split(' ').at(0));

        if (elements.every(e => Gst.ElementFactory.find(e) !== null))
            return true;

        return false;
    }

    constructor() {
        super(ScreencastIface, '/org/gnome/Shell/Screencast');

        this.hold(); // gstreamer initializing can take a bit
        this._canScreencast = ScreencastService.canScreencast();

        Gst.init(null);
        Gtk.init();

        this.release();

        this._recorders = new Map();
        this._senders = new Map();

        this._lockdownSettings = new Gio.Settings({
            schema_id: 'org.gnome.desktop.lockdown',
        });

        this._proxy = new ScreenCastProxy(Gio.DBus.session,
            'org.gnome.Mutter.ScreenCast',
            '/org/gnome/Mutter/ScreenCast');

        this._introspectProxy = new IntrospectProxy(Gio.DBus.session,
            'org.gnome.Shell.Introspect',
            '/org/gnome/Shell/Introspect');
    }

    get ScreencastSupported() {
        return this._canScreencast;
    }

    _removeRecorder(sender) {
        if (!this._recorders.delete(sender))
            return;

        if (this._recorders.size === 0)
            this.release();
    }

    _addRecorder(sender, recorder) {
        this._recorders.set(sender, recorder);
        if (this._recorders.size === 1)
            this.hold();
    }

    _getAbsolutePath(filename) {
        if (GLib.path_is_absolute(filename))
            return filename;

        const videoDir =
            GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_VIDEOS) ||
            GLib.get_home_dir();

        return GLib.build_filenamev([videoDir, filename]);
    }

    _generateFilePath(template) {
        let filename = '';
        let escape = false;

        // FIXME: temporarily detect and strip .webm prefix to avoid breaking
        // external consumers of our API, remove this again
        if (template.endsWith('.webm')) {
            console.log("'file_template' for screencast includes '.webm' file-extension. Passing the file-extension as part of the filename has been deprecated, pass the 'file_template' without a file-extension instead.");
            template = template.substring(0, template.length - '.webm'.length);
        }

        [...template].forEach(c => {
            if (escape) {
                switch (c) {
                case '%':
                    filename += '%';
                    break;
                case 'd': {
                    const datetime = GLib.DateTime.new_now_local();
                    const datestr = datetime.format('%Y-%m-%d');

                    filename += datestr;
                    break;
                }

                case 't': {
                    const datetime = GLib.DateTime.new_now_local();
                    const datestr = datetime.format('%H-%M-%S');

                    filename += datestr;
                    break;
                }

                default:
                    log(`Warning: Unknown escape ${c}`);
                }

                escape = false;
            } else if (c === '%') {
                escape = true;
            } else {
                filename += c;
            }
        });

        if (escape)
            filename += '%';

        return this._getAbsolutePath(filename);
    }

    async ScreencastAsync(params, invocation) {
        if (this._lockdownSettings.get_boolean('disable-save-to-disk')) {
            invocation.return_error_literal(ScreencastErrors,
                ScreencastError.SAVE_TO_DISK_DISABLED,
                'Saving to disk is disabled');
            return;
        }

        const sender = invocation.get_sender();

        if (this._recorders.get(sender)) {
            invocation.return_error_literal(ScreencastErrors,
                ScreencastError.ALREADY_RECORDING,
                'Service is already recording');
            return;
        }

        const [sessionPath] = this._proxy.CreateSessionSync({});

        const [fileTemplate, options] = params;
        const [screenWidth, screenHeight] = this._introspectProxy.ScreenSize;
        const filePathStem = this._generateFilePath(fileTemplate);

        let recorder;

        try {
            recorder = new Recorder(
                sessionPath,
                0, 0,
                screenWidth, screenHeight,
                filePathStem,
                options,
                invocation);
        } catch (error) {
            log(`Failed to create recorder: ${error.message}`);
            invocation.return_error_literal(ScreencastErrors,
                ScreencastError.RECORDER_ERROR,
                error.message);

            return;
        }

        this._addRecorder(sender, recorder);

        try {
            const pathWithExtension = await recorder.startRecording();
            invocation.return_value(GLib.Variant.new('(bs)', [true, pathWithExtension]));
        } catch (error) {
            log(`Failed to start recorder: ${error.message}`);
            this._removeRecorder(sender);
            if (error instanceof GLib.Error) {
                invocation.return_gerror(error);
            } else {
                invocation.return_error_literal(ScreencastErrors,
                    ScreencastError.RECORDER_ERROR,
                    error.message);
            }

            return;
        }

        recorder.connect('error', (r, error) => {
            log(`Fatal error while recording: ${error.message}`);
            this._removeRecorder(sender);
            this._dbusImpl.emit_signal('Error',
                new GLib.Variant('(ss)', [
                    Gio.DBusError.encode_gerror(error),
                    error.message,
                ]));
        });
    }

    async ScreencastAreaAsync(params, invocation) {
        if (this._lockdownSettings.get_boolean('disable-save-to-disk')) {
            invocation.return_error_literal(ScreencastErrors,
                ScreencastError.SAVE_TO_DISK_DISABLED,
                'Saving to disk is disabled');
            return;
        }

        const sender = invocation.get_sender();

        if (this._recorders.get(sender)) {
            invocation.return_error_literal(ScreencastErrors,
                ScreencastError.ALREADY_RECORDING,
                'Service is already recording');
            return;
        }

        const [sessionPath] = this._proxy.CreateSessionSync({});

        const [x, y, width, height, fileTemplate, options] = params;
        const filePathStem = this._generateFilePath(fileTemplate);

        let recorder;

        try {
            recorder = new Recorder(
                sessionPath,
                x, y,
                width, height,
                filePathStem,
                options,
                invocation);
        } catch (error) {
            log(`Failed to create recorder: ${error.message}`);
            invocation.return_error_literal(ScreencastErrors,
                ScreencastError.RECORDER_ERROR,
                error.message);

            return;
        }

        this._addRecorder(sender, recorder);

        try {
            const pathWithExtension = await recorder.startRecording();
            invocation.return_value(GLib.Variant.new('(bs)', [true, pathWithExtension]));
        } catch (error) {
            log(`Failed to start recorder: ${error.message}`);
            this._removeRecorder(sender);
            if (error instanceof GLib.Error) {
                invocation.return_gerror(error);
            } else {
                invocation.return_error_literal(ScreencastErrors,
                    ScreencastError.RECORDER_ERROR,
                    error.message);
            }

            return;
        }

        recorder.connect('error', (r, error) => {
            log(`Fatal error while recording: ${error.message}`);
            this._removeRecorder(sender);
            this._dbusImpl.emit_signal('Error',
                new GLib.Variant('(ss)', [
                    Gio.DBusError.encode_gerror(error),
                    error.message,
                ]));
        });
    }

    async StopScreencastAsync(params, invocation) {
        const sender = invocation.get_sender();

        const recorder = this._recorders.get(sender);
        if (!recorder) {
            invocation.return_value(GLib.Variant.new('(b)', [false]));
            return;
        }

        try {
            await recorder.stopRecording();
        } catch (error) {
            log(`${sender}: Error while stopping recorder: ${error.message}`);
        } finally {
            this._removeRecorder(sender);
            invocation.return_value(GLib.Variant.new('(b)', [true]));
        }
    }
};
(uuay)dbusErrors.js5import GLib from 'gi://GLib';
import Gio from 'gi://Gio';

function camelcase(str) {
    const words = str.toLowerCase().split('_');
    return words.map(w => `${w.at(0).toUpperCase()}${w.substring(1)}`).join('');
}

function decamelcase(str) {
    return str.replace(/(.)([A-Z])/g, '$1-$2');
}

function registerErrorDomain(domain, errorEnum, prefix = 'org.gnome.Shell') {
    const domainName =
        `shell-${decamelcase(domain).toLowerCase()}-error`;
    const quark = GLib.quark_from_string(domainName);

    for (const [name, code] of Object.entries(errorEnum)) {
        Gio.dbus_error_register_error(quark,
            code, `${prefix}.${domain}.Error.${camelcase(name)}`);
    }
    return quark;
}

export const ModalDialogError = {
    UNKNOWN_TYPE: 0,
    GRAB_FAILED: 1,
};
export const ModalDialogErrors =
    registerErrorDomain('ModalDialog', ModalDialogError);

export const NotificationError = {
    INVALID_APP: 0,
};
export const NotificationErrors =
    registerErrorDomain('Notifications', NotificationError, 'org.gtk');

export const ExtensionError = {
    INFO_DOWNLOAD_FAILED: 0,
    DOWNLOAD_FAILED: 1,
    EXTRACT_FAILED: 2,
    ENABLE_FAILED: 3,
    NOT_ALLOWED: 4,
};
export const ExtensionErrors =
    registerErrorDomain('Extensions', ExtensionError);

export const ScreencastError = {
    ALL_PIPELINES_FAILED: 0,
    PIPELINE_ERROR: 1,
    SAVE_TO_DISK_DISABLED: 2,
    ALREADY_RECORDING: 3,
    RECORDER_ERROR: 4,
    SERVICE_CRASH: 5,
    OUT_OF_DISK_SPACE: 6,
};
export const ScreencastErrors =
    registerErrorDomain('Screencast', ScreencastError);
(uuay)

Zerion Mini Shell 1.0