mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-04 04:42:05 -04:00
Add terminal multiplexer launcher (#1687)
* Add tmux
* Add mux modal
* Restore the settings config version
* Revert typo
* Use DankModal for InputModal
* Simplify terminal flags
* use showWithOptions for inputModals instead
* Fix translation
* use Quickshell.env("TERMINAL") to choose terminal
* Fix typo
* Hide muxModal after creating new session
* Add mux check, moved exclusion to service, And use ScriptModel
* Revert unrelated change
* Add blank line
This commit is contained in:
230
quickshell/Services/MuxService.qml
Normal file
230
quickshell/Services/MuxService.qml
Normal file
@@ -0,0 +1,230 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var sessions: []
|
||||
property bool loading: false
|
||||
|
||||
property bool tmuxAvailable: false
|
||||
property bool zellijAvailable: false
|
||||
readonly property bool currentMuxAvailable: muxType === "zellij" ? zellijAvailable : tmuxAvailable
|
||||
|
||||
readonly property string muxType: SettingsData.muxType
|
||||
readonly property string displayName: muxType === "zellij" ? "Zellij" : "Tmux"
|
||||
|
||||
readonly property var terminalFlags: ({
|
||||
"ghostty": ["-e"],
|
||||
"kitty": ["-e"],
|
||||
"alacritty": ["-e"],
|
||||
"foot": [],
|
||||
"wezterm": ["start", "--"],
|
||||
"gnome-terminal": ["--"],
|
||||
"xterm": ["-e"],
|
||||
"konsole": ["-e"],
|
||||
"st": ["-e"],
|
||||
"terminator": ["-e"],
|
||||
"xfce4-terminal": ["-e"]
|
||||
})
|
||||
|
||||
function getTerminalFlag(terminal) {
|
||||
return terminalFlags[terminal] ?? ["-e"]
|
||||
}
|
||||
|
||||
readonly property string terminal: Quickshell.env("TERMINAL") || "ghostty"
|
||||
|
||||
function _terminalPrefix() {
|
||||
return [terminal].concat(getTerminalFlag(terminal))
|
||||
}
|
||||
|
||||
Process {
|
||||
id: tmuxCheckProcess
|
||||
command: ["which", "tmux"]
|
||||
running: false
|
||||
onExited: (code) => { root.tmuxAvailable = (code === 0) }
|
||||
}
|
||||
|
||||
Process {
|
||||
id: zellijCheckProcess
|
||||
command: ["which", "zellij"]
|
||||
running: false
|
||||
onExited: (code) => { root.zellijAvailable = (code === 0) }
|
||||
}
|
||||
|
||||
function checkAvailability() {
|
||||
tmuxCheckProcess.running = true
|
||||
zellijCheckProcess.running = true
|
||||
}
|
||||
|
||||
Component.onCompleted: checkAvailability()
|
||||
|
||||
Process {
|
||||
id: listProcess
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
if (root.muxType === "zellij")
|
||||
root._parseZellijSessions(text)
|
||||
else
|
||||
root._parseTmuxSessions(text)
|
||||
} catch (e) {
|
||||
console.error("[MuxService] Error parsing sessions:", e)
|
||||
root.sessions = []
|
||||
}
|
||||
root.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
stderr: SplitParser {
|
||||
onRead: (line) => {
|
||||
if (line.trim())
|
||||
console.error("[MuxService] stderr:", line)
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (code) => {
|
||||
if (code !== 0 && code !== 1) {
|
||||
console.warn("[MuxService] Process exited with code:", code)
|
||||
root.sessions = []
|
||||
}
|
||||
root.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
function refreshSessions() {
|
||||
if (!root.currentMuxAvailable) {
|
||||
root.sessions = []
|
||||
return
|
||||
}
|
||||
|
||||
root.loading = true
|
||||
|
||||
if (listProcess.running)
|
||||
listProcess.running = false
|
||||
|
||||
if (root.muxType === "zellij")
|
||||
listProcess.command = ["zellij", "list-sessions", "--no-formatting"]
|
||||
else
|
||||
listProcess.command = ["tmux", "list-sessions", "-F", "#{session_name}|#{session_windows}|#{session_attached}"]
|
||||
|
||||
Qt.callLater(function () {
|
||||
listProcess.running = true
|
||||
})
|
||||
}
|
||||
|
||||
function _isSessionExcluded(name) {
|
||||
var filter = SettingsData.muxSessionFilter.trim()
|
||||
if (filter.length === 0)
|
||||
return false
|
||||
var parts = filter.split(",")
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var pattern = parts[i].trim()
|
||||
if (pattern.length === 0)
|
||||
continue
|
||||
if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length > 2) {
|
||||
try {
|
||||
var re = new RegExp(pattern.slice(1, -1))
|
||||
if (re.test(name))
|
||||
return true
|
||||
} catch (e) {}
|
||||
} else {
|
||||
if (name.toLowerCase() === pattern.toLowerCase())
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function _parseTmuxSessions(output) {
|
||||
var sessionList = []
|
||||
var lines = output.trim().split('\n')
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim()
|
||||
if (line.length === 0)
|
||||
continue
|
||||
|
||||
var parts = line.split('|')
|
||||
if (parts.length >= 3 && !_isSessionExcluded(parts[0])) {
|
||||
sessionList.push({
|
||||
name: parts[0],
|
||||
windows: parts[1],
|
||||
attached: parts[2] === "1"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
root.sessions = sessionList
|
||||
}
|
||||
|
||||
function _parseZellijSessions(output) {
|
||||
var sessionList = []
|
||||
var lines = output.trim().split('\n')
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim()
|
||||
if (line.length === 0)
|
||||
continue
|
||||
|
||||
var exited = line.includes("(EXITED")
|
||||
var bracketIdx = line.indexOf(" [")
|
||||
var name = (bracketIdx > 0 ? line.substring(0, bracketIdx) : line).trim()
|
||||
|
||||
if (!_isSessionExcluded(name)) {
|
||||
sessionList.push({
|
||||
name: name,
|
||||
windows: "N/A",
|
||||
attached: !exited
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
root.sessions = sessionList
|
||||
}
|
||||
|
||||
function attachToSession(name) {
|
||||
if (SettingsData.muxUseCustomCommand && SettingsData.muxCustomCommand) {
|
||||
Quickshell.execDetached([SettingsData.muxCustomCommand, name])
|
||||
} else if (root.muxType === "zellij") {
|
||||
Quickshell.execDetached(_terminalPrefix().concat(["zellij", "attach", name]))
|
||||
} else {
|
||||
Quickshell.execDetached(_terminalPrefix().concat(["tmux", "attach", "-t", name]))
|
||||
}
|
||||
}
|
||||
|
||||
function createSession(name) {
|
||||
if (SettingsData.muxUseCustomCommand && SettingsData.muxCustomCommand) {
|
||||
Quickshell.execDetached([SettingsData.muxCustomCommand, name])
|
||||
} else if (root.muxType === "zellij") {
|
||||
Quickshell.execDetached(_terminalPrefix().concat(["zellij", "-s", name]))
|
||||
} else {
|
||||
Quickshell.execDetached(_terminalPrefix().concat(["tmux", "new-session", "-s", name]))
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool supportsRename: muxType !== "zellij"
|
||||
|
||||
function renameSession(oldName, newName) {
|
||||
if (root.muxType === "zellij")
|
||||
return
|
||||
Quickshell.execDetached(["tmux", "rename-session", "-t", oldName, newName])
|
||||
Qt.callLater(refreshSessions)
|
||||
}
|
||||
|
||||
function killSession(name) {
|
||||
if (root.muxType === "zellij") {
|
||||
Quickshell.execDetached(["zellij", "kill-session", name])
|
||||
} else {
|
||||
Quickshell.execDetached(["tmux", "kill-session", "-t", name])
|
||||
}
|
||||
Qt.callLater(refreshSessions)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user