mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-07 19:59:14 -04:00
bb05cbb6c5
- Core user is logged in tty1 while user two is in tty3, you can now seamlessly switch bewteen them New IPC options: - `dms ipc call sessions list` - `dms switch-user [target]` - New Powermenu switch users option
237 lines
8.0 KiB
QML
237 lines
8.0 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Common
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
readonly property var log: Log.scoped("SessionsService")
|
|
|
|
property var sessions: []
|
|
property string currentSessionId: ""
|
|
property string currentSeat: ""
|
|
property bool refreshing: false
|
|
|
|
signal switchFailed(string sessionId, string username, string message)
|
|
signal switchRequested
|
|
|
|
function isCurrent(sessionId) {
|
|
return sessionId === currentSessionId;
|
|
}
|
|
|
|
function findByUsername(username) {
|
|
for (let i = 0; i < sessions.length; i++) {
|
|
const s = sessions[i];
|
|
if (s.username === username && !s.current)
|
|
return s;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function findById(sessionId) {
|
|
for (let i = 0; i < sessions.length; i++) {
|
|
if (sessions[i].sessionId === sessionId)
|
|
return sessions[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function otherSessions() {
|
|
return sessions.filter(s => !s.current);
|
|
}
|
|
|
|
function refresh() {
|
|
if (refreshing)
|
|
return;
|
|
refreshing = true;
|
|
Proc.runCommand("sessionsService-current", ["sh", "-c", "echo \"${XDG_SESSION_ID}:$(loginctl show-session \"${XDG_SESSION_ID}\" -p Seat --value 2>/dev/null)\""], (output, exitCode) => {
|
|
const trimmed = (output || "").trim();
|
|
const parts = trimmed.split(":");
|
|
root.currentSessionId = parts[0] || "";
|
|
root.currentSeat = parts[1] || "";
|
|
_loadSessions();
|
|
}, 0);
|
|
}
|
|
|
|
function _loadSessions() {
|
|
const script = "loginctl list-sessions --no-legend 2>/dev/null | awk '{print $1}' | while read id; do loginctl show-session \"$id\" -p Id -p User -p Name -p Seat -p TTY -p Type -p Class -p Active -p State -p Remote 2>/dev/null | tr '\\n' '|'; echo; done";
|
|
Proc.runCommand("sessionsService-list", ["sh", "-c", script], (output, exitCode) => {
|
|
const lines = (output || "").trim().split("\n").filter(l => l.length > 0);
|
|
const list = [];
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const fields = {};
|
|
const pairs = lines[i].split("|");
|
|
for (let j = 0; j < pairs.length; j++) {
|
|
const eq = pairs[j].indexOf("=");
|
|
if (eq <= 0)
|
|
continue;
|
|
fields[pairs[j].substring(0, eq)] = pairs[j].substring(eq + 1);
|
|
}
|
|
if (!fields.Id)
|
|
continue;
|
|
if (fields.Class !== "user")
|
|
continue;
|
|
if (fields.State === "closing")
|
|
continue;
|
|
const sessionId = fields.Id;
|
|
list.push({
|
|
sessionId: sessionId,
|
|
uid: parseInt(fields.User || "0", 10),
|
|
username: fields.Name || "",
|
|
seat: fields.Seat || "",
|
|
tty: fields.TTY || "",
|
|
type: fields.Type || "",
|
|
sessionClass: fields.Class || "",
|
|
active: fields.Active === "yes",
|
|
state: fields.State || "",
|
|
remote: fields.Remote === "yes",
|
|
current: sessionId === root.currentSessionId
|
|
});
|
|
}
|
|
list.sort((a, b) => {
|
|
if (a.current !== b.current)
|
|
return a.current ? -1 : 1;
|
|
if (a.username !== b.username)
|
|
return a.username.localeCompare(b.username);
|
|
return parseInt(a.sessionId, 10) - parseInt(b.sessionId, 10);
|
|
});
|
|
root.sessions = list;
|
|
root.refreshing = false;
|
|
}, 0);
|
|
}
|
|
|
|
function activate(sessionId, callback) {
|
|
if (!sessionId) {
|
|
_fail("", "", I18n.tr("No session selected"), callback);
|
|
return;
|
|
}
|
|
if (sessionId === root.currentSessionId) {
|
|
_fail(sessionId, "", I18n.tr("Already on that session"), callback);
|
|
return;
|
|
}
|
|
const session = findById(sessionId);
|
|
const username = session ? session.username : "";
|
|
_spawnActivate(sessionId, username, callback);
|
|
}
|
|
|
|
function switchToUser(target, callback) {
|
|
if (!target) {
|
|
_fail("", "", I18n.tr("No user specified"), callback);
|
|
return;
|
|
}
|
|
let session = findById(target);
|
|
if (!session)
|
|
session = findByUsername(target);
|
|
if (!session) {
|
|
_fail("", target, I18n.tr("No active session found for %1").arg(target), callback);
|
|
return;
|
|
}
|
|
if (session.current) {
|
|
_fail(session.sessionId, session.username, I18n.tr("Already on that session"), callback);
|
|
return;
|
|
}
|
|
_spawnActivate(session.sessionId, session.username, callback);
|
|
}
|
|
|
|
function _fail(sessionId, username, message, callback) {
|
|
log.warn("switch failed:", sessionId, username, message);
|
|
root.switchFailed(sessionId, username, message);
|
|
if (typeof callback === "function") {
|
|
try {
|
|
callback(false, message);
|
|
} catch (e) {
|
|
log.warn("SessionsService callback error:", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: activateComp
|
|
Process {
|
|
id: activateProc
|
|
property string targetSession: ""
|
|
property string targetUsername: ""
|
|
property var cb: null
|
|
property string capturedErr: ""
|
|
running: false
|
|
stdout: StdioCollector {}
|
|
stderr: StdioCollector {
|
|
onStreamFinished: activateProc.capturedErr = text || ""
|
|
}
|
|
onExited: exitCode => {
|
|
const svc = root;
|
|
const sessionId = activateProc.targetSession;
|
|
const username = activateProc.targetUsername;
|
|
const cb = activateProc.cb;
|
|
const err = (activateProc.capturedErr || "").trim();
|
|
Qt.callLater(() => activateProc.destroy());
|
|
|
|
if (exitCode !== 0) {
|
|
svc._fail(sessionId, username, err || I18n.tr("loginctl activate failed (exit %1)").arg(exitCode), cb);
|
|
return;
|
|
}
|
|
if (typeof cb === "function") {
|
|
try {
|
|
cb(true, "");
|
|
} catch (e) {
|
|
svc.log.warn("activate cb error:", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _spawnActivate(sessionId, username, callback) {
|
|
const proc = activateComp.createObject(root, {
|
|
command: ["loginctl", "activate", sessionId],
|
|
targetSession: sessionId,
|
|
targetUsername: username,
|
|
cb: callback
|
|
});
|
|
proc.running = true;
|
|
}
|
|
|
|
IpcHandler {
|
|
target: "sessions"
|
|
|
|
function list(): string {
|
|
const lines = [];
|
|
for (let i = 0; i < root.sessions.length; i++) {
|
|
const s = root.sessions[i];
|
|
lines.push([s.sessionId, s.username, s.seat || "-", s.tty || "-", s.type || "-", s.current ? "*current*" : ""].join("\t"));
|
|
}
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function refresh(): string {
|
|
root.refresh();
|
|
return "ok";
|
|
}
|
|
|
|
function open(): string {
|
|
root.refresh();
|
|
root.switchRequested();
|
|
return "ok";
|
|
}
|
|
|
|
function activate(sessionId: string): string {
|
|
if (!sessionId)
|
|
return "ERROR: missing session id";
|
|
root.activate(sessionId, null);
|
|
return "ok";
|
|
}
|
|
|
|
function switchTo(target: string): string {
|
|
if (!target)
|
|
return "ERROR: missing target (username or session id)";
|
|
root.switchToUser(target, null);
|
|
return "ok";
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: refresh()
|
|
}
|