mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 02:22:06 -04:00
Compare commits
9 Commits
e7c8d208e2
...
f0be36062e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0be36062e | ||
|
|
9578d6daf9 | ||
|
|
cc6766135d | ||
|
|
28c9bb0925 | ||
|
|
7826d827dd | ||
|
|
7f392acc54 | ||
|
|
190fd662ad | ||
|
|
e18587c471 | ||
|
|
ddb079b62d |
@@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property int settingsConfigVersion: 6
|
||||
readonly property int settingsConfigVersion: 5
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
@@ -150,6 +150,7 @@ Singleton {
|
||||
property int mangoLayoutBorderSize: -1
|
||||
|
||||
property int firstDayOfWeek: -1
|
||||
property bool showWeekNumber: false
|
||||
property bool use24HourClock: true
|
||||
property bool showSeconds: false
|
||||
property bool padHours12Hour: false
|
||||
@@ -453,6 +454,11 @@ Singleton {
|
||||
property bool syncModeWithPortal: true
|
||||
property bool terminalsAlwaysDark: false
|
||||
|
||||
property string muxType: "tmux"
|
||||
property bool muxUseCustomCommand: false
|
||||
property string muxCustomCommand: ""
|
||||
property string muxSessionFilter: ""
|
||||
|
||||
property bool runDmsMatugenTemplates: true
|
||||
property bool matugenTemplateGtk: true
|
||||
property bool matugenTemplateNiri: true
|
||||
|
||||
@@ -33,6 +33,7 @@ var SPEC = {
|
||||
mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
|
||||
|
||||
firstDayOfWeek: { def: -1 },
|
||||
showWeekNumber: { def: false },
|
||||
use24HourClock: { def: true },
|
||||
showSeconds: { def: false },
|
||||
padHours12Hour: { def: false },
|
||||
@@ -268,6 +269,11 @@ var SPEC = {
|
||||
syncModeWithPortal: { def: true },
|
||||
terminalsAlwaysDark: { def: false, onChange: "regenSystemThemes" },
|
||||
|
||||
muxType: { def: "tmux" },
|
||||
muxUseCustomCommand: { def: false },
|
||||
muxCustomCommand: { def: "" },
|
||||
muxSessionFilter: { def: "" },
|
||||
|
||||
runDmsMatugenTemplates: { def: true },
|
||||
matugenTemplateGtk: { def: true },
|
||||
matugenTemplateNiri: { def: true },
|
||||
|
||||
@@ -7,6 +7,7 @@ import qs.Modals.Clipboard
|
||||
import qs.Modals.Greeter
|
||||
import qs.Modals.Settings
|
||||
import qs.Modals.DankLauncherV2
|
||||
import qs.Modals
|
||||
import qs.Modules
|
||||
import qs.Modules.AppDrawer
|
||||
import qs.Modules.DankDash
|
||||
@@ -619,6 +620,10 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
MuxModal {
|
||||
id: muxModal
|
||||
}
|
||||
|
||||
ClipboardHistoryModal {
|
||||
id: clipboardHistoryModalPopup
|
||||
|
||||
|
||||
312
quickshell/Modals/Common/InputModal.qml
Normal file
312
quickshell/Modals/Common/InputModal.qml
Normal file
@@ -0,0 +1,312 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
layerNamespace: "dms:input-modal"
|
||||
keepPopoutsOpen: true
|
||||
|
||||
property string inputTitle: ""
|
||||
property string inputMessage: ""
|
||||
property string inputPlaceholder: ""
|
||||
property string inputText: ""
|
||||
property string confirmButtonText: "Confirm"
|
||||
property string cancelButtonText: "Cancel"
|
||||
property color confirmButtonColor: Theme.primary
|
||||
property var onConfirm: function (text) {}
|
||||
property var onCancel: function () {}
|
||||
property int selectedButton: -1
|
||||
property bool keyboardNavigation: false
|
||||
|
||||
function show(title, message, onConfirmCallback, onCancelCallback) {
|
||||
inputTitle = title || "";
|
||||
inputMessage = message || "";
|
||||
inputPlaceholder = "";
|
||||
inputText = "";
|
||||
confirmButtonText = "Confirm";
|
||||
cancelButtonText = "Cancel";
|
||||
confirmButtonColor = Theme.primary;
|
||||
onConfirm = onConfirmCallback || ((text) => {});
|
||||
onCancel = onCancelCallback || (() => {});
|
||||
selectedButton = -1;
|
||||
keyboardNavigation = false;
|
||||
open();
|
||||
}
|
||||
|
||||
function showWithOptions(options) {
|
||||
inputTitle = options.title || "";
|
||||
inputMessage = options.message || "";
|
||||
inputPlaceholder = options.placeholder || "";
|
||||
inputText = options.initialText || "";
|
||||
confirmButtonText = options.confirmText || "Confirm";
|
||||
cancelButtonText = options.cancelText || "Cancel";
|
||||
confirmButtonColor = options.confirmColor || Theme.primary;
|
||||
onConfirm = options.onConfirm || ((text) => {});
|
||||
onCancel = options.onCancel || (() => {});
|
||||
selectedButton = -1;
|
||||
keyboardNavigation = false;
|
||||
open();
|
||||
}
|
||||
|
||||
function confirmAndClose() {
|
||||
const text = inputText;
|
||||
close();
|
||||
if (onConfirm) {
|
||||
onConfirm(text);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAndClose() {
|
||||
close();
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
function selectButton() {
|
||||
if (selectedButton === 0) {
|
||||
cancelAndClose();
|
||||
} else {
|
||||
confirmAndClose();
|
||||
}
|
||||
}
|
||||
|
||||
shouldBeVisible: false
|
||||
allowStacking: true
|
||||
modalWidth: 350
|
||||
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 200
|
||||
enableShadow: true
|
||||
shouldHaveFocus: true
|
||||
onBackgroundClicked: cancelAndClose()
|
||||
onOpened: {
|
||||
Qt.callLater(function () {
|
||||
if (contentLoader.item && contentLoader.item.textInputRef) {
|
||||
contentLoader.item.textInputRef.forceActiveFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
content: Component {
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
implicitHeight: mainColumn.implicitHeight
|
||||
focus: true
|
||||
|
||||
property alias textInputRef: textInput
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
const textFieldFocused = textInput.activeFocus;
|
||||
|
||||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
root.cancelAndClose();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Tab:
|
||||
if (textFieldFocused) {
|
||||
root.keyboardNavigation = true;
|
||||
root.selectedButton = 0;
|
||||
textInput.focus = false;
|
||||
} else {
|
||||
root.keyboardNavigation = true;
|
||||
if (root.selectedButton === -1) {
|
||||
root.selectedButton = 0;
|
||||
} else if (root.selectedButton === 0) {
|
||||
root.selectedButton = 1;
|
||||
} else {
|
||||
root.selectedButton = -1;
|
||||
textInput.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Left:
|
||||
if (!textFieldFocused) {
|
||||
root.keyboardNavigation = true;
|
||||
root.selectedButton = 0;
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_Right:
|
||||
if (!textFieldFocused) {
|
||||
root.keyboardNavigation = true;
|
||||
root.selectedButton = 1;
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
if (root.selectedButton !== -1) {
|
||||
root.selectButton();
|
||||
} else {
|
||||
root.confirmAndClose();
|
||||
}
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
anchors.topMargin: Theme.spacingL
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
text: root.inputTitle
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: Theme.spacingL
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.inputMessage
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
visible: root.inputMessage !== ""
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: root.inputMessage !== "" ? Theme.spacingL : 0
|
||||
visible: root.inputMessage !== ""
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceVariantAlpha
|
||||
border.color: textInput.activeFocus ? Theme.primary : "transparent"
|
||||
border.width: textInput.activeFocus ? 1 : 0
|
||||
|
||||
TextInput {
|
||||
id: textInput
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
selectionColor: Theme.primary
|
||||
selectedTextColor: Theme.primaryText
|
||||
clip: true
|
||||
text: root.inputText
|
||||
onTextChanged: root.inputText = text
|
||||
|
||||
StyledText {
|
||||
anchors.fill: parent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
text: root.inputPlaceholder
|
||||
visible: textInput.text === "" && !textInput.activeFocus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: Theme.spacingL * 1.5
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: 120
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
if (root.keyboardNavigation && root.selectedButton === 0) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
} else if (cancelButton.containsMouse) {
|
||||
return Theme.surfacePressed;
|
||||
} else {
|
||||
return Theme.surfaceVariantAlpha;
|
||||
}
|
||||
}
|
||||
border.color: (root.keyboardNavigation && root.selectedButton === 0) ? Theme.primary : "transparent"
|
||||
border.width: (root.keyboardNavigation && root.selectedButton === 0) ? 1 : 0
|
||||
|
||||
StyledText {
|
||||
text: root.cancelButtonText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cancelButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.cancelAndClose()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 120
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: {
|
||||
const baseColor = root.confirmButtonColor;
|
||||
if (root.keyboardNavigation && root.selectedButton === 1) {
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 1);
|
||||
} else if (confirmButton.containsMouse) {
|
||||
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9);
|
||||
} else {
|
||||
return baseColor;
|
||||
}
|
||||
}
|
||||
border.color: (root.keyboardNavigation && root.selectedButton === 1) ? "white" : "transparent"
|
||||
border.width: (root.keyboardNavigation && root.selectedButton === 1) ? 1 : 0
|
||||
|
||||
StyledText {
|
||||
text: root.confirmButtonText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.primaryText
|
||||
font.weight: Font.Medium
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: confirmButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.confirmAndClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: Theme.spacingL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,9 +207,12 @@ Rectangle {
|
||||
selectedActionIndex = 0;
|
||||
}
|
||||
|
||||
function cycleAction() {
|
||||
function cycleAction(reverse = false) {
|
||||
if (actions.length > 0) {
|
||||
selectedActionIndex = (selectedActionIndex + 1) % actions.length;
|
||||
if (! reverse)
|
||||
selectedActionIndex = (selectedActionIndex + 1) % actions.length;
|
||||
else
|
||||
selectedActionIndex = (selectedActionIndex - 1) % actions.length;
|
||||
ensureSelectedVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,10 +353,13 @@ Item {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
function cycleMode() {
|
||||
function cycleMode(reverse = false) {
|
||||
var modes = ["all", "apps", "files", "plugins"];
|
||||
var currentIndex = modes.indexOf(searchMode);
|
||||
var nextIndex = (currentIndex + 1) % modes.length;
|
||||
if (!reverse)
|
||||
var nextIndex = (currentIndex + 1) % modes.length;
|
||||
else
|
||||
var nextIndex = (currentIndex - 1 + modes.length) % modes.length;
|
||||
setMode(modes[nextIndex]);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,10 @@ FocusScope {
|
||||
controller.selectPageUp(8);
|
||||
return;
|
||||
case Qt.Key_Right:
|
||||
if (hasCtrl) {
|
||||
controller.cycleMode();
|
||||
return;
|
||||
}
|
||||
if (controller.getCurrentSectionViewMode() !== "list") {
|
||||
controller.selectRight();
|
||||
return;
|
||||
@@ -165,12 +169,25 @@ FocusScope {
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Left:
|
||||
if (hasCtrl) {
|
||||
const reverse = true;
|
||||
controller.cycleMode(reverse);
|
||||
return;
|
||||
}
|
||||
if (controller.getCurrentSectionViewMode() !== "list") {
|
||||
controller.selectLeft();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_H:
|
||||
if (hasCtrl) {
|
||||
const reverse = true;
|
||||
controller.cycleMode(reverse);
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_J:
|
||||
if (hasCtrl) {
|
||||
controller.selectNext();
|
||||
@@ -185,6 +202,13 @@ FocusScope {
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_L:
|
||||
if (hasCtrl) {
|
||||
controller.cycleMode();
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_N:
|
||||
if (hasCtrl) {
|
||||
controller.selectNextSection();
|
||||
@@ -200,13 +224,19 @@ FocusScope {
|
||||
event.accepted = false;
|
||||
return;
|
||||
case Qt.Key_Tab:
|
||||
if (actionPanel.hasActions) {
|
||||
if (hasCtrl && actionPanel.hasActions) {
|
||||
actionPanel.expanded ? actionPanel.cycleAction() : actionPanel.show();
|
||||
return;
|
||||
}
|
||||
controller.selectNext();
|
||||
return;
|
||||
case Qt.Key_Backtab:
|
||||
if (actionPanel.expanded)
|
||||
actionPanel.hide();
|
||||
if (hasCtrl && actionPanel.expanded) {
|
||||
const reverse = true
|
||||
actionPanel.expanded ? actionPanel.cycleAction(reverse) : actionPanel.show();
|
||||
return;
|
||||
}
|
||||
controller.selectPrevious();
|
||||
return;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
@@ -388,7 +418,7 @@ FocusScope {
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Tab " + I18n.tr("actions")
|
||||
text: "Ctrl-Tab " + I18n.tr("actions")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
visible: actionPanel.hasActions
|
||||
|
||||
621
quickshell/Modals/MuxModal.qml
Normal file
621
quickshell/Modals/MuxModal.qml
Normal file
@@ -0,0 +1,621 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: muxModal
|
||||
|
||||
layerNamespace: "dms:mux"
|
||||
|
||||
property int selectedIndex: -1
|
||||
property string searchText: ""
|
||||
property var filteredSessions: []
|
||||
|
||||
function updateFilteredSessions() {
|
||||
var filtered = []
|
||||
var lowerSearch = searchText.trim().toLowerCase()
|
||||
for (var i = 0; i < MuxService.sessions.length; i++) {
|
||||
var session = MuxService.sessions[i]
|
||||
if (lowerSearch.length > 0 && !session.name.toLowerCase().includes(lowerSearch))
|
||||
continue
|
||||
filtered.push(session)
|
||||
}
|
||||
filteredSessions = filtered
|
||||
|
||||
if (selectedIndex >= filteredSessions.length) {
|
||||
selectedIndex = Math.max(0, filteredSessions.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
onSearchTextChanged: updateFilteredSessions()
|
||||
|
||||
Connections {
|
||||
target: MuxService
|
||||
function onSessionsChanged() {
|
||||
updateFilteredSessions()
|
||||
}
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: grab
|
||||
windows: [muxModal.contentWindow]
|
||||
active: CompositorService.isHyprland && muxModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (shouldBeVisible) {
|
||||
hide()
|
||||
} else {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
open()
|
||||
selectedIndex = -1
|
||||
searchText = ""
|
||||
MuxService.refreshSessions()
|
||||
shouldHaveFocus = true
|
||||
|
||||
Qt.callLater(() => {
|
||||
if (muxPanel && muxPanel.searchField) {
|
||||
muxPanel.searchField.forceActiveFocus();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function hide() {
|
||||
close()
|
||||
selectedIndex = -1
|
||||
searchText = ""
|
||||
}
|
||||
|
||||
function attachToSession(name) {
|
||||
MuxService.attachToSession(name)
|
||||
hide()
|
||||
}
|
||||
|
||||
function renameSession(name) {
|
||||
inputModal.showWithOptions({
|
||||
title: I18n.tr("Rename Session"),
|
||||
message: I18n.tr("Enter a new name for session \"%1\"").arg(name),
|
||||
initialText: name,
|
||||
onConfirm: function (newName) {
|
||||
MuxService.renameSession(name, newName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function killSession(name) {
|
||||
confirmModal.showWithOptions({
|
||||
title: I18n.tr("Kill Session"),
|
||||
message: I18n.tr("Are you sure you want to kill session \"%1\"?").arg(name),
|
||||
confirmText: I18n.tr("Kill"),
|
||||
confirmColor: Theme.primary,
|
||||
onConfirm: function () {
|
||||
MuxService.killSession(name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createNewSession() {
|
||||
inputModal.showWithOptions({
|
||||
title: I18n.tr("New Session"),
|
||||
message: I18n.tr("Please write a name for your new %1 session").arg(MuxService.displayName),
|
||||
onConfirm: function (name) {
|
||||
MuxService.createSession(name)
|
||||
hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredSessions.length - 1)
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
selectedIndex = Math.max(selectedIndex - 1, -1)
|
||||
}
|
||||
|
||||
function activateSelected() {
|
||||
if (selectedIndex === -1) {
|
||||
createNewSession()
|
||||
} else if (selectedIndex >= 0 && selectedIndex < filteredSessions.length) {
|
||||
attachToSession(filteredSessions[selectedIndex].name)
|
||||
}
|
||||
}
|
||||
|
||||
visible: false
|
||||
modalWidth: 600
|
||||
modalHeight: 600
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
cornerRadius: Theme.cornerRadius
|
||||
borderColor: Theme.outlineMedium
|
||||
borderWidth: 1
|
||||
enableShadow: true
|
||||
keepContentLoaded: true
|
||||
|
||||
onBackgroundClicked: hide()
|
||||
|
||||
Timer {
|
||||
interval: 3000
|
||||
running: muxModal.shouldBeVisible
|
||||
repeat: true
|
||||
onTriggered: MuxService.refreshSessions()
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
muxModal.show()
|
||||
return "MUX_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
muxModal.hide()
|
||||
return "MUX_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
muxModal.toggle()
|
||||
return "MUX_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "mux"
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
muxModal.show()
|
||||
return "TMUX_OPEN_SUCCESS"
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
muxModal.hide()
|
||||
return "TMUX_CLOSE_SUCCESS"
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
muxModal.toggle()
|
||||
return "TMUX_TOGGLE_SUCCESS"
|
||||
}
|
||||
|
||||
target: "tmux"
|
||||
}
|
||||
|
||||
InputModal {
|
||||
id: inputModal
|
||||
onShouldBeVisibleChanged: {
|
||||
if (shouldBeVisible) {
|
||||
muxModal.shouldHaveFocus = false;
|
||||
muxModal.contentWindow.visible = false;
|
||||
return;
|
||||
}
|
||||
if (muxModal.shouldBeVisible) {
|
||||
muxModal.contentWindow.visible = true;
|
||||
}
|
||||
Qt.callLater(function () {
|
||||
if (!muxModal.shouldBeVisible) {
|
||||
return;
|
||||
}
|
||||
muxModal.shouldHaveFocus = true;
|
||||
muxModal.modalFocusScope.forceActiveFocus();
|
||||
if (muxPanel.searchField) {
|
||||
muxPanel.searchField.forceActiveFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmModal {
|
||||
id: confirmModal
|
||||
onShouldBeVisibleChanged: {
|
||||
if (shouldBeVisible) {
|
||||
muxModal.shouldHaveFocus = false;
|
||||
muxModal.contentWindow.visible = false;
|
||||
return;
|
||||
}
|
||||
if (muxModal.shouldBeVisible) {
|
||||
muxModal.contentWindow.visible = true;
|
||||
}
|
||||
Qt.callLater(function () {
|
||||
if (!muxModal.shouldBeVisible) {
|
||||
return;
|
||||
}
|
||||
muxModal.shouldHaveFocus = true;
|
||||
muxModal.modalFocusScope.forceActiveFocus();
|
||||
if (muxPanel.searchField) {
|
||||
muxPanel.searchField.forceActiveFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
directContent: Item {
|
||||
id: muxPanel
|
||||
|
||||
clip: false
|
||||
|
||||
property alias searchField: searchField
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if ((event.key === Qt.Key_J && (event.modifiers & Qt.ControlModifier)) ||
|
||||
(event.key === Qt.Key_Down)) {
|
||||
selectNext()
|
||||
event.accepted = true
|
||||
} else if ((event.key === Qt.Key_K && (event.modifiers & Qt.ControlModifier)) ||
|
||||
(event.key === Qt.Key_Up)) {
|
||||
selectPrevious()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_N && (event.modifiers & Qt.ControlModifier)) {
|
||||
createNewSession()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_R && (event.modifiers & Qt.ControlModifier)) {
|
||||
if (MuxService.supportsRename && selectedIndex >= 0 && selectedIndex < filteredSessions.length) {
|
||||
renameSession(filteredSessions[selectedIndex].name)
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_D && (event.modifiers & Qt.ControlModifier)) {
|
||||
if (selectedIndex >= 0 && selectedIndex < filteredSessions.length) {
|
||||
killSession(filteredSessions[selectedIndex].name)
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
hide()
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
activateSelected()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
height: parent.height - Theme.spacingM * 2
|
||||
x: Theme.spacingM
|
||||
y: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
// Header
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("%1 Sessions").arg(MuxService.displayName)
|
||||
font.pixelSize: Theme.fontSizeLarge + 4
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("%1 active, %2 filtered").arg(MuxService.sessions.length).arg(muxModal.filteredSessions.length)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
// Search field
|
||||
DankTextField {
|
||||
id: searchField
|
||||
|
||||
width: parent.width
|
||||
height: 48
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: Theme.surfaceContainerHigh
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: "search"
|
||||
leftIconSize: Theme.iconSize
|
||||
leftIconColor: Theme.surfaceVariantText
|
||||
leftIconFocusedColor: Theme.primary
|
||||
showClearButton: true
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
placeholderText: I18n.tr("Search sessions...")
|
||||
keyForwardTargets: [muxPanel]
|
||||
|
||||
onTextEdited: {
|
||||
muxModal.searchText = text
|
||||
muxModal.selectedIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
// New Session Button
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 56
|
||||
radius: Theme.cornerRadius
|
||||
color: muxModal.selectedIndex === -1 ? Theme.primaryContainer :
|
||||
(newMouse.containsMouse ? Theme.surfaceContainerHigh : Theme.surfaceContainer)
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
radius: 20
|
||||
color: Theme.primaryContainer
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "add"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("New Session")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Create a new %1 session (n)").arg(MuxService.displayName)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: newMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: muxModal.createNewSession()
|
||||
}
|
||||
}
|
||||
|
||||
// Sessions List
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height - 88 - 48 - shortcutsBar.height - Theme.spacingS * 3
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: muxModal.filteredSessions
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 64
|
||||
radius: Theme.cornerRadius
|
||||
color: muxModal.selectedIndex === index ? Theme.primaryContainer :
|
||||
(sessionMouse.containsMouse ? Theme.surfaceContainerHigh : "transparent")
|
||||
|
||||
MouseArea {
|
||||
id: sessionMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: muxModal.attachToSession(modelData.name)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Avatar
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
radius: 20
|
||||
color: modelData.attached ? Theme.primaryContainer : Theme.surfaceContainerHigh
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.name.charAt(0).toUpperCase()
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: modelData.attached ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
// Info
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
var parts = []
|
||||
if (modelData.windows !== "N/A")
|
||||
parts.push(I18n.tr("%1 windows").arg(modelData.windows))
|
||||
parts.push(modelData.attached ? I18n.tr("attached") : I18n.tr("detached"))
|
||||
return parts.join(" \u2022 ")
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
// Rename button (tmux only)
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 36
|
||||
Layout.preferredHeight: 36
|
||||
radius: 18
|
||||
visible: MuxService.supportsRename
|
||||
color: renameMouse.containsMouse ? Theme.surfaceContainerHighest : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "edit"
|
||||
size: Theme.iconSizeSmall
|
||||
color: renameMouse.containsMouse ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: renameMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: muxModal.renameSession(modelData.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete button
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 36
|
||||
Layout.preferredHeight: 36
|
||||
radius: 18
|
||||
color: deleteMouse.containsMouse ? Theme.errorContainer : "transparent"
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "delete"
|
||||
size: Theme.iconSizeSmall
|
||||
color: deleteMouse.containsMouse ? Theme.error : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deleteMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
muxModal.killSession(modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state
|
||||
Item {
|
||||
width: parent.width
|
||||
height: muxModal.filteredSessions.length === 0 ? 200 : 0
|
||||
visible: muxModal.filteredSessions.length === 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: muxModal.searchText.length > 0 ? "search_off" : "terminal"
|
||||
size: 48
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: muxModal.searchText.length > 0 ? I18n.tr("No sessions found") : I18n.tr("No active %1 sessions").arg(MuxService.displayName)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: muxModal.searchText.length > 0 ? I18n.tr("Try a different search") : I18n.tr("Press 'n' or click 'New Session' to create one")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shortcuts bar
|
||||
Row {
|
||||
id: shortcutsBar
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
bottomPadding: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
var shortcuts = [
|
||||
{ key: "↑↓", label: I18n.tr("Navigate") },
|
||||
{ key: "↵", label: I18n.tr("Attach") },
|
||||
{ key: "^N", label: I18n.tr("New") },
|
||||
{ key: "^D", label: I18n.tr("Kill") },
|
||||
{ key: "Esc", label: I18n.tr("Close") }
|
||||
]
|
||||
if (MuxService.supportsRename)
|
||||
shortcuts.splice(3, 0, { key: "^R", label: I18n.tr("Rename") })
|
||||
return shortcuts
|
||||
}
|
||||
|
||||
delegate: Row {
|
||||
required property var modelData
|
||||
spacing: 4
|
||||
|
||||
Rectangle {
|
||||
width: keyText.width + Theme.spacingS
|
||||
height: keyText.height + 4
|
||||
radius: 4
|
||||
color: Theme.surfaceContainerHighest
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
id: keyText
|
||||
anchors.centerIn: parent
|
||||
text: modelData.key
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,5 +503,20 @@ FocusScope {
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: muxLoader
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 30
|
||||
visible: active
|
||||
focus: active
|
||||
|
||||
sourceComponent: MuxTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +266,12 @@ Rectangle {
|
||||
"tabIndex": 8,
|
||||
"cupsOnly": true
|
||||
},
|
||||
{
|
||||
"id": "multiplexers",
|
||||
"text": I18n.tr("Multiplexers"),
|
||||
"icon": "terminal",
|
||||
"tabIndex": 30
|
||||
},
|
||||
{
|
||||
"id": "window_rules",
|
||||
"text": I18n.tr("Window Rules"),
|
||||
|
||||
@@ -70,6 +70,16 @@ DankPopout {
|
||||
|
||||
backgroundInteractive: !anyModalOpen
|
||||
|
||||
onCredentialsPromptOpenChanged: {
|
||||
if (credentialsPromptOpen && shouldBeVisible)
|
||||
close();
|
||||
}
|
||||
|
||||
onPolkitModalOpenChanged: {
|
||||
if (polkitModalOpen && shouldBeVisible)
|
||||
close();
|
||||
}
|
||||
|
||||
customKeyboardFocus: {
|
||||
if (!shouldBeVisible)
|
||||
return WlrKeyboardFocus.None;
|
||||
|
||||
@@ -289,7 +289,7 @@ PanelWindow {
|
||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||
if (!onThisScreen)
|
||||
return false;
|
||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
||||
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
@@ -312,7 +312,7 @@ PanelWindow {
|
||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||
if (!onThisScreen)
|
||||
return false;
|
||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
||||
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
@@ -336,7 +336,7 @@ PanelWindow {
|
||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||
if (!onThisScreen)
|
||||
return false;
|
||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
||||
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
@@ -360,7 +360,7 @@ PanelWindow {
|
||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||
if (!onThisScreen)
|
||||
return false;
|
||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
||||
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
@@ -686,6 +686,7 @@ PanelWindow {
|
||||
onHasActivePopoutChanged: evaluateReveal()
|
||||
|
||||
function updateActivePopoutState() {
|
||||
if (!barWindow.screen) return;
|
||||
const screenName = barWindow.screen.name;
|
||||
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
|
||||
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
@@ -38,12 +39,20 @@ BasePill {
|
||||
property var _vAudio: null
|
||||
property var _vBrightness: null
|
||||
property var _vMic: null
|
||||
property var _interactionDelegates: []
|
||||
readonly property var defaultControlCenterGroupOrder: ["network", "vpn", "bluetooth", "audio", "microphone", "brightness", "battery", "printer", "screenSharing"]
|
||||
readonly property var effectiveControlCenterGroupOrder: getEffectiveControlCenterGroupOrder()
|
||||
readonly property var controlCenterRenderModel: getControlCenterRenderModel()
|
||||
|
||||
onIsVerticalOrientationChanged: root.clearInteractionRefs()
|
||||
|
||||
onWheel: function (wheelEvent) {
|
||||
const delta = wheelEvent.angleDelta.y;
|
||||
if (delta === 0)
|
||||
return;
|
||||
|
||||
root.refreshInteractionRefs();
|
||||
|
||||
const rootX = wheelEvent.x - root.leftMargin;
|
||||
const rootY = wheelEvent.y - root.topMargin;
|
||||
|
||||
@@ -72,6 +81,8 @@ BasePill {
|
||||
}
|
||||
|
||||
onRightClicked: function (rootX, rootY) {
|
||||
root.refreshInteractionRefs();
|
||||
|
||||
if (root.isVerticalOrientation && _vCol) {
|
||||
const pos = root.mapToItem(_vCol, rootX, rootY);
|
||||
if (_vAudio?.visible && pos.y >= _vAudio.y && pos.y < _vAudio.y + _vAudio.height) {
|
||||
@@ -279,26 +290,142 @@ BasePill {
|
||||
return CupsService.getTotalJobsNum() > 0;
|
||||
}
|
||||
|
||||
function getControlCenterIconSize() {
|
||||
return Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale);
|
||||
}
|
||||
|
||||
function getEffectiveControlCenterGroupOrder() {
|
||||
const knownIds = root.defaultControlCenterGroupOrder;
|
||||
const savedOrder = root.widgetData?.controlCenterGroupOrder;
|
||||
const result = [];
|
||||
const seen = {};
|
||||
|
||||
if (savedOrder && typeof savedOrder.length === "number") {
|
||||
for (let i = 0; i < savedOrder.length; ++i) {
|
||||
const groupId = savedOrder[i];
|
||||
if (knownIds.indexOf(groupId) === -1 || seen[groupId])
|
||||
continue;
|
||||
|
||||
seen[groupId] = true;
|
||||
result.push(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < knownIds.length; ++i) {
|
||||
const groupId = knownIds[i];
|
||||
if (seen[groupId])
|
||||
continue;
|
||||
|
||||
seen[groupId] = true;
|
||||
result.push(groupId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function isGroupVisible(groupId) {
|
||||
switch (groupId) {
|
||||
case "screenSharing":
|
||||
return root.showScreenSharingIcon && NiriService.hasCasts;
|
||||
case "network":
|
||||
return root.showNetworkIcon && NetworkService.networkAvailable;
|
||||
case "vpn":
|
||||
return root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected;
|
||||
case "bluetooth":
|
||||
return root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled;
|
||||
case "audio":
|
||||
return root.showAudioIcon;
|
||||
case "microphone":
|
||||
return root.showMicIcon;
|
||||
case "brightness":
|
||||
return root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice();
|
||||
case "battery":
|
||||
return root.showBatteryIcon && BatteryService.batteryAvailable;
|
||||
case "printer":
|
||||
return root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isCompositeGroup(groupId) {
|
||||
return groupId === "audio" || groupId === "microphone" || groupId === "brightness";
|
||||
}
|
||||
|
||||
function getControlCenterRenderModel() {
|
||||
return root.effectiveControlCenterGroupOrder.map(groupId => ({
|
||||
"id": groupId,
|
||||
"visible": root.isGroupVisible(groupId),
|
||||
"composite": root.isCompositeGroup(groupId)
|
||||
}));
|
||||
}
|
||||
|
||||
function clearInteractionRefs() {
|
||||
root._hAudio = null;
|
||||
root._hBrightness = null;
|
||||
root._hMic = null;
|
||||
root._vAudio = null;
|
||||
root._vBrightness = null;
|
||||
root._vMic = null;
|
||||
}
|
||||
|
||||
function registerInteractionDelegate(isVertical, item) {
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < root._interactionDelegates.length; ++i) {
|
||||
const entry = root._interactionDelegates[i];
|
||||
if (entry && entry.item === item) {
|
||||
entry.isVertical = isVertical;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
root._interactionDelegates = root._interactionDelegates.concat([
|
||||
{
|
||||
"isVertical": isVertical,
|
||||
"item": item
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
function unregisterInteractionDelegate(item) {
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
root._interactionDelegates = root._interactionDelegates.filter(entry => entry && entry.item !== item);
|
||||
}
|
||||
|
||||
function refreshInteractionRefs() {
|
||||
root.clearInteractionRefs();
|
||||
|
||||
for (let i = 0; i < root._interactionDelegates.length; ++i) {
|
||||
const entry = root._interactionDelegates[i];
|
||||
const item = entry?.item;
|
||||
if (!item || !item.visible)
|
||||
continue;
|
||||
|
||||
const groupId = item.interactionGroupId;
|
||||
if (entry.isVertical) {
|
||||
if (groupId === "audio")
|
||||
root._vAudio = item;
|
||||
else if (groupId === "microphone")
|
||||
root._vMic = item;
|
||||
else if (groupId === "brightness")
|
||||
root._vBrightness = item;
|
||||
} else {
|
||||
if (groupId === "audio")
|
||||
root._hAudio = item;
|
||||
else if (groupId === "microphone")
|
||||
root._hMic = item;
|
||||
else if (groupId === "brightness")
|
||||
root._hBrightness = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasNoVisibleIcons() {
|
||||
if (root.showScreenSharingIcon && NiriService.hasCasts)
|
||||
return false;
|
||||
if (root.showNetworkIcon && NetworkService.networkAvailable)
|
||||
return false;
|
||||
if (root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected)
|
||||
return false;
|
||||
if (root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled)
|
||||
return false;
|
||||
if (root.showAudioIcon)
|
||||
return false;
|
||||
if (root.showMicIcon)
|
||||
return false;
|
||||
if (root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice())
|
||||
return false;
|
||||
if (root.showBatteryIcon && BatteryService.batteryAvailable)
|
||||
return false;
|
||||
if (root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs())
|
||||
return false;
|
||||
return true;
|
||||
return !root.controlCenterRenderModel.some(entry => entry.visible);
|
||||
}
|
||||
|
||||
content: Component {
|
||||
@@ -309,12 +436,7 @@ BasePill {
|
||||
Component.onCompleted: {
|
||||
root._hRow = controlIndicators;
|
||||
root._vCol = controlColumn;
|
||||
root._hAudio = audioIcon.parent;
|
||||
root._hBrightness = brightnessIcon.parent;
|
||||
root._hMic = micIcon.parent;
|
||||
root._vAudio = audioIconV.parent;
|
||||
root._vBrightness = brightnessIconV.parent;
|
||||
root._vMic = micIconV.parent;
|
||||
root.clearInteractionRefs();
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -324,162 +446,151 @@ BasePill {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showScreenSharingIcon && NiriService.hasCasts
|
||||
Repeater {
|
||||
model: root.controlCenterRenderModel
|
||||
Item {
|
||||
id: verticalGroupItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
property string interactionGroupId: modelData.id
|
||||
|
||||
DankIcon {
|
||||
name: "screen_record"
|
||||
size: root.vIconSize
|
||||
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
width: parent.width
|
||||
height: {
|
||||
switch (modelData.id) {
|
||||
case "audio":
|
||||
return root.vIconSize + (audioPercentV.visible ? audioPercentV.implicitHeight + 2 : 0);
|
||||
case "microphone":
|
||||
return root.vIconSize + (micPercentV.visible ? micPercentV.implicitHeight + 2 : 0);
|
||||
case "brightness":
|
||||
return root.vIconSize + (brightnessPercentV.visible ? brightnessPercentV.implicitHeight + 2 : 0);
|
||||
default:
|
||||
return root.vIconSize;
|
||||
}
|
||||
}
|
||||
visible: modelData.visible
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
Component.onCompleted: {
|
||||
root.registerInteractionDelegate(true, verticalGroupItem);
|
||||
root.refreshInteractionRefs();
|
||||
}
|
||||
Component.onDestruction: {
|
||||
if (root) {
|
||||
root.unregisterInteractionDelegate(verticalGroupItem);
|
||||
root.refreshInteractionRefs();
|
||||
}
|
||||
}
|
||||
onVisibleChanged: root.refreshInteractionRefs()
|
||||
onInteractionGroupIdChanged: {
|
||||
root.refreshInteractionRefs();
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: root.getNetworkIconName()
|
||||
size: root.vIconSize
|
||||
color: root.getNetworkIconColor()
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !verticalGroupItem.modelData.composite
|
||||
name: {
|
||||
switch (verticalGroupItem.modelData.id) {
|
||||
case "screenSharing":
|
||||
return "screen_record";
|
||||
case "network":
|
||||
return root.getNetworkIconName();
|
||||
case "vpn":
|
||||
return "vpn_lock";
|
||||
case "bluetooth":
|
||||
return "bluetooth";
|
||||
case "battery":
|
||||
return Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable);
|
||||
case "printer":
|
||||
return "print";
|
||||
default:
|
||||
return "settings";
|
||||
}
|
||||
}
|
||||
size: root.vIconSize
|
||||
color: {
|
||||
switch (verticalGroupItem.modelData.id) {
|
||||
case "screenSharing":
|
||||
return NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText;
|
||||
case "network":
|
||||
return root.getNetworkIconColor();
|
||||
case "vpn":
|
||||
return NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText;
|
||||
case "bluetooth":
|
||||
return BluetoothService.connected ? Theme.primary : Theme.surfaceText;
|
||||
case "battery":
|
||||
return root.getBatteryIconColor();
|
||||
case "printer":
|
||||
return Theme.primary;
|
||||
default:
|
||||
return Theme.widgetIconColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||
DankIcon {
|
||||
id: audioIconV
|
||||
visible: verticalGroupItem.modelData.id === "audio"
|
||||
name: root.getVolumeIconName()
|
||||
size: root.vIconSize
|
||||
color: Theme.widgetIconColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "vpn_lock"
|
||||
size: root.vIconSize
|
||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
NumericText {
|
||||
id: audioPercentV
|
||||
visible: verticalGroupItem.modelData.id === "audio" && root.showAudioPercent && isFinite(AudioService.sink?.audio?.volume)
|
||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: audioIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
DankIcon {
|
||||
id: micIconV
|
||||
visible: verticalGroupItem.modelData.id === "microphone"
|
||||
name: root.getMicIconName()
|
||||
size: root.vIconSize
|
||||
color: root.getMicIconColor()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: root.vIconSize
|
||||
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
NumericText {
|
||||
id: micPercentV
|
||||
visible: verticalGroupItem.modelData.id === "microphone" && root.showMicPercent && isFinite(AudioService.source?.audio?.volume)
|
||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: micIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize + (root.showAudioPercent ? audioPercentV.implicitHeight + 2 : 0)
|
||||
visible: root.showAudioIcon
|
||||
DankIcon {
|
||||
id: brightnessIconV
|
||||
visible: verticalGroupItem.modelData.id === "brightness"
|
||||
name: root.getBrightnessIconName()
|
||||
size: root.vIconSize
|
||||
color: Theme.widgetIconColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: audioIconV
|
||||
name: root.getVolumeIconName()
|
||||
size: root.vIconSize
|
||||
color: Theme.widgetIconColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: audioPercentV
|
||||
visible: root.showAudioPercent
|
||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: audioIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize + (root.showMicPercent ? micPercentV.implicitHeight + 2 : 0)
|
||||
visible: root.showMicIcon
|
||||
|
||||
DankIcon {
|
||||
id: micIconV
|
||||
name: root.getMicIconName()
|
||||
size: root.vIconSize
|
||||
color: root.getMicIconColor()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: micPercentV
|
||||
visible: root.showMicPercent
|
||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: micIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize + (root.showBrightnessPercent ? brightnessPercentV.implicitHeight + 2 : 0)
|
||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
||||
|
||||
DankIcon {
|
||||
id: brightnessIconV
|
||||
name: root.getBrightnessIconName()
|
||||
size: root.vIconSize
|
||||
color: Theme.widgetIconColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: brightnessPercentV
|
||||
visible: root.showBrightnessPercent
|
||||
text: Math.round(getBrightness() * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: brightnessIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||
|
||||
DankIcon {
|
||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
||||
size: root.vIconSize
|
||||
color: root.getBatteryIconColor()
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: root.vIconSize
|
||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||
|
||||
DankIcon {
|
||||
name: "print"
|
||||
size: root.vIconSize
|
||||
color: Theme.primary
|
||||
anchors.centerIn: parent
|
||||
NumericText {
|
||||
id: brightnessPercentV
|
||||
visible: verticalGroupItem.modelData.id === "brightness" && root.showBrightnessPercent && isFinite(getBrightness())
|
||||
text: Math.round(getBrightness() * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: brightnessIconV.bottom
|
||||
anchors.topMargin: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,157 +614,206 @@ BasePill {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "screen_record"
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showScreenSharingIcon && NiriService.hasCasts
|
||||
}
|
||||
Repeater {
|
||||
model: root.controlCenterRenderModel
|
||||
|
||||
DankIcon {
|
||||
id: networkIcon
|
||||
name: root.getNetworkIconName()
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: root.getNetworkIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
}
|
||||
Item {
|
||||
id: horizontalGroupItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
property string interactionGroupId: modelData.id
|
||||
|
||||
DankIcon {
|
||||
id: vpnIcon
|
||||
name: "vpn_lock"
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||
}
|
||||
width: {
|
||||
switch (modelData.id) {
|
||||
case "audio":
|
||||
return audioGroup.width;
|
||||
case "microphone":
|
||||
return micGroup.width;
|
||||
case "brightness":
|
||||
return brightnessGroup.width;
|
||||
default:
|
||||
return root.getControlCenterIconSize();
|
||||
}
|
||||
}
|
||||
implicitWidth: width
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
visible: modelData.visible
|
||||
|
||||
DankIcon {
|
||||
id: bluetoothIcon
|
||||
name: "bluetooth"
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||
}
|
||||
Component.onCompleted: {
|
||||
root.registerInteractionDelegate(false, horizontalGroupItem);
|
||||
root.refreshInteractionRefs();
|
||||
}
|
||||
Component.onDestruction: {
|
||||
if (root) {
|
||||
root.unregisterInteractionDelegate(horizontalGroupItem);
|
||||
root.refreshInteractionRefs();
|
||||
}
|
||||
}
|
||||
onVisibleChanged: root.refreshInteractionRefs()
|
||||
onInteractionGroupIdChanged: {
|
||||
root.refreshInteractionRefs();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.reservedWidth : 0) + 4
|
||||
implicitWidth: width
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showAudioIcon
|
||||
DankIcon {
|
||||
id: iconOnlyItem
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
visible: !horizontalGroupItem.modelData.composite
|
||||
name: {
|
||||
switch (horizontalGroupItem.modelData.id) {
|
||||
case "screenSharing":
|
||||
return "screen_record";
|
||||
case "network":
|
||||
return root.getNetworkIconName();
|
||||
case "vpn":
|
||||
return "vpn_lock";
|
||||
case "bluetooth":
|
||||
return "bluetooth";
|
||||
case "battery":
|
||||
return Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable);
|
||||
case "printer":
|
||||
return "print";
|
||||
default:
|
||||
return "settings";
|
||||
}
|
||||
}
|
||||
size: root.getControlCenterIconSize()
|
||||
color: {
|
||||
switch (horizontalGroupItem.modelData.id) {
|
||||
case "screenSharing":
|
||||
return NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText;
|
||||
case "network":
|
||||
return root.getNetworkIconColor();
|
||||
case "vpn":
|
||||
return NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText;
|
||||
case "bluetooth":
|
||||
return BluetoothService.connected ? Theme.primary : Theme.surfaceText;
|
||||
case "battery":
|
||||
return root.getBatteryIconColor();
|
||||
case "printer":
|
||||
return Theme.primary;
|
||||
default:
|
||||
return Theme.widgetIconColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: audioIcon
|
||||
name: root.getVolumeIconName()
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
Rectangle {
|
||||
id: audioGroup
|
||||
width: audioContent.implicitWidth + 2
|
||||
implicitWidth: width
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: horizontalGroupItem.modelData.id === "audio"
|
||||
|
||||
Row {
|
||||
id: audioContent
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
DankIcon {
|
||||
id: audioIcon
|
||||
name: root.getVolumeIconName()
|
||||
size: root.getControlCenterIconSize()
|
||||
color: Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: audioPercent
|
||||
visible: root.showAudioPercent && isFinite(AudioService.sink?.audio?.volume)
|
||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: visible ? implicitWidth : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: micGroup
|
||||
width: micContent.implicitWidth + 2
|
||||
implicitWidth: width
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: horizontalGroupItem.modelData.id === "microphone"
|
||||
|
||||
Row {
|
||||
id: micContent
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
DankIcon {
|
||||
id: micIcon
|
||||
name: root.getMicIconName()
|
||||
size: root.getControlCenterIconSize()
|
||||
color: root.getMicIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: micPercent
|
||||
visible: root.showMicPercent && isFinite(AudioService.source?.audio?.volume)
|
||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: visible ? implicitWidth : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: brightnessGroup
|
||||
width: brightnessContent.implicitWidth + 2
|
||||
implicitWidth: width
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: horizontalGroupItem.modelData.id === "brightness"
|
||||
|
||||
Row {
|
||||
id: brightnessContent
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
DankIcon {
|
||||
id: brightnessIcon
|
||||
name: root.getBrightnessIconName()
|
||||
size: root.getControlCenterIconSize()
|
||||
color: Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: brightnessPercent
|
||||
visible: root.showBrightnessPercent && isFinite(getBrightness())
|
||||
text: Math.round(getBrightness() * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: visible ? implicitWidth : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: audioPercent
|
||||
visible: root.showAudioPercent
|
||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: audioIcon.right
|
||||
anchors.leftMargin: 2
|
||||
width: reservedWidth
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.reservedWidth : 0) + 4
|
||||
implicitWidth: width
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showMicIcon
|
||||
|
||||
DankIcon {
|
||||
id: micIcon
|
||||
name: root.getMicIconName()
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: root.getMicIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: micPercent
|
||||
visible: root.showMicPercent
|
||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: micIcon.right
|
||||
anchors.leftMargin: 2
|
||||
width: reservedWidth
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.reservedWidth : 0) + 4
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
||||
|
||||
DankIcon {
|
||||
id: brightnessIcon
|
||||
name: root.getBrightnessIconName()
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
}
|
||||
|
||||
NumericText {
|
||||
id: brightnessPercent
|
||||
visible: root.showBrightnessPercent
|
||||
text: Math.round(getBrightness() * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: brightnessIcon.right
|
||||
anchors.leftMargin: 2
|
||||
width: reservedWidth
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: batteryIcon
|
||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: root.getBatteryIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: printerIcon
|
||||
name: "print"
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "settings"
|
||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
size: root.getControlCenterIconSize()
|
||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasNoVisibleIcons()
|
||||
|
||||
@@ -87,11 +87,11 @@ BasePill {
|
||||
}
|
||||
|
||||
const workspaceWindows = NiriService.windows.filter(w => w.workspace_id === currentWorkspaceId);
|
||||
return workspaceWindows.length > 0 && activeWindow && activeWindow.title;
|
||||
return workspaceWindows.length > 0 && activeWindow && (activeWindow.title || activeWindow.appId);
|
||||
}
|
||||
|
||||
if (CompositorService.isHyprland) {
|
||||
if (!Hyprland.focusedWorkspace || !activeWindow || !activeWindow.title) {
|
||||
if (!Hyprland.focusedWorkspace || !activeWindow || !(activeWindow.title || activeWindow.appId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
return activeWindow && activeWindow.title;
|
||||
return activeWindow && (activeWindow.title || activeWindow.appId);
|
||||
}
|
||||
|
||||
width: hasWindowsOnCurrentWorkspace ? (isVerticalOrientation ? barThickness : visualWidth) : 0
|
||||
@@ -212,17 +212,19 @@ BasePill {
|
||||
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
||||
const appName = appText.text;
|
||||
|
||||
if (compactMode && title === appName) {
|
||||
if (compactMode) {
|
||||
if (!title || title === appName)
|
||||
return title || appName;
|
||||
if (title.endsWith(appName))
|
||||
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "") || appName;
|
||||
return title;
|
||||
}
|
||||
|
||||
if (!title || !appName) {
|
||||
if (!title || !appName)
|
||||
return title;
|
||||
}
|
||||
|
||||
if (title.endsWith(appName)) {
|
||||
if (title.endsWith(appName))
|
||||
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "");
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ DankPopout {
|
||||
property var triggerScreen: null
|
||||
property int currentTabIndex: 0
|
||||
|
||||
popupWidth: 700
|
||||
popupWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
||||
triggerWidth: 80
|
||||
screen: triggerScreen
|
||||
@@ -168,6 +168,7 @@ DankPopout {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
implicitWidth: Math.max(700, pages.implicitWidth + (Theme.spacingM * 2))
|
||||
implicitHeight: contentColumn.height + Theme.spacingM * 2
|
||||
color: "transparent"
|
||||
focus: true
|
||||
@@ -316,6 +317,7 @@ DankPopout {
|
||||
id: pages
|
||||
width: parent.width
|
||||
height: implicitHeight
|
||||
implicitWidth: currentItem && currentItem.implicitWidth > 0 ? currentItem.implicitWidth : (700 - Theme.spacingM * 2)
|
||||
implicitHeight: {
|
||||
if (root.currentTabIndex === 0)
|
||||
return overviewLoader.item?.implicitHeight ?? 410;
|
||||
|
||||
@@ -105,7 +105,7 @@ Item {
|
||||
return Math.max(0, Math.min(1, calculatedRatio));
|
||||
}
|
||||
|
||||
implicitWidth: 700
|
||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||
implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -7,6 +7,8 @@ import qs.Widgets
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||
|
||||
property bool showEventDetails: false
|
||||
property date selectedDate: systemClock.date
|
||||
property var selectedDateEvents: []
|
||||
@@ -41,6 +43,40 @@ Rectangle {
|
||||
return d;
|
||||
}
|
||||
|
||||
function getWeekNumber(dateObj) {
|
||||
// Set time to noon to avoid potential Daylight Saving Time related bugs
|
||||
const weekStartDay = startOfWeek(dateObj);
|
||||
weekStartDay.setHours(12, 0, 0, 0);
|
||||
|
||||
let week1Start;
|
||||
|
||||
if (weekStartJs() === 1) {
|
||||
// ISO 8601 Standard, week start on Monday
|
||||
// A week belongs to the year its Thursday falls in
|
||||
// So we have to get the yearTarget from weekStartDay instead of dateObj
|
||||
let yearTarget = weekStartDay;
|
||||
yearTarget.setDate(yearTarget.getDate() + 3); // Monday + 3 = Thursday
|
||||
|
||||
// Week 1 is the week containing Jan 4th
|
||||
const jan4 = new Date(yearTarget.getFullYear(), 0, 4);
|
||||
week1Start = startOfWeek(jan4);
|
||||
} else {
|
||||
// Traditional / US Standard, week start on Sunday
|
||||
// A week belongs to the year its Sunday falls in
|
||||
let yearTarget = weekStartDay;
|
||||
yearTarget.setDate(yearTarget.getDate() + 6); // Monday + 6 = Sunday
|
||||
|
||||
// Week 1 is the week containing Jan 1st
|
||||
const jan1 = new Date(yearTarget.getFullYear(), 0, 1);
|
||||
week1Start = startOfWeek(jan1);
|
||||
}
|
||||
|
||||
week1Start.setHours(12, 0, 0, 0);
|
||||
|
||||
const diffDays = Math.round((weekStartDay.getTime() - week1Start.getTime()) / 86400000); // Number of miliseconds in a day
|
||||
return Math.floor(diffDays / 7) + 1;
|
||||
}
|
||||
|
||||
function updateSelectedDateEvents() {
|
||||
if (CalendarService && CalendarService.khalAvailable) {
|
||||
const events = CalendarService.getEventsForDate(selectedDate);
|
||||
@@ -151,6 +187,7 @@ Rectangle {
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 28
|
||||
@@ -224,120 +261,172 @@ Rectangle {
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 18
|
||||
height: parent.height - 28 - Theme.spacingS
|
||||
visible: !showEventDetails
|
||||
spacing: SettingsData.showWeekNumber ? Theme.spacingS : 0
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
const days = [];
|
||||
const qtFirst = weekStartQt();
|
||||
for (let i = 0; i < 7; ++i) {
|
||||
const qtDay = ((qtFirst - 1 + i) % 7) + 1;
|
||||
days.push(I18n.locale().dayName(qtDay, Locale.ShortFormat));
|
||||
}
|
||||
return days;
|
||||
}
|
||||
Column {
|
||||
id: weekNumberColumn
|
||||
visible: SettingsData.showWeekNumber
|
||||
width: SettingsData.showWeekNumber ? 28 : 0
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
width: parent.width / 7
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 18
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
font.weight: Font.Medium
|
||||
Grid {
|
||||
width: parent.width
|
||||
height: parent.height - 18 - Theme.spacingS
|
||||
columns: 1
|
||||
rows: 6
|
||||
|
||||
Repeater {
|
||||
model: 6
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height / 6
|
||||
color: "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
const rowDate = new Date(calendarGrid.firstDay);
|
||||
rowDate.setDate(rowDate.getDate() + index * 7);
|
||||
return root.getWeekNumber(rowDate);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: calendarGrid
|
||||
visible: !showEventDetails
|
||||
Column {
|
||||
width: SettingsData.showWeekNumber ? (parent.width - weekNumberColumn.width - parent.spacing) : parent.width
|
||||
height: parent.height
|
||||
spacing: Theme.spacingS
|
||||
|
||||
property date displayDate: systemClock.date
|
||||
property date selectedDate: systemClock.date
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 18
|
||||
|
||||
readonly property date firstDay: {
|
||||
const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
|
||||
return startOfWeek(firstOfMonth);
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - 28 - 18 - Theme.spacingS * 2
|
||||
columns: 7
|
||||
rows: 6
|
||||
|
||||
Repeater {
|
||||
model: 42
|
||||
|
||||
Rectangle {
|
||||
readonly property date dayDate: {
|
||||
const date = new Date(parent.firstDay);
|
||||
date.setDate(date.getDate() + index);
|
||||
return date;
|
||||
}
|
||||
readonly property bool isCurrentMonth: dayDate.getMonth() === calendarGrid.displayDate.getMonth()
|
||||
readonly property bool isToday: dayDate.toDateString() === new Date().toDateString()
|
||||
readonly property bool isSelected: dayDate.toDateString() === calendarGrid.selectedDate.toDateString()
|
||||
|
||||
width: parent.width / 7
|
||||
height: parent.height / 6
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width - 4, parent.height - 4, 32)
|
||||
height: width
|
||||
color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: dayDate.getDate()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
font.weight: isToday ? Font.Medium : Font.Normal
|
||||
Repeater {
|
||||
model: {
|
||||
const days = [];
|
||||
const qtFirst = weekStartQt();
|
||||
for (let i = 0; i < 7; ++i) {
|
||||
const qtDay = ((qtFirst - 1 + i) % 7) + 1;
|
||||
days.push(I18n.locale().dayName(qtDay, Locale.ShortFormat));
|
||||
}
|
||||
return days;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: 4
|
||||
width: 12
|
||||
height: 2
|
||||
radius: Theme.cornerRadius
|
||||
visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)
|
||||
color: isToday ? Qt.lighter(Theme.primary, 1.3) : Theme.primary
|
||||
opacity: isToday ? 0.9 : 0.7
|
||||
width: parent.width / 7
|
||||
height: 18
|
||||
color: "transparent"
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: calendarGrid
|
||||
width: parent.width
|
||||
height: parent.height - 18 - Theme.spacingS
|
||||
columns: 7
|
||||
rows: 6
|
||||
|
||||
property date displayDate: systemClock.date
|
||||
property date selectedDate: systemClock.date
|
||||
|
||||
readonly property date firstDay: {
|
||||
const firstOfMonth = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1);
|
||||
return startOfWeek(firstOfMonth);
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 42
|
||||
|
||||
Rectangle {
|
||||
readonly property date dayDate: {
|
||||
const date = new Date(parent.firstDay);
|
||||
date.setDate(date.getDate() + index);
|
||||
return date;
|
||||
}
|
||||
readonly property bool isCurrentMonth: dayDate.getMonth() === calendarGrid.displayDate.getMonth()
|
||||
readonly property bool isToday: dayDate.toDateString() === new Date().toDateString()
|
||||
readonly property bool isSelected: dayDate.toDateString() === calendarGrid.selectedDate.toDateString()
|
||||
|
||||
width: parent.width / 7
|
||||
height: parent.height / 6
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width - 4, parent.height - 4, 32)
|
||||
height: width
|
||||
color: isToday ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : dayArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: dayDate.getDate()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isToday ? Theme.primary : isCurrentMonth ? Theme.surfaceText : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
|
||||
font.weight: isToday ? Font.Medium : Font.Normal
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: 4
|
||||
width: 12
|
||||
height: 2
|
||||
radius: Theme.cornerRadius
|
||||
visible: CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)
|
||||
color: isToday ? Qt.lighter(Theme.primary, 1.3) : Theme.primary
|
||||
opacity: isToday ? 0.9 : 0.7
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dayArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)) {
|
||||
root.selectedDate = dayDate;
|
||||
root.showEventDetails = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dayArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (CalendarService && CalendarService.khalAvailable && CalendarService.hasEventsForDate(dayDate)) {
|
||||
root.selectedDate = dayDate;
|
||||
root.showEventDetails = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: parent.height - (showEventDetails ? 40 : 28 + 18) - Theme.spacingS
|
||||
|
||||
@@ -8,7 +8,7 @@ Item {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
implicitWidth: 700
|
||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||
implicitHeight: 410
|
||||
|
||||
signal switchToWeatherTab
|
||||
|
||||
@@ -12,7 +12,7 @@ Item {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
implicitWidth: 700
|
||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||
implicitHeight: 410
|
||||
|
||||
property string wallpaperDir: ""
|
||||
|
||||
@@ -11,7 +11,7 @@ Item {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
implicitWidth: 700
|
||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||
implicitHeight: 410
|
||||
property bool syncing: false
|
||||
property bool showHourly: false
|
||||
|
||||
113
quickshell/Modules/Settings/MuxTab.qml
Normal file
113
quickshell/Modules/Settings/MuxTab.qml
Normal file
@@ -0,0 +1,113 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Settings.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property var muxTypeOptions: [
|
||||
"tmux",
|
||||
"zellij"
|
||||
]
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: mainColumn.height + Theme.spacingXL
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
topPadding: 4
|
||||
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
SettingsCard {
|
||||
tab: "mux"
|
||||
tags: ["mux", "multiplexer", "tmux", "zellij", "type"]
|
||||
title: I18n.tr("Multiplexer")
|
||||
iconName: "terminal"
|
||||
|
||||
SettingsDropdownRow {
|
||||
tab: "mux"
|
||||
tags: ["mux", "multiplexer", "tmux", "zellij", "type", "backend"]
|
||||
settingKey: "muxType"
|
||||
text: I18n.tr("Multiplexer Type")
|
||||
description: I18n.tr("Terminal multiplexer backend to use")
|
||||
options: root.muxTypeOptions
|
||||
currentValue: SettingsData.muxType
|
||||
onValueChanged: value => SettingsData.set("muxType", value)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
tab: "mux"
|
||||
tags: ["mux", "terminal", "custom", "command", "script"]
|
||||
title: I18n.tr("Terminal")
|
||||
iconName: "desktop_windows"
|
||||
|
||||
SettingsToggleRow {
|
||||
tab: "mux"
|
||||
tags: ["mux", "custom", "command", "override"]
|
||||
settingKey: "muxUseCustomCommand"
|
||||
text: I18n.tr("Use Custom Command")
|
||||
description: I18n.tr("Override terminal with a custom command or script")
|
||||
checked: SettingsData.muxUseCustomCommand
|
||||
onToggled: checked => SettingsData.set("muxUseCustomCommand", checked)
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.spacingS
|
||||
visible: SettingsData.muxUseCustomCommand
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("The custom command used when attaching to sessions (receives the session name as the first argument)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: parent.width
|
||||
text: SettingsData.muxCustomCommand
|
||||
placeholderText: I18n.tr("Enter command or script path")
|
||||
onTextEdited: SettingsData.set("muxCustomCommand", text)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
tab: "mux"
|
||||
tags: ["mux", "session", "filter", "exclude", "hide"]
|
||||
title: I18n.tr("Session Filter")
|
||||
iconName: "filter_list"
|
||||
|
||||
Column {
|
||||
width: parent?.width ?? 0
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Comma-separated list of session names to hide. Wrap in slashes for regex (e.g., /^_.*/).")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
width: parent.width
|
||||
text: SettingsData.muxSessionFilter
|
||||
placeholderText: I18n.tr("e.g., scratch, /^tmp_.*/, build")
|
||||
onTextEdited: SettingsData.set("muxSessionFilter", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,16 @@ Item {
|
||||
settingKey: "dateFormat"
|
||||
iconName: "calendar_today"
|
||||
|
||||
SettingsToggleRow {
|
||||
tab: "time"
|
||||
tags: ["show", "week"]
|
||||
settingKey: "showWeekNumber"
|
||||
text: I18n.tr("Show Week Number")
|
||||
description: I18n.tr("Show week number in the calendar")
|
||||
checked: SettingsData.showWeekNumber
|
||||
onToggled: checked => SettingsData.set("showWeekNumber", checked)
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
tab: "time"
|
||||
tags: ["first", "day", "week"]
|
||||
|
||||
@@ -391,6 +391,7 @@ Item {
|
||||
widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon;
|
||||
widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon;
|
||||
widgetObj.showScreenSharingIcon = SettingsData.controlCenterShowScreenSharingIcon;
|
||||
widgetObj.controlCenterGroupOrder = ["network", "vpn", "bluetooth", "audio", "microphone", "brightness", "battery", "printer", "screenSharing"];
|
||||
}
|
||||
if (widgetId === "runningApps") {
|
||||
widgetObj.runningAppsCompactMode = SettingsData.runningAppsCompactMode;
|
||||
@@ -429,7 +430,7 @@ Item {
|
||||
"id": widget.id,
|
||||
"enabled": widget.enabled
|
||||
};
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (widget[keys[i]] !== undefined)
|
||||
result[keys[i]] = widget[keys[i]];
|
||||
@@ -498,6 +499,32 @@ Item {
|
||||
return;
|
||||
var newWidget = cloneWidgetData(widgets[widgetIndex]);
|
||||
newWidget[settingName] = value;
|
||||
|
||||
if (!value) {
|
||||
switch (settingName) {
|
||||
case "showAudioIcon":
|
||||
newWidget.showAudioPercent = false;
|
||||
break;
|
||||
case "showMicIcon":
|
||||
newWidget.showMicPercent = false;
|
||||
break;
|
||||
case "showBrightnessIcon":
|
||||
newWidget.showBrightnessPercent = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
widgets[widgetIndex] = newWidget;
|
||||
setWidgetsForSection(sectionId, widgets);
|
||||
}
|
||||
|
||||
function handleControlCenterGroupOrderChanged(sectionId, widgetIndex, groupOrder) {
|
||||
var widgets = getWidgetsForSection(sectionId).slice();
|
||||
if (widgetIndex < 0 || widgetIndex >= widgets.length)
|
||||
return;
|
||||
var previousWidget = widgets[widgetIndex];
|
||||
var newWidget = cloneWidgetData(previousWidget);
|
||||
newWidget.controlCenterGroupOrder = groupOrder.slice();
|
||||
widgets[widgetIndex] = newWidget;
|
||||
setWidgetsForSection(sectionId, widgets);
|
||||
}
|
||||
@@ -655,6 +682,8 @@ Item {
|
||||
item.showPrinterIcon = widget.showPrinterIcon;
|
||||
if (widget.showScreenSharingIcon !== undefined)
|
||||
item.showScreenSharingIcon = widget.showScreenSharingIcon;
|
||||
if (widget.controlCenterGroupOrder !== undefined)
|
||||
item.controlCenterGroupOrder = widget.controlCenterGroupOrder;
|
||||
if (widget.minimumWidth !== undefined)
|
||||
item.minimumWidth = widget.minimumWidth;
|
||||
if (widget.showSwap !== undefined)
|
||||
@@ -948,6 +977,9 @@ Item {
|
||||
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
||||
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
||||
}
|
||||
onControlCenterGroupOrderChanged: (sectionId, index, groupOrder) => {
|
||||
widgetsTab.handleControlCenterGroupOrderChanged(sectionId, index, groupOrder);
|
||||
}
|
||||
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
||||
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
||||
}
|
||||
@@ -1012,6 +1044,9 @@ Item {
|
||||
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
||||
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
||||
}
|
||||
onControlCenterGroupOrderChanged: (sectionId, index, groupOrder) => {
|
||||
widgetsTab.handleControlCenterGroupOrderChanged(sectionId, index, groupOrder);
|
||||
}
|
||||
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
||||
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
||||
}
|
||||
@@ -1076,6 +1111,9 @@ Item {
|
||||
onControlCenterSettingChanged: (sectionId, index, setting, value) => {
|
||||
widgetsTab.handleControlCenterSettingChanged(sectionId, index, setting, value);
|
||||
}
|
||||
onControlCenterGroupOrderChanged: (sectionId, index, groupOrder) => {
|
||||
widgetsTab.handleControlCenterGroupOrderChanged(sectionId, index, groupOrder);
|
||||
}
|
||||
onPrivacySettingChanged: (sectionId, index, setting, value) => {
|
||||
widgetsTab.handlePrivacySettingChanged(sectionId, index, setting, value);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ Column {
|
||||
signal gpuSelectionChanged(string sectionId, int widgetIndex, int selectedIndex)
|
||||
signal diskMountSelectionChanged(string sectionId, int widgetIndex, string mountPath)
|
||||
signal controlCenterSettingChanged(string sectionId, int widgetIndex, string settingName, bool value)
|
||||
signal controlCenterGroupOrderChanged(string sectionId, int widgetIndex, var groupOrder)
|
||||
signal privacySettingChanged(string sectionId, int widgetIndex, string settingName, bool value)
|
||||
signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled)
|
||||
signal showSwapChanged(string sectionId, int widgetIndex, bool enabled)
|
||||
@@ -39,7 +40,7 @@ Column {
|
||||
"id": widget.id,
|
||||
"enabled": widget.enabled
|
||||
};
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
||||
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "diskUsageMode", "minimumWidth", "showSwap", "showInGb", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "runningAppsGroupByApp", "runningAppsCurrentWorkspace", "runningAppsCurrentMonitor", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon", "controlCenterGroupOrder", "barMaxVisibleApps", "barMaxVisibleRunningApps", "barShowOverflowBadge"];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (widget[keys[i]] !== undefined)
|
||||
result[keys[i]] = widget[keys[i]];
|
||||
@@ -90,7 +91,6 @@ Column {
|
||||
height: 70
|
||||
z: held ? 2 : 1
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: itemBackground
|
||||
|
||||
@@ -587,6 +587,7 @@ Column {
|
||||
controlCenterContextMenu.widgetData = modelData;
|
||||
controlCenterContextMenu.sectionId = root.sectionId;
|
||||
controlCenterContextMenu.widgetIndex = index;
|
||||
controlCenterContextMenu.controlCenterGroups = controlCenterContextMenu.getOrderedControlCenterGroups();
|
||||
|
||||
var buttonPos = ccMenuButton.mapToItem(root, 0, 0);
|
||||
var popupWidth = controlCenterContextMenu.width;
|
||||
@@ -1054,13 +1055,236 @@ Column {
|
||||
property string sectionId: ""
|
||||
property int widgetIndex: -1
|
||||
|
||||
width: 220
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
readonly property real minimumContentWidth: controlCenterContentMetrics.implicitWidth + Theme.spacingS * 2
|
||||
readonly property real controlCenterRowHeight: 32
|
||||
readonly property real controlCenterRowSpacing: 1
|
||||
readonly property real controlCenterGroupVerticalPadding: Theme.spacingXS * 2
|
||||
readonly property real controlCenterMenuSpacing: 2
|
||||
width: Math.max(220, minimumContentWidth)
|
||||
height: getControlCenterPopupHeight(controlCenterGroups)
|
||||
padding: 0
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
onClosed: {
|
||||
cancelControlCenterDrag();
|
||||
}
|
||||
|
||||
readonly property var defaultControlCenterGroups: [
|
||||
{
|
||||
id: "network",
|
||||
rows: [
|
||||
{
|
||||
icon: "lan",
|
||||
label: I18n.tr("Network"),
|
||||
setting: "showNetworkIcon"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "vpn",
|
||||
rows: [
|
||||
{
|
||||
icon: "vpn_lock",
|
||||
label: I18n.tr("VPN"),
|
||||
setting: "showVpnIcon"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "bluetooth",
|
||||
rows: [
|
||||
{
|
||||
icon: "bluetooth",
|
||||
label: I18n.tr("Bluetooth"),
|
||||
setting: "showBluetoothIcon"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "audio",
|
||||
rows: [
|
||||
{
|
||||
icon: "volume_up",
|
||||
label: I18n.tr("Audio"),
|
||||
setting: "showAudioIcon"
|
||||
},
|
||||
{
|
||||
icon: "percent",
|
||||
label: I18n.tr("Volume"),
|
||||
setting: "showAudioPercent"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "microphone",
|
||||
rows: [
|
||||
{
|
||||
icon: "mic",
|
||||
label: I18n.tr("Microphone"),
|
||||
setting: "showMicIcon"
|
||||
},
|
||||
{
|
||||
icon: "percent",
|
||||
label: I18n.tr("Microphone Volume"),
|
||||
setting: "showMicPercent"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "brightness",
|
||||
rows: [
|
||||
{
|
||||
icon: "brightness_high",
|
||||
label: I18n.tr("Brightness"),
|
||||
setting: "showBrightnessIcon"
|
||||
},
|
||||
{
|
||||
icon: "percent",
|
||||
label: I18n.tr("Brightness Value"),
|
||||
setting: "showBrightnessPercent"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "battery",
|
||||
rows: [
|
||||
{
|
||||
icon: "battery_full",
|
||||
label: I18n.tr("Battery"),
|
||||
setting: "showBatteryIcon"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "printer",
|
||||
rows: [
|
||||
{
|
||||
icon: "print",
|
||||
label: I18n.tr("Printer"),
|
||||
setting: "showPrinterIcon"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "screenSharing",
|
||||
rows: [
|
||||
{
|
||||
icon: "screen_record",
|
||||
label: I18n.tr("Screen Sharing"),
|
||||
setting: "showScreenSharingIcon"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
property var controlCenterGroups: defaultControlCenterGroups
|
||||
property int draggedControlCenterGroupIndex: -1
|
||||
property int controlCenterGroupDropIndex: -1
|
||||
|
||||
function updateControlCenterGroupDropIndex(draggedIndex, localY) {
|
||||
const totalGroups = controlCenterGroups.length;
|
||||
let dropIndex = totalGroups;
|
||||
|
||||
for (let i = 0; i < totalGroups; i++) {
|
||||
const delegate = groupRepeater.itemAt(i);
|
||||
if (!delegate)
|
||||
continue;
|
||||
|
||||
const midpoint = delegate.y + delegate.height / 2;
|
||||
if (localY < midpoint) {
|
||||
dropIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
controlCenterGroupDropIndex = Math.max(0, Math.min(totalGroups, dropIndex));
|
||||
draggedControlCenterGroupIndex = draggedIndex;
|
||||
}
|
||||
|
||||
function finishControlCenterDrag() {
|
||||
if (draggedControlCenterGroupIndex < 0) {
|
||||
controlCenterGroupDropIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
const fromIndex = draggedControlCenterGroupIndex;
|
||||
let toIndex = controlCenterGroupDropIndex;
|
||||
|
||||
draggedControlCenterGroupIndex = -1;
|
||||
controlCenterGroupDropIndex = -1;
|
||||
|
||||
if (toIndex < 0 || toIndex > controlCenterGroups.length || toIndex === fromIndex || toIndex === fromIndex + 1)
|
||||
return;
|
||||
|
||||
const groups = controlCenterGroups.slice();
|
||||
const moved = groups.splice(fromIndex, 1)[0];
|
||||
|
||||
if (toIndex > fromIndex)
|
||||
toIndex -= 1;
|
||||
|
||||
groups.splice(toIndex, 0, moved);
|
||||
controlCenterGroups = groups;
|
||||
const reorderedGroupIds = groups.map(group => group.id);
|
||||
root.controlCenterGroupOrderChanged(sectionId, widgetIndex, reorderedGroupIds);
|
||||
}
|
||||
|
||||
function cancelControlCenterDrag() {
|
||||
draggedControlCenterGroupIndex = -1;
|
||||
controlCenterGroupDropIndex = -1;
|
||||
}
|
||||
|
||||
function getControlCenterGroupHeight(group) {
|
||||
const rowCount = group?.rows?.length ?? 0;
|
||||
if (rowCount <= 0)
|
||||
return controlCenterGroupVerticalPadding;
|
||||
|
||||
return rowCount * controlCenterRowHeight + Math.max(0, rowCount - 1) * controlCenterRowSpacing + controlCenterGroupVerticalPadding;
|
||||
}
|
||||
|
||||
function getControlCenterPopupHeight(groups) {
|
||||
const orderedGroups = groups || [];
|
||||
let totalHeight = Theme.spacingS * 2;
|
||||
|
||||
for (let i = 0; i < orderedGroups.length; i++) {
|
||||
totalHeight += getControlCenterGroupHeight(orderedGroups[i]);
|
||||
if (i < orderedGroups.length - 1)
|
||||
totalHeight += controlCenterMenuSpacing;
|
||||
}
|
||||
|
||||
return totalHeight;
|
||||
}
|
||||
|
||||
function getOrderedControlCenterGroups() {
|
||||
const baseGroups = defaultControlCenterGroups.slice();
|
||||
const currentWidget = contentItem.getCurrentWidgetData();
|
||||
const savedOrder = currentWidget?.controlCenterGroupOrder;
|
||||
if (!savedOrder || !savedOrder.length)
|
||||
return baseGroups;
|
||||
|
||||
const groupMap = {};
|
||||
for (let i = 0; i < baseGroups.length; i++)
|
||||
groupMap[baseGroups[i].id] = baseGroups[i];
|
||||
|
||||
const orderedGroups = [];
|
||||
for (let i = 0; i < savedOrder.length; i++) {
|
||||
const groupId = savedOrder[i];
|
||||
const group = groupMap[groupId];
|
||||
if (group) {
|
||||
orderedGroups.push(group);
|
||||
delete groupMap[groupId];
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < baseGroups.length; i++) {
|
||||
const group = baseGroups[i];
|
||||
if (groupMap[group.id])
|
||||
orderedGroups.push(group);
|
||||
}
|
||||
|
||||
return orderedGroups;
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
@@ -1069,83 +1293,64 @@ Column {
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
function getCurrentWidgetData() {
|
||||
const widgets = root.items || [];
|
||||
if (controlCenterContextMenu.widgetIndex >= 0 && controlCenterContextMenu.widgetIndex < widgets.length)
|
||||
return widgets[controlCenterContextMenu.widgetIndex];
|
||||
return controlCenterContextMenu.widgetData;
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
icon: "lan",
|
||||
label: I18n.tr("Network"),
|
||||
setting: "showNetworkIcon"
|
||||
},
|
||||
{
|
||||
icon: "vpn_lock",
|
||||
label: I18n.tr("VPN"),
|
||||
setting: "showVpnIcon"
|
||||
},
|
||||
{
|
||||
icon: "bluetooth",
|
||||
label: I18n.tr("Bluetooth"),
|
||||
setting: "showBluetoothIcon"
|
||||
},
|
||||
{
|
||||
icon: "volume_up",
|
||||
label: I18n.tr("Audio"),
|
||||
setting: "showAudioIcon"
|
||||
},
|
||||
{
|
||||
icon: "percent",
|
||||
label: I18n.tr("Volume"),
|
||||
setting: "showAudioPercent"
|
||||
},
|
||||
{
|
||||
icon: "mic",
|
||||
label: I18n.tr("Microphone"),
|
||||
setting: "showMicIcon"
|
||||
},
|
||||
{
|
||||
icon: "percent",
|
||||
label: I18n.tr("Microphone Volume"),
|
||||
setting: "showMicPercent"
|
||||
},
|
||||
{
|
||||
icon: "brightness_high",
|
||||
label: I18n.tr("Brightness"),
|
||||
setting: "showBrightnessIcon"
|
||||
},
|
||||
{
|
||||
icon: "percent",
|
||||
label: I18n.tr("Brightness Value"),
|
||||
setting: "showBrightnessPercent"
|
||||
},
|
||||
{
|
||||
icon: "battery_full",
|
||||
label: I18n.tr("Battery"),
|
||||
setting: "showBatteryIcon"
|
||||
},
|
||||
{
|
||||
icon: "print",
|
||||
label: I18n.tr("Printer"),
|
||||
setting: "showPrinterIcon"
|
||||
},
|
||||
{
|
||||
icon: "screen_record",
|
||||
label: I18n.tr("Screen Sharing"),
|
||||
setting: "showScreenSharingIcon"
|
||||
}
|
||||
]
|
||||
Item {
|
||||
id: controlCenterContentMetrics
|
||||
visible: false
|
||||
implicitWidth: 16 + Theme.spacingS + 16 + Theme.spacingS + longestControlCenterLabelMetrics.advanceWidth + Theme.spacingM + 40 + Theme.spacingS * 2 + Theme.spacingM
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: longestControlCenterLabelMetrics
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
text: {
|
||||
const labels = [
|
||||
I18n.tr("Network"),
|
||||
I18n.tr("VPN"),
|
||||
I18n.tr("Bluetooth"),
|
||||
I18n.tr("Audio"),
|
||||
I18n.tr("Volume"),
|
||||
I18n.tr("Microphone"),
|
||||
I18n.tr("Microphone Volume"),
|
||||
I18n.tr("Brightness"),
|
||||
I18n.tr("Brightness Value"),
|
||||
I18n.tr("Battery"),
|
||||
I18n.tr("Printer"),
|
||||
I18n.tr("Screen Sharing")
|
||||
];
|
||||
let longest = "";
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
if (labels[i].length > longest.length)
|
||||
longest = labels[i];
|
||||
}
|
||||
return longest;
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: controlCenterContextMenu.controlCenterGroups
|
||||
|
||||
delegate: Item {
|
||||
id: delegateRoot
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
function getCheckedState() {
|
||||
var wd = controlCenterContextMenu.widgetData;
|
||||
switch (modelData.setting) {
|
||||
function getCheckedState(settingName) {
|
||||
const wd = controlCenterContextMenu.contentItem.getCurrentWidgetData();
|
||||
switch (settingName) {
|
||||
case "showNetworkIcon":
|
||||
return wd?.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
|
||||
case "showVpnIcon":
|
||||
@@ -1175,57 +1380,197 @@ Column {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property string rootSetting: modelData.rows[0]?.setting ?? ""
|
||||
readonly property bool rootEnabled: rootSetting ? getCheckedState(rootSetting) : true
|
||||
readonly property bool isDragged: controlCenterContextMenu.draggedControlCenterGroupIndex === index
|
||||
readonly property bool showDropIndicatorAbove: controlCenterContextMenu.controlCenterGroupDropIndex === index
|
||||
readonly property bool showDropIndicatorBelow: controlCenterContextMenu.controlCenterGroupDropIndex === controlCenterContextMenu.controlCenterGroups.length && index === controlCenterContextMenu.controlCenterGroups.length - 1
|
||||
|
||||
width: menuColumn.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: toggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
height: groupBackground.height
|
||||
|
||||
Row {
|
||||
Rectangle {
|
||||
id: groupBackground
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: modelData.icon
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: toggle
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 20
|
||||
checked: getCheckedState()
|
||||
onToggled: {
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled);
|
||||
}
|
||||
anchors.top: parent.top
|
||||
height: groupContent.implicitHeight + Theme.spacingXS * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: isDragged ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.18) : (groupHoverArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent")
|
||||
opacity: isDragged ? 0.75 : 1.0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: toggleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
toggle.checked = !toggle.checked;
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggle.checked);
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -1
|
||||
height: 2
|
||||
radius: 1
|
||||
color: Theme.primary
|
||||
visible: showDropIndicatorAbove
|
||||
z: 3
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -1
|
||||
height: 2
|
||||
radius: 1
|
||||
color: Theme.primary
|
||||
visible: showDropIndicatorBelow
|
||||
z: 3
|
||||
}
|
||||
|
||||
Item {
|
||||
id: groupContent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
implicitHeight: groupColumn.implicitHeight
|
||||
|
||||
Column {
|
||||
id: groupColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
id: groupColumnRepeater
|
||||
model: modelData.rows
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property var rowData: modelData
|
||||
readonly property bool isFirstRow: index === 0
|
||||
readonly property bool rowEnabled: isFirstRow ? true : delegateRoot.rootEnabled
|
||||
readonly property bool computedCheckedState: rowEnabled ? getCheckedState(rowData.setting) : false
|
||||
readonly property bool rowHovered: rowEnabled && (toggleArea.containsMouse || (isFirstRow && groupDragHandleArea.containsMouse))
|
||||
|
||||
width: groupColumn.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
opacity: rowEnabled ? 1.0 : 0.5
|
||||
color: rowHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: toggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Item {
|
||||
width: 16
|
||||
height: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "drag_indicator"
|
||||
size: 16
|
||||
color: groupDragHandleArea.pressed || isDragged ? Theme.primary : Theme.outline
|
||||
visible: isFirstRow
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: groupDragHandleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
enabled: isFirstRow
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||||
|
||||
onPressed: mouse => {
|
||||
mouse.accepted = true;
|
||||
const point = mapToItem(menuColumn, mouse.x, mouse.y);
|
||||
controlCenterContextMenu.updateControlCenterGroupDropIndex(delegateRoot.index, point.y);
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
if (!pressed)
|
||||
return;
|
||||
mouse.accepted = true;
|
||||
const point = mapToItem(menuColumn, mouse.x, mouse.y);
|
||||
controlCenterContextMenu.updateControlCenterGroupDropIndex(delegateRoot.index, point.y);
|
||||
}
|
||||
onReleased: mouse => {
|
||||
mouse.accepted = true;
|
||||
const point = mapToItem(menuColumn, mouse.x, mouse.y);
|
||||
controlCenterContextMenu.updateControlCenterGroupDropIndex(delegateRoot.index, point.y);
|
||||
controlCenterContextMenu.finishControlCenterDrag();
|
||||
}
|
||||
onCanceled: {
|
||||
controlCenterContextMenu.cancelControlCenterDrag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: rowData.icon
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: rowData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: toggle
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 20
|
||||
enabled: rowEnabled
|
||||
checked: computedCheckedState
|
||||
|
||||
onToggled: {
|
||||
if (!rowEnabled)
|
||||
return;
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, rowData.setting, toggled);
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: toggleArea
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 16 + Theme.spacingS * 2
|
||||
hoverEnabled: true
|
||||
cursorShape: rowEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: rowEnabled && controlCenterContextMenu.draggedControlCenterGroupIndex < 0
|
||||
onPressed: {
|
||||
if (!rowEnabled)
|
||||
return;
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, rowData.setting, !computedCheckedState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: groupHoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id: groupRepeater
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -453,8 +453,8 @@ Item {
|
||||
visible: false
|
||||
x: contentContainer.x
|
||||
y: contentContainer.y
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
width: shouldBeVisible ? root.alignedWidth : 0
|
||||
height: shouldBeVisible ? root.alignedHeight : 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -162,6 +162,13 @@ StyledRect {
|
||||
if (root.keyForwardTargets[i])
|
||||
root.keyForwardTargets[i].Keys.pressed(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((event.modifiers & (Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier)) && root.keyForwardTargets.length > 0) {
|
||||
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||
if (root.keyForwardTargets[i])
|
||||
root.keyForwardTargets[i].Keys.pressed(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,8 +99,8 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
// M3 disabled track: on surface 12% opacity
|
||||
color: !toggle.enabled ? Qt.alpha(Theme.surfaceText, 0.12) : (toggle.checked ? Theme.primary : Theme.surfaceVariantAlpha)
|
||||
// Distinguish disabled checked vs unchecked so unchecked disabled switches don't look enabled
|
||||
color: !toggle.enabled ? (toggle.checked ? Qt.alpha(Theme.surfaceText, 0.12) : "transparent") : (toggle.checked ? Theme.primary : Theme.surfaceVariantAlpha)
|
||||
opacity: toggle.toggling ? 0.6 : 1
|
||||
|
||||
// M3 disabled unchecked border: on surface 12% opacity
|
||||
@@ -119,8 +119,8 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// M3 disabled thumb:
|
||||
// checked = solid surface | unchecked = on surface 38%
|
||||
color: !toggle.enabled ? (toggle.checked ? Theme.surface : Qt.alpha(Theme.surfaceText, 0.38)) : (toggle.checked ? Theme.surface : Theme.outline)
|
||||
// checked = solid surface | unchecked = outlined off-state thumb
|
||||
color: !toggle.enabled ? (toggle.checked ? Theme.surface : "transparent") : (toggle.checked ? Theme.surface : Theme.outline)
|
||||
border.color: !toggle.enabled ? (toggle.checked ? "transparent" : Qt.alpha(Theme.surfaceText, 0.38)) : Theme.outline
|
||||
border.width: (toggle.checked && toggle.enabled) ? 1 : 2
|
||||
|
||||
@@ -165,8 +165,8 @@ Item {
|
||||
// M3 disabled icon: on surface 38%
|
||||
color: toggle.enabled ? Theme.surfaceText : Qt.alpha(Theme.surfaceText, 0.38)
|
||||
filled: true
|
||||
opacity: toggle.checked ? 1 : 0
|
||||
scale: toggle.checked ? 1 : 0.6
|
||||
opacity: (toggle.checked && toggle.enabled) ? 1 : 0
|
||||
scale: (toggle.checked && toggle.enabled) ? 1 : 0.6
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user