You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
6.4 KiB
280 lines
6.4 KiB
/**
|
|
* RootMode module
|
|
*
|
|
* @author Deminder <tremminder@gmail.com>
|
|
* @copyright 2021
|
|
* @license GNU General Public License v3.0
|
|
*/
|
|
/* exported shutdown, shutdownCancel, wake, wakeCancel, installScript, uninstallScript */
|
|
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
|
const logDebug = Me.imports.lib.Convenience.logDebug;
|
|
|
|
const { Gio, GLib } = imports.gi;
|
|
|
|
// translations
|
|
const Gettext = imports.gettext.domain('ShutdownTimer');
|
|
const _ = Gettext.gettext;
|
|
|
|
/**
|
|
*
|
|
* @param stream
|
|
* @param cancellable
|
|
*/
|
|
function readLine(stream, cancellable) {
|
|
return new Promise((resolve, reject) => {
|
|
stream.read_line_async(0, cancellable, (s, res) => {
|
|
try {
|
|
const line = s.read_line_finish_utf8(res)[0];
|
|
|
|
if (line !== null) {
|
|
resolve(line);
|
|
} else {
|
|
reject(new Error('No line was read!'));
|
|
}
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param str
|
|
*/
|
|
function quoteEscape(str) {
|
|
return str.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
|
}
|
|
|
|
/**
|
|
* Execute a command asynchronously and check the exit status.
|
|
*
|
|
* If given, @cancellable can be used to stop the process before it finishes.
|
|
*
|
|
* @param {string[] | string} argv - a list of string arguments or command line that will be parsed
|
|
* @param {Gio.Cancellable} [cancellable] - optional cancellable object
|
|
* @param {boolean} shell - run command as shell command
|
|
* @param logFunc
|
|
* @returns {Promise<void>} - The process success
|
|
*/
|
|
function execCheck(
|
|
argv,
|
|
cancellable = null,
|
|
shell = true,
|
|
logFunc = undefined
|
|
) {
|
|
if (!shell && typeof argv === 'string') {
|
|
argv = GLib.shell_parse_argv(argv)[1];
|
|
}
|
|
|
|
const isRootProc = argv[0] && argv[0].endsWith('pkexec');
|
|
|
|
if (shell && Array.isArray(argv)) {
|
|
argv = argv.map(c => `"${quoteEscape(c)}"`).join(' ');
|
|
}
|
|
let cancelId = 0;
|
|
let proc = new Gio.Subprocess({
|
|
argv: (shell ? ['/bin/sh', '-c'] : []).concat(argv),
|
|
flags:
|
|
logFunc !== undefined
|
|
? Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
|
|
: Gio.SubprocessFlags.NONE,
|
|
});
|
|
proc.init(cancellable);
|
|
|
|
if (cancellable instanceof Gio.Cancellable) {
|
|
cancelId = cancellable.connect(() => {
|
|
if (logFunc !== undefined) {
|
|
if (isRootProc) {
|
|
logFunc(`# ${_('Can not cancel root process!')}`);
|
|
} else {
|
|
logFunc(`[${_('CANCEL')}]`);
|
|
}
|
|
}
|
|
proc.force_exit();
|
|
});
|
|
}
|
|
let stdoutStream = null;
|
|
let stderrStream = null;
|
|
let stdCancel = null;
|
|
|
|
if (logFunc !== undefined) {
|
|
stdoutStream = new Gio.DataInputStream({
|
|
base_stream: proc.get_stdout_pipe(),
|
|
close_base_stream: true,
|
|
});
|
|
|
|
stderrStream = new Gio.DataInputStream({
|
|
base_stream: proc.get_stderr_pipe(),
|
|
close_base_stream: true,
|
|
});
|
|
const readNextLine = async (stream, prefix) => {
|
|
stdCancel = new Gio.Cancellable();
|
|
const line = await readLine(stream, stdCancel);
|
|
logFunc(prefix + line);
|
|
logDebug(line);
|
|
return readNextLine(stream, prefix);
|
|
};
|
|
// read stdout and stderr asynchronously
|
|
readNextLine(stdoutStream, '').catch(() => {});
|
|
readNextLine(stderrStream, '# ').catch(() => {});
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
proc.wait_check_async(null, (p, res) => {
|
|
try {
|
|
const success = p.wait_check_finish(res);
|
|
if (stdCancel !== null) {
|
|
stdCancel.cancel();
|
|
}
|
|
if (!success) {
|
|
let status = p.get_exit_status();
|
|
|
|
throw new Gio.IOErrorEnum({
|
|
code: Gio.io_error_from_errno(status),
|
|
message: GLib.strerror(status),
|
|
});
|
|
}
|
|
|
|
resolve();
|
|
} catch (e) {
|
|
reject(e);
|
|
} finally {
|
|
const maybeCloseStream = stream => {
|
|
if (stream !== null && !stream.is_closed()) {
|
|
stream.close_async(0, null, (s, sRes) => {
|
|
try {
|
|
s.close_finish(sRes);
|
|
} catch (e) {
|
|
logError(e, 'StreamCloseError');
|
|
}
|
|
});
|
|
}
|
|
};
|
|
maybeCloseStream(stdoutStream);
|
|
maybeCloseStream(stderrStream);
|
|
if (cancelId > 0) {
|
|
cancellable.disconnect(cancelId);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function installedScriptPath() {
|
|
for (const name of [
|
|
'shutdowntimerctl',
|
|
`shutdowntimerctl-${GLib.get_user_name()}`,
|
|
]) {
|
|
const standard = GLib.find_program_in_path(name);
|
|
if (standard !== null) {
|
|
return standard;
|
|
}
|
|
for (const bindir of ['/usr/local/bin/', '/usr/bin/']) {
|
|
const path = bindir + name;
|
|
logDebug(`Looking for: ${path}`);
|
|
if (Gio.File.new_for_path(path).query_exists(null)) {
|
|
return path;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param action
|
|
* @param cancellable
|
|
* @param logFunc
|
|
*/
|
|
function _runInstaller(action, cancellable, logFunc) {
|
|
const user = GLib.get_user_name();
|
|
logDebug(`? installer.sh --tool-user ${user} ${action}`);
|
|
return execCheck(
|
|
[
|
|
'pkexec',
|
|
Me.dir.get_child('tool').get_child('installer.sh').get_path(),
|
|
'--tool-user',
|
|
user,
|
|
action,
|
|
],
|
|
cancellable,
|
|
false,
|
|
logFunc
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param cancellable
|
|
* @param logFunc
|
|
*/
|
|
async function installScript(cancellable, logFunc) {
|
|
// install for user if installed in the /home directory
|
|
await _runInstaller('install', cancellable, logFunc);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param cancellable
|
|
* @param logFunc
|
|
*/
|
|
async function uninstallScript(cancellable, logFunc) {
|
|
await _runInstaller('uninstall', cancellable, logFunc);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param args
|
|
* @param noScriptArgs
|
|
*/
|
|
function runWithScript(args, noScriptArgs) {
|
|
const installedScript = installedScriptPath();
|
|
if (installedScript !== null) {
|
|
return execCheck(['pkexec', installedScript].concat(args), null, false);
|
|
}
|
|
if (noScriptArgs === undefined) {
|
|
throw new Error(_('Privileged script installation required!'));
|
|
}
|
|
return execCheck(noScriptArgs, null, false);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param minutes
|
|
* @param reboot
|
|
*/
|
|
function shutdown(minutes, reboot = false) {
|
|
return runWithScript(
|
|
[reboot ? 'reboot' : 'shutdown', `${minutes}`],
|
|
['shutdown', reboot ? '-r' : '-P', `${minutes}`]
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function shutdownCancel() {
|
|
return runWithScript(['shutdown-cancel'], ['shutdown', '-c']);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param minutes
|
|
*/
|
|
function wake(minutes) {
|
|
return runWithScript(['wake', `${minutes}`]);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function wakeCancel() {
|
|
return runWithScript(['wake-cancel']);
|
|
}
|