Compare commits

..
7 Commits
6 changed files with 62 additions and 10 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mrterm", "name": "mrterm",
"version": "0.4.0", "version": "0.4.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mrterm", "name": "mrterm",
"version": "0.4.0", "version": "0.4.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mrterm", "name": "mrterm",
"productName": "MrTerm", "productName": "MrTerm",
"version": "0.4.0", "version": "0.4.3",
"description": "Moderner SSH-, SFTP- und RDP-Client", "description": "Moderner SSH-, SFTP- und RDP-Client",
"main": "src/main/main.js", "main": "src/main/main.js",
"author": "MrBlake", "author": "MrBlake",
+3 -1
View File
@@ -24,7 +24,9 @@ if [[ "$version" != "$current" ]]; then
git add package.json package-lock.json git add package.json package-lock.json
git commit -m "Version ${version}" git commit -m "Version ${version}"
fi fi
git push origin HEAD # Push mit dem Gitea-Token (Basic-Auth, Benutzer aus GITEA_USER, Standard MrBlake)
auth="$(printf '%s:%s' "${GITEA_USER:-MrBlake}" "$GITEA_TOKEN" | base64 -w0)"
git -c http.extraHeader="Authorization: Basic ${auth}" push origin HEAD
rm -rf dist rm -rf dist
npm run dist:linux npm run dist:linux
+53 -6
View File
@@ -7,7 +7,53 @@ const os = require('os');
const path = require('path'); const path = require('path');
const i18n = require('../i18n'); const i18n = require('../i18n');
const DEFAULT_REPO = 'https://git.mrblake.cc/MrBlake/MrTerm'; // Startet ein Programm und liefert den Exit-Code ('ENOENT', falls es nicht existiert).
// Electron/Chromium setzt unter Linux PR_SET_NO_NEW_PRIVS, das alle Kindprozesse erben – dann
// funktionieren weder pkexec noch sudo. Deshalb über systemd-run --user starten: Elternprozess ist
// dann der systemd-User-Manager, ohne diesen Schalter.
const GUI_ENV = ['DISPLAY', 'WAYLAND_DISPLAY', 'XAUTHORITY', 'XDG_RUNTIME_DIR', 'XDG_SESSION_TYPE', 'DBUS_SESSION_BUS_ADDRESS'];
function spawnCode(cmd, args) {
return new Promise((resolve) => {
const p = spawn(cmd, args, { stdio: 'ignore' });
p.on('error', (e) => resolve(e.code === 'ENOENT' ? 'ENOENT' : e.message));
p.on('exit', (c) => resolve(c));
});
}
let hasSystemdRun;
async function run(cmd, args) {
if (hasSystemdRun === undefined) hasSystemdRun = (await spawnCode('systemd-run', ['--user', '--quiet', '--wait', '--collect', 'true'])) === 0;
if (!hasSystemdRun) return spawnCode(cmd, args);
const inPath = cmd.includes('/') ? fs.existsSync(cmd) : (process.env.PATH || '').split(':').some((d) => d && fs.existsSync(path.join(d, cmd)));
if (!inPath) return 'ENOENT';
const env = GUI_ENV.filter((k) => process.env[k]).flatMap((k) => ['-E', `${k}=${process.env[k]}`]);
return spawnCode('systemd-run', ['--user', '--quiet', '--wait', '--collect', ...env, '--', cmd, ...args]);
}
// Fallback ohne Polkit-Agent: pacman -U per sudo in einem Terminalfenster; wartet, bis das Fenster geschlossen ist.
// Der Erfolg wird über eine Marker-Datei erkannt, da nicht jedes Terminal den Exit-Code durchreicht.
async function installInTerminal(file) {
const q = (s) => `'${String(s).replace(/'/g, `'\\''`)}'`;
const marker = file + '.ok';
try { fs.unlinkSync(marker); } catch {}
const script = `sudo /usr/bin/pacman -U --noconfirm ${q(file)} && touch ${q(marker)} || { echo; read -rp "[Enter]"; }`;
const terms = [
['konsole', ['--nofork', '-e', 'bash', '-c', script]],
['gnome-terminal', ['--wait', '--', 'bash', '-c', script]],
['kitty', ['bash', '-c', script]],
['alacritty', ['-e', 'bash', '-c', script]],
['foot', ['bash', '-c', script]],
['xfce4-terminal', ['--disable-server', '-x', 'bash', '-c', script]],
['xterm', ['-e', 'bash', '-c', script]],
];
for (const [cmd, args] of terms) {
const c = await run(cmd, args);
if (c === 'ENOENT') continue;
return fs.existsSync(marker) ? 0 : c || 1;
}
return 'ENOENT';
}
const DEFAULT_REPO ='https://git.mrblake.cc/MrBlake/MrTerm';
function parseRepo(url) { function parseRepo(url) {
const u = new URL((url || DEFAULT_REPO).replace(/\.git$/, '').replace(/\/+$/, '')); const u = new URL((url || DEFAULT_REPO).replace(/\.git$/, '').replace(/\/+$/, ''));
@@ -134,11 +180,12 @@ class Updater {
app.relaunch({ execPath: target }); app.relaunch({ execPath: target });
app.exit(0); app.exit(0);
} else if (kind === 'pacman' && /\.(pacman|pkg\.tar\.zst)$/.test(file)) { } else if (kind === 'pacman' && /\.(pacman|pkg\.tar\.zst)$/.test(file)) {
await new Promise((resolve, reject) => { // 1. pkexec (grafische Passwortabfrage über den Polkit-Agent)
const p = spawn('pkexec', ['pacman', '-U', '--noconfirm', file], { stdio: 'ignore' }); let code = await run('pkexec', ['/usr/bin/pacman', '-U', '--noconfirm', file]);
p.on('error', reject); // 126/127 = Abfrage abgebrochen bzw. kein Polkit-Agent → Terminal mit sudo öffnen
p.on('exit', (c) => (c === 0 ? resolve() : reject(new Error(i18n.t('pacman exited with code {code}', { code: c }))))); if (code === 126 || code === 127 || code === 'ENOENT') code = await installInTerminal(file);
}); if (code === 'ENOENT') throw new Error(i18n.t('Automatic installation is not possible here. The file is located at: {file}', { file }));
if (code !== 0) throw new Error(i18n.t('pacman exited with code {code}', { code }));
app.relaunch(); app.relaunch();
app.exit(0); app.exit(0);
} else { } else {
+2
View File
@@ -1018,6 +1018,7 @@ class TerminalSession {
this.term.loadAddon(this.search); this.term.loadAddon(this.search);
this.term.loadAddon(new WebLinksAddon.WebLinksAddon((_e, url) => api.call('shell:open', url))); this.term.loadAddon(new WebLinksAddon.WebLinksAddon((_e, url) => api.call('shell:open', url)));
this.term.open($('.term', this.el)); this.term.open($('.term', this.el));
$('.term', this.el).style.background = termTheme().background; // Innenabstand in Terminalfarbe
this.term.onData((d) => { if (this.alive) api.ssh.write(this.id, d); else if (d === '\r' && this.ended) this.reconnect(); }); this.term.onData((d) => { if (this.alive) api.ssh.write(this.id, d); else if (d === '\r' && this.ended) this.reconnect(); });
this.term.onResize(({ cols, rows }) => { if (this.alive) api.ssh.resize(this.id, cols, rows); }); this.term.onResize(({ cols, rows }) => { if (this.alive) api.ssh.resize(this.id, cols, rows); });
this.term.onSelectionChange(() => { if (settings().copyOnSelect && this.term.hasSelection()) api.call('clipboard:write', this.term.getSelection()); }); this.term.onSelectionChange(() => { if (settings().copyOnSelect && this.term.hasSelection()) api.call('clipboard:write', this.term.getSelection()); });
@@ -1091,6 +1092,7 @@ class TerminalSession {
applySettings() { applySettings() {
const st = settings(); const st = settings();
Object.assign(this.term.options, { fontFamily: st.fontFamily, fontSize: st.fontSize, cursorStyle: st.cursorStyle, cursorBlink: st.cursorBlink, scrollback: st.scrollback, theme: termTheme() }); Object.assign(this.term.options, { fontFamily: st.fontFamily, fontSize: st.fontSize, cursorStyle: st.cursorStyle, cursorBlink: st.cursorBlink, scrollback: st.scrollback, theme: termTheme() });
$('.term', this.el).style.background = termTheme().background;
this.doFit(); this.doFit();
} }
+1
View File
@@ -238,6 +238,7 @@ body.in-session #sidebar { display: none; }
.session .term { flex: 1; min-width: 0; min-height: 0; overflow: hidden; } .session .term { flex: 1; min-width: 0; min-height: 0; overflow: hidden; }
/* Innenabstand am .xterm-Element: FitAddon zieht nur dessen Padding ab (am Elternelement würde es mitgezählt) */ /* Innenabstand am .xterm-Element: FitAddon zieht nur dessen Padding ab (am Elternelement würde es mitgezählt) */
.session .term .xterm { height: 100%; padding: 8px 0 4px 10px; } .session .term .xterm { height: 100%; padding: 8px 0 4px 10px; }
.session .term .xterm-viewport { background-color: transparent !important; } /* sonst schwarzer Rand im Innenabstand */
.session .overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 16px; background: var(--overlay); z-index: 3; } .session .overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 16px; background: var(--overlay); z-index: 3; }
.session .overlay .spinner { width: 36px; height: 36px; border: 3px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.9s linear infinite; } .session .overlay .spinner { width: 36px; height: 36px; border: 3px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.9s linear infinite; }
.session .overlay .msg { color: var(--muted); max-width: 520px; text-align: center; } .session .overlay .msg { color: var(--muted); max-width: 520px; text-align: center; }