diff --git a/dots/.config/hypr/hyprland/execs.conf b/dots/.config/hypr/hyprland/execs.conf index 252e3afa8..e4f33f6aa 100644 --- a/dots/.config/hypr/hyprland/execs.conf +++ b/dots/.config/hypr/hyprland/execs.conf @@ -23,3 +23,5 @@ exec-once = wl-paste --type image --watch bash -c 'cliphist store && qs -c $qsCo # Cursor exec-once = hyprctl setcursor Bibata-Modern-Classic 24 +# Fix dock pinned apps not launching properly (https://github.com/end-4/dots-hyprland/issues/2200) +exec-once = sleep 3.5 && hyprctl reload && sleep 0.5 && touch ~/.config/quickshell/ii/shell.qml diff --git a/dots/.config/quickshell/ii/modules/bar/BatteryPopup.qml b/dots/.config/quickshell/ii/modules/bar/BatteryPopup.qml index 5bc07d18c..26eda569e 100644 --- a/dots/.config/quickshell/ii/modules/bar/BatteryPopup.qml +++ b/dots/.config/quickshell/ii/modules/bar/BatteryPopup.qml @@ -13,121 +13,60 @@ StyledPopup { spacing: 4 // Header - Row { - id: header - spacing: 5 - - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - fill: 0 - font.weight: Font.Medium - text: "battery_android_full" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnSurfaceVariant - } - - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: "Battery" - font { - weight: Font.Medium - pixelSize: Appearance.font.pixelSize.normal - } - color: Appearance.colors.colOnSurfaceVariant - } + StyledPopupHeaderRow { + icon: "battery_android_full" + label: Translation.tr("Battery") } - // This row is hidden when the battery is full. - RowLayout { - spacing: 5 - Layout.fillWidth: true - property bool rowVisible: { + StyledPopupValueRow { + visible: { let timeValue = Battery.isCharging ? Battery.timeToFull : Battery.timeToEmpty; let power = Battery.energyRate; return !(Battery.chargeState == 4 || timeValue <= 0 || power <= 0.01); } - visible: rowVisible - opacity: rowVisible ? 1 : 0 - Behavior on opacity { - NumberAnimation { - duration: 500 + icon: "schedule" + label: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:") + value: { + function formatTime(seconds) { + var h = Math.floor(seconds / 3600); + var m = Math.floor((seconds % 3600) / 60); + if (h > 0) + return `${h}h, ${m}m`; + else + return `${m}m`; + } + if (Battery.isCharging) + return formatTime(Battery.timeToFull); + else + return formatTime(Battery.timeToEmpty); + } + } + + StyledPopupValueRow { + visible: !(Battery.chargeState != 4 && Battery.energyRate == 0) + icon: "bolt" + label: { + if (Battery.chargeState == 4) { + return Translation.tr("Fully charged"); + } else if (Battery.chargeState == 1) { + return Translation.tr("Charging:"); + } else { + return Translation.tr("Discharging:"); } } - - MaterialSymbol { - text: "schedule" - color: Appearance.colors.colOnSurfaceVariant - iconSize: Appearance.font.pixelSize.large - } - StyledText { - text: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:") - color: Appearance.colors.colOnSurfaceVariant - } - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - color: Appearance.colors.colOnSurfaceVariant - text: { - function formatTime(seconds) { - var h = Math.floor(seconds / 3600); - var m = Math.floor((seconds % 3600) / 60); - if (h > 0) - return `${h}h, ${m}m`; - else - return `${m}m`; - } - if (Battery.isCharging) - return formatTime(Battery.timeToFull); - else - return formatTime(Battery.timeToEmpty); + value: { + if (Battery.chargeState == 4) { + return ""; + } else { + return `${Battery.energyRate.toFixed(2)}W`; } } } - RowLayout { - spacing: 5 - Layout.fillWidth: true - - property bool rowVisible: !(Battery.chargeState != 4 && Battery.energyRate == 0) - visible: rowVisible - opacity: rowVisible ? 1 : 0 - Behavior on opacity { - NumberAnimation { - duration: 500 - } - } - - MaterialSymbol { - text: "bolt" - color: Appearance.colors.colOnSurfaceVariant - iconSize: Appearance.font.pixelSize.large - } - - StyledText { - text: { - if (Battery.chargeState == 4) { - return Translation.tr("Fully charged"); - } else if (Battery.chargeState == 1) { - return Translation.tr("Charging:"); - } else { - return Translation.tr("Discharging:"); - } - } - color: Appearance.colors.colOnSurfaceVariant - } - - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - color: Appearance.colors.colOnSurfaceVariant - text: { - if (Battery.chargeState == 4) { - return ""; - } else { - return `${Battery.energyRate.toFixed(2)}W`; - } - } - } + StyledPopupValueRow { + icon: "heart_check" + label: Translation.tr("Health:") + value: `${(Battery.health).toFixed(1)}%` } } } diff --git a/dots/.config/quickshell/ii/modules/bar/ClockWidget.qml b/dots/.config/quickshell/ii/modules/bar/ClockWidget.qml index 4d6fb61f2..895ad9be0 100644 --- a/dots/.config/quickshell/ii/modules/bar/ClockWidget.qml +++ b/dots/.config/quickshell/ii/modules/bar/ClockWidget.qml @@ -43,7 +43,7 @@ Item { hoverEnabled: true acceptedButtons: Qt.NoButton - ClockWidgetTooltip { + ClockWidgetPopup { hoverTarget: mouseArea } } diff --git a/dots/.config/quickshell/ii/modules/bar/ClockWidgetPopup.qml b/dots/.config/quickshell/ii/modules/bar/ClockWidgetPopup.qml new file mode 100644 index 000000000..c23edb7b4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/bar/ClockWidgetPopup.qml @@ -0,0 +1,70 @@ +import qs.modules.common +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Layouts + +StyledPopup { + id: root + property string formattedDate: Qt.locale().toString(DateTime.clock.date, "dddd, MMMM dd, yyyy") + property string formattedTime: DateTime.time + property string formattedUptime: DateTime.uptime + property string todosSection: getUpcomingTodos() + + function getUpcomingTodos() { + const unfinishedTodos = Todo.list.filter(function (item) { + return !item.done; + }); + if (unfinishedTodos.length === 0) { + return Translation.tr("No pending tasks"); + } + + // Limit to first 5 todos to keep popup manageable + const limitedTodos = unfinishedTodos.slice(0, 5); + let todoText = limitedTodos.map(function (item, index) { + return ` ${index + 1}. ${item.content}`; + }).join('\n'); + + if (unfinishedTodos.length > 5) { + todoText += `\n ${Translation.tr("... and %1 more").arg(unfinishedTodos.length - 5)}`; + } + + return todoText; + } + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: 4 + + StyledPopupHeaderRow { + icon: "calendar_month" + label: root.formattedDate + } + + StyledPopupValueRow { + icon: "timelapse" + label: Translation.tr("System uptime:") + value: root.formattedUptime + } + + // Tasks + Column { + spacing: 0 + Layout.fillWidth: true + + StyledPopupValueRow { + icon: "checklist" + label: Translation.tr("To Do:") + value: "" + } + + StyledText { + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + color: Appearance.colors.colOnSurfaceVariant + text: root.todosSection + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/bar/ClockWidgetTooltip.qml b/dots/.config/quickshell/ii/modules/bar/ClockWidgetTooltip.qml deleted file mode 100644 index 734d89b22..000000000 --- a/dots/.config/quickshell/ii/modules/bar/ClockWidgetTooltip.qml +++ /dev/null @@ -1,110 +0,0 @@ -import qs.modules.common -import qs.modules.common.widgets -import qs.services -import QtQuick -import QtQuick.Layouts - -StyledPopup { - id: root - property string formattedDate: Qt.locale().toString(DateTime.clock.date, "dddd, MMMM dd, yyyy") - property string formattedTime: DateTime.time - property string formattedUptime: DateTime.uptime - property string todosSection: getUpcomingTodos() - - function getUpcomingTodos() { - const unfinishedTodos = Todo.list.filter(function (item) { - return !item.done; - }); - if (unfinishedTodos.length === 0) { - return Translation.tr("No pending tasks"); - } - - // Limit to first 5 todos to keep popup manageable - const limitedTodos = unfinishedTodos.slice(0, 5); - let todoText = limitedTodos.map(function (item, index) { - return `${index + 1}. ${item.content}`; - }).join('\n'); - - if (unfinishedTodos.length > 5) { - todoText += `\n${Translation.tr("... and %1 more").arg(unfinishedTodos.length - 5)}`; - } - - return todoText; - } - - ColumnLayout { - id: columnLayout - anchors.centerIn: parent - spacing: 4 - - // Date + Time row - Row { - spacing: 5 - - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - fill: 0 - font.weight: Font.Medium - text: "calendar_month" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnSurfaceVariant - } - StyledText { - anchors.verticalCenter: parent.verticalCenter - horizontalAlignment: Text.AlignLeft - color: Appearance.colors.colOnSurfaceVariant - text: `${root.formattedDate}` - font.weight: Font.Medium - } - } - - // Uptime row - RowLayout { - spacing: 5 - Layout.fillWidth: true - MaterialSymbol { - text: "timelapse" - color: Appearance.colors.colOnSurfaceVariant - font.pixelSize: Appearance.font.pixelSize.large - } - StyledText { - text: Translation.tr("System uptime:") - color: Appearance.colors.colOnSurfaceVariant - } - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - color: Appearance.colors.colOnSurfaceVariant - text: root.formattedUptime - } - } - - // Tasks - Column { - spacing: 0 - Layout.fillWidth: true - - Row { - spacing: 4 - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - text: "checklist" - color: Appearance.colors.colOnSurfaceVariant - font.pixelSize: Appearance.font.pixelSize.large - } - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: Translation.tr("To Do:") - color: Appearance.colors.colOnSurfaceVariant - } - } - - StyledText { - horizontalAlignment: Text.AlignLeft - wrapMode: Text.Wrap - color: Appearance.colors.colOnSurfaceVariant - text: root.todosSection - } - } - } -} diff --git a/dots/.config/quickshell/ii/modules/bar/ResourcesPopup.qml b/dots/.config/quickshell/ii/modules/bar/ResourcesPopup.qml index 1cac240d8..40ed72756 100644 --- a/dots/.config/quickshell/ii/modules/bar/ResourcesPopup.qml +++ b/dots/.config/quickshell/ii/modules/bar/ResourcesPopup.qml @@ -12,57 +12,6 @@ StyledPopup { return (kb / (1024 * 1024)).toFixed(1) + " GB"; } - component ResourceItem: RowLayout { - id: resourceItem - required property string icon - required property string label - required property string value - spacing: 4 - - MaterialSymbol { - text: resourceItem.icon - color: Appearance.colors.colOnSurfaceVariant - iconSize: Appearance.font.pixelSize.large - } - StyledText { - text: resourceItem.label - color: Appearance.colors.colOnSurfaceVariant - } - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - visible: resourceItem.value !== "" - color: Appearance.colors.colOnSurfaceVariant - text: resourceItem.value - } - } - - component ResourceHeaderItem: Row { - id: headerItem - required property var icon - required property var label - spacing: 5 - - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - fill: 0 - font.weight: Font.Medium - text: headerItem.icon - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnSurfaceVariant - } - - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: headerItem.label - font { - weight: Font.Medium - pixelSize: Appearance.font.pixelSize.normal - } - color: Appearance.colors.colOnSurfaceVariant - } - } - Row { anchors.centerIn: parent spacing: 12 @@ -71,23 +20,23 @@ StyledPopup { anchors.top: parent.top spacing: 8 - ResourceHeaderItem { + StyledPopupHeaderRow { icon: "memory" label: "RAM" } Column { spacing: 4 - ResourceItem { + StyledPopupValueRow { icon: "clock_loader_60" label: Translation.tr("Used:") value: root.formatKB(ResourceUsage.memoryUsed) } - ResourceItem { + StyledPopupValueRow { icon: "check_circle" label: Translation.tr("Free:") value: root.formatKB(ResourceUsage.memoryFree) } - ResourceItem { + StyledPopupValueRow { icon: "empty_dashboard" label: Translation.tr("Total:") value: root.formatKB(ResourceUsage.memoryTotal) @@ -100,23 +49,23 @@ StyledPopup { anchors.top: parent.top spacing: 8 - ResourceHeaderItem { + StyledPopupHeaderRow { icon: "swap_horiz" label: "Swap" } Column { spacing: 4 - ResourceItem { + StyledPopupValueRow { icon: "clock_loader_60" label: Translation.tr("Used:") value: root.formatKB(ResourceUsage.swapUsed) } - ResourceItem { + StyledPopupValueRow { icon: "check_circle" label: Translation.tr("Free:") value: root.formatKB(ResourceUsage.swapFree) } - ResourceItem { + StyledPopupValueRow { icon: "empty_dashboard" label: Translation.tr("Total:") value: root.formatKB(ResourceUsage.swapTotal) @@ -128,16 +77,16 @@ StyledPopup { anchors.top: parent.top spacing: 8 - ResourceHeaderItem { + StyledPopupHeaderRow { icon: "planner_review" label: "CPU" } Column { spacing: 4 - ResourceItem { + StyledPopupValueRow { icon: "bolt" label: Translation.tr("Load:") - value: (ResourceUsage.cpuUsage > 0.8 ? Translation.tr("High") : ResourceUsage.cpuUsage > 0.4 ? Translation.tr("Medium") : Translation.tr("Low")) + ` (${Math.round(ResourceUsage.cpuUsage * 100)}%)` + value: `${Math.round(ResourceUsage.cpuUsage * 100)}%` } } } diff --git a/dots/.config/quickshell/ii/modules/bar/StyledPopupHeaderRow.qml b/dots/.config/quickshell/ii/modules/bar/StyledPopupHeaderRow.qml new file mode 100644 index 000000000..f5c7ba445 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/bar/StyledPopupHeaderRow.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common +import qs.modules.common.widgets + +Row { + id: root + required property var icon + required property var label + spacing: 5 + + MaterialSymbol { + anchors.verticalCenter: parent.verticalCenter + fill: 0 + font.weight: Font.DemiBold + text: root.icon + iconSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnSurfaceVariant + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: root.label + font { + weight: Font.DemiBold + pixelSize: Appearance.font.pixelSize.normal + } + color: Appearance.colors.colOnSurfaceVariant + } +} \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/bar/StyledPopupValueRow.qml b/dots/.config/quickshell/ii/modules/bar/StyledPopupValueRow.qml new file mode 100644 index 000000000..de8ac579e --- /dev/null +++ b/dots/.config/quickshell/ii/modules/bar/StyledPopupValueRow.qml @@ -0,0 +1,29 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common +import qs.modules.common.widgets + +RowLayout { + id: root + required property string icon + required property string label + required property string value + spacing: 4 + + MaterialSymbol { + text: root.icon + color: Appearance.colors.colOnSurfaceVariant + iconSize: Appearance.font.pixelSize.large + } + StyledText { + text: root.label + color: Appearance.colors.colOnSurfaceVariant + } + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + visible: root.value !== "" + color: Appearance.colors.colOnSurfaceVariant + text: root.value + } +} diff --git a/dots/.config/quickshell/ii/modules/bar/SysTray.qml b/dots/.config/quickshell/ii/modules/bar/SysTray.qml index 5489eb2f3..0233b2405 100644 --- a/dots/.config/quickshell/ii/modules/bar/SysTray.qml +++ b/dots/.config/quickshell/ii/modules/bar/SysTray.qml @@ -104,7 +104,6 @@ Item { id: overflowPopup hoverTarget: trayOverflowButton active: root.trayOverflowOpen && root.unpinnedItems.length > 0 - popupBackgroundMargin: 300 // This should be plenty... makes sure tooltips don't get cutoff (easily) GridLayout { id: trayOverflowLayout diff --git a/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml b/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml index cc3eb64a2..2f7197b58 100644 --- a/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml +++ b/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml @@ -25,7 +25,7 @@ Item { visible: Config.options.bar.utilButtons.showScreenSnip sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter - onClicked: Hyprland.dispatch("global quickshell:regionScreenshot") + onClicked: Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]); MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 1 diff --git a/dots/.config/quickshell/ii/modules/cheatsheet/CheatsheetKeybinds.qml b/dots/.config/quickshell/ii/modules/cheatsheet/CheatsheetKeybinds.qml index 5c8124e0a..3a78e86ce 100644 --- a/dots/.config/quickshell/ii/modules/cheatsheet/CheatsheetKeybinds.qml +++ b/dots/.config/quickshell/ii/modules/cheatsheet/CheatsheetKeybinds.qml @@ -14,9 +14,50 @@ Item { property real padding: 4 implicitWidth: row.implicitWidth + padding * 2 implicitHeight: row.implicitHeight + padding * 2 + // Excellent symbol explaination and source : + // http://xahlee.info/comp/unicode_computing_symbols.html + // https://www.nerdfonts.com/cheat-sheet + property var macSymbolMap: ({ + "Ctrl": "󰘴", + "Alt": "󰘵", + "Shift": "󰘶", + "Space": "󱁐", + "Tab": "↹", + "Equal": "󰇼", + "Minus": "", + "Print": "", + "BackSpace": "󰭜", + "Delete": "⌦", + "Return": "󰌑", + "Period": ".", + "Escape": "⎋" + }) + property var functionSymbolMap: ({ + "F1": "󱊫", + "F2": "󱊬", + "F3": "󱊭", + "F4": "󱊮", + "F5": "󱊯", + "F6": "󱊰", + "F7": "󱊱", + "F8": "󱊲", + "F9": "󱊳", + "F10": "󱊴", + "F11": "󱊵", + "F12": "󱊶", + }) + + property var mouseSymbolMap: ({ + "mouse_up": "󱕐", + "mouse_down": "󱕑", + "mouse:272": "L󰍽", + "mouse:273": "R󰍽", + "Scroll ↑/↓": "󱕒", + "Page_↑/↓": "⇞/⇟", + }) property var keyBlacklist: ["Super_L"] - property var keySubstitutions: ({ + property var keySubstitutions: Object.assign({ "Super": "󰖳", "mouse_up": "Scroll ↓", // ikr, weird "mouse_down": "Scroll ↑", // trust me bro @@ -27,7 +68,14 @@ Item { "Hash": "#", "Return": "Enter", // "Shift": "", - }) + }, + !!Config.options.cheatsheet.superKey ? { + "Super": Config.options.cheatsheet.superKey, + }: {}, + Config.options.cheatsheet.useMacSymbol ? macSymbolMap : {}, + Config.options.cheatsheet.useFnSymbol ? functionSymbolMap : {}, + Config.options.cheatsheet.useMouseSymbol ? mouseSymbolMap : {}, + ) Row { // Keybind columns id: row @@ -77,6 +125,17 @@ Item { var result = []; for (var i = 0; i < keybindSection.modelData.keybinds.length; i++) { const keybind = keybindSection.modelData.keybinds[i]; + + if (!Config.options.cheatsheet.splitButtons) { + + for (var j = 0; j < keybind.mods.length; j++) { + keybind.mods[j] = keySubstitutions[keybind.mods[j]] || keybind.mods[j]; + } + keybind.mods = [keybind.mods.join(' ') ] + keybind.mods[0] += !keyBlacklist.includes(keybind.key) && keybind.mods[0].length ? ' ' : '' + keybind.mods[0] += !keyBlacklist.includes(keybind.key) ? (keySubstitutions[keybind.key] || keybind.key) : '' + } + result.push({ "type": "keys", "mods": keybind.mods, @@ -108,17 +167,19 @@ Item { delegate: KeyboardKey { required property var modelData key: keySubstitutions[modelData] || modelData + pixelSize: Config.options.cheatsheet.fontSize.key } } StyledText { id: keybindPlus - visible: !keyBlacklist.includes(modelData.key) && modelData.mods.length > 0 + visible: Config.options.cheatsheet.splitButtons && !keyBlacklist.includes(modelData.key) && modelData.mods.length > 0 text: "+" } KeyboardKey { id: keybindKey - visible: !keyBlacklist.includes(modelData.key) + visible: Config.options.cheatsheet.splitButtons && !keyBlacklist.includes(modelData.key) key: keySubstitutions[modelData.key] || modelData.key + pixelSize: Config.options.cheatsheet.fontSize.key color: Appearance.colors.colOnLayer0 } } @@ -134,7 +195,7 @@ Item { StyledText { id: commentText anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.smaller + font.pixelSize: Config.options.cheatsheet.fontSize.comment || Appearance.font.pixelSize.smaller text: modelData.comment } } @@ -152,4 +213,4 @@ Item { } } -} \ No newline at end of file +} diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 6cfcd3637..d064d2533 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -267,6 +267,22 @@ Singleton { property int suspend: 3 } + property JsonObject cheatsheet: JsonObject { + // Use a nerdfont to see the icons + // 0: 󰖳 | 1: 󰌽 | 2: 󰘳 | 3:  | 4: 󰨡 + // 5:  | 6:  | 7: 󰣇 | 8:  | 9:  + // 10:  | 11:  | 12:  | 13:  | 14: 󱄛 + property string superKey: "󰖳" + property bool useMacSymbol: false + property bool splitButtons: true + property bool useMouseSymbol: false + property bool useFnSymbol: false + property JsonObject fontSize: JsonObject { + property int key: Appearance.font.pixelSize.smaller + property int comment: Appearance.font.pixelSize.smaller + } + } + property JsonObject conflictKiller: JsonObject { property bool autoKillNotificationDaemons: false property bool autoKillTrays: false @@ -395,6 +411,7 @@ Singleton { property JsonObject resources: JsonObject { property int updateInterval: 3000 + property int historyLength: 60 } property JsonObject musicRecognition: JsonObject { @@ -475,6 +492,14 @@ Singleton { } } + property JsonObject screenRecord: JsonObject { + property string savePath: Directories.videos.replace("file://","") // strip "file://" + } + + property JsonObject screenSnip: JsonObject { + property string savePath: "" // only copy to clipboard when empty + } + property JsonObject sounds: JsonObject { property bool battery: false property bool pomodoro: false diff --git a/dots/.config/quickshell/ii/modules/common/Persistent.qml b/dots/.config/quickshell/ii/modules/common/Persistent.qml index 7e077b2c3..c4c0b2f1a 100644 --- a/dots/.config/quickshell/ii/modules/common/Persistent.qml +++ b/dots/.config/quickshell/ii/modules/common/Persistent.qml @@ -80,18 +80,31 @@ Singleton { } property JsonObject overlay: JsonObject { - property list open: ["crosshair"] + property list open: ["crosshair", "recorder", "volumeMixer", "resources"] property JsonObject crosshair: JsonObject { property bool pinned: false property bool clickthrough: true - property real x: 100 - property real y: 100 + property real x: 835 + property real y: 490 + } + property JsonObject recorder: JsonObject { + property bool pinned: false + property bool clickthrough: false + property real x: 80 + property real y: 80 + } + property JsonObject resources: JsonObject { + property bool pinned: false + property bool clickthrough: true + property real x: 1500 + property real y: 770 + property int tabIndex: 0 } property JsonObject volumeMixer: JsonObject { property bool pinned: false property bool clickthrough: false - property real x: 55 - property real y: 188 + property real x: 80 + property real y: 280 } property JsonObject fpsLimiter: JsonObject { property bool pinned: false diff --git a/dots/.config/quickshell/ii/modules/common/widgets/Graph.qml b/dots/.config/quickshell/ii/modules/common/widgets/Graph.qml new file mode 100644 index 000000000..4747e5665 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/Graph.qml @@ -0,0 +1,51 @@ +import QtQuick +import qs.modules.common +import qs.modules.common.functions + +/* + * Simple one value line graph + */ +Canvas { + id: root + + enum Alignment { Left, Right } + + required property list values + property int points: values.length + property color color: Appearance.colors.colPrimary + property real fillOpacity: 0.5 + property var alignment: Graph.Alignment.Left + + onValuesChanged: root.requestPaint() + onPaint: { + var ctx = getContext("2d") + ctx.clearRect(0, 0, width, height) + if (!root.values || root.values.length < 2) + return + + var n = root.points + var dx = width / (n - 1) + ctx.strokeStyle = root.color + ctx.fillStyle = ColorUtils.transparentize(root.color, 1 - root.fillOpacity) + ctx.lineWidth = 2 + ctx.beginPath() + for (var i = 0; i < n; ++i) { + var valueIndex = (root.alignment === Graph.Alignment.Right) ? root.values.length - n + i : i + if (valueIndex < 0 || valueIndex >= root.values.length) { + continue; // No data for this point + } + var x = i * dx + var norm = root.values[valueIndex] // already in 0-1 range + var y = height - norm * height + if (valueIndex === 0) { + ctx.moveTo(x, height) + ctx.lineTo(x, y) + } else { + ctx.lineTo(x, y) + } + } + ctx.stroke() + ctx.lineTo(width, height) + ctx.fill() + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/KeyboardKey.qml b/dots/.config/quickshell/ii/modules/common/widgets/KeyboardKey.qml index 14c75c62d..cdb287aca 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/KeyboardKey.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/KeyboardKey.qml @@ -11,6 +11,7 @@ Rectangle { property real extraBottomBorderWidth: 2 property color borderColor: Appearance.colors.colOnLayer0 property real borderRadius: 5 + property real pixelSize: Appearance.font.pixelSize.smaller property color keyColor: Appearance.m3colors.m3surfaceContainerLow implicitWidth: keyFace.implicitWidth + borderWidth * 2 implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth @@ -35,7 +36,7 @@ Rectangle { id: keyText anchors.centerIn: parent font.family: Appearance.font.family.monospace - font.pixelSize: Appearance.font.pixelSize.smaller + font.pixelSize: root.pixelSize text: key } } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabBar.qml b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabBar.qml new file mode 100644 index 000000000..dcb490048 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabBar.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.modules.common +import qs.modules.common.models + +TabBar { + id: root + property real indicatorPadding: 8 + Layout.fillWidth: true + + background: Item { + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) root.incrementCurrentIndex(); + else if (event.angleDelta.y > 0) root.decrementCurrentIndex(); + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + + Rectangle { + id: activeIndicator + z: 9999 + anchors.bottom: parent.bottom + topLeftRadius: height + topRightRadius: height + bottomLeftRadius: 0 + bottomRightRadius: 0 + color: Appearance.colors.colPrimary + // Animation + property real baseWidth: root.width / root.count + AnimatedTabIndexPair { + id: idxPair + index: root.currentIndex + } + height: 3 + x: Math.min(idxPair.idx1, idxPair.idx2) * baseWidth + root.indicatorPadding + width: ((Math.max(idxPair.idx1, idxPair.idx2) + 1) * baseWidth - root.indicatorPadding) - x + } + + Rectangle { // Tabbar bottom border + id: tabBarBottomBorder + z: 9998 + anchors.bottom: parent.bottom + height: 1 + anchors { + left: parent.left + right: parent.right + } + color: Appearance.colors.colOutlineVariant + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml index b1036c777..b0bc9b4de 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml @@ -1,6 +1,6 @@ import qs.modules.common -import qs.modules.common.widgets import qs.modules.common.functions +import qs.modules.common.widgets import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -10,14 +10,12 @@ TabButton { id: root property string buttonText property string buttonIcon - property bool selected: false property int rippleDuration: 1200 - height: buttonBackground.height property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2 - property color colBackground: ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) - property color colBackgroundHover: Appearance.colors.colLayer1Hover - property color colRipple: Appearance.colors.colLayer1Active + property color colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer) + property color colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, root.checked ? 1 : 0.95) + property color colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95) PointingHandInteraction {} @@ -91,8 +89,12 @@ TabButton { background: Rectangle { id: buttonBackground + anchors { + fill: parent + margins: 3 + } radius: Appearance?.rounding.normal - implicitHeight: 37 + implicitHeight: 42 color: (root.hovered ? root.colBackgroundHover : root.colBackground) layer.enabled: true layer.effect: OpacityMask { @@ -156,8 +158,8 @@ TabButton { verticalAlignment: Text.AlignVCenter text: buttonIcon iconSize: Appearance.font.pixelSize.huge - fill: selected ? 1 : 0 - color: selected ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 + fill: root.checked ? 1 : 0 + color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } @@ -167,7 +169,7 @@ TabButton { id: buttonTextWidget verticalAlignment: Text.AlignVCenter font.pixelSize: Appearance.font.pixelSize.small - color: selected ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 + color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 text: buttonText Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/dots/.config/quickshell/ii/modules/lock/Lock.qml b/dots/.config/quickshell/ii/modules/lock/Lock.qml index e5557425b..4266d03f7 100644 --- a/dots/.config/quickshell/ii/modules/lock/Lock.qml +++ b/dots/.config/quickshell/ii/modules/lock/Lock.qml @@ -128,11 +128,19 @@ Scope { } } + function lock() { + if (Config.options.lock.useHyprlock) { + Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); + return; + } + GlobalStates.screenLocked = true; + } + IpcHandler { target: "lock" function activate(): void { - GlobalStates.screenLocked = true; + root.lock(); } function focus(): void { lockContext.shouldReFocus(); @@ -144,11 +152,7 @@ Scope { description: "Locks the screen" onPressed: { - if (Config.options.lock.useHyprlock) { - Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); - return; - } - GlobalStates.screenLocked = true; + root.lock() } } @@ -165,7 +169,7 @@ Scope { function initIfReady() { if (!Config.ready || !Persistent.ready) return; if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) { - Hyprland.dispatch("global quickshell:lock") + root.lock(); } else { KeyringStorage.fetchKeyringData(); } diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml index dcfeae099..b228deda6 100644 --- a/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml @@ -6,9 +6,11 @@ Singleton { id: root readonly property list availableWidgets: [ + { identifier: "recorder", materialSymbol: "screen_record" }, + { identifier: "volumeMixer", materialSymbol: "volume_up" }, { identifier: "crosshair", materialSymbol: "point_scan" }, { identifier: "fpsLimiter", materialSymbol: "animation" }, - { identifier: "volumeMixer", materialSymbol: "volume_up" } + { identifier: "resources", materialSymbol: "browse_activity" } ] readonly property bool hasPinnedWidgets: root.pinnedWidgetIdentifiers.length > 0 diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml index dffdfaaee..fc75d1455 100644 --- a/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml @@ -9,6 +9,8 @@ import Quickshell.Bluetooth import qs.modules.overlay.crosshair import qs.modules.overlay.volumeMixer import qs.modules.overlay.fpsLimiter +import qs.modules.overlay.recorder +import qs.modules.overlay.resources DelegateChooser { id: root @@ -17,4 +19,6 @@ DelegateChooser { DelegateChoice { roleValue: "crosshair"; Crosshair {} } DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} } DelegateChoice { roleValue: "fpsLimiter"; FpsLimiter {} } + DelegateChoice { roleValue: "recorder"; Recorder {} } + DelegateChoice { roleValue: "resources"; Resources {} } } diff --git a/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml b/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml index 5e6a0ce3f..42d61c3bd 100644 --- a/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml +++ b/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml @@ -21,6 +21,7 @@ AbstractOverlayWidget { id: root required property Item contentItem + property bool fancyBorders: true required property var modelData readonly property string identifier: modelData.identifier @@ -29,6 +30,8 @@ AbstractOverlayWidget { property var persistentStateEntry: Persistent.states.overlay[identifier] property real radius: Appearance.rounding.windowRounding property real minWidth: 250 + property real padding: 6 + property real contentRadius: radius - padding draggable: GlobalStates.overlayOpen x: Math.round(persistentStateEntry.x) // Round or it'll be blurry @@ -96,11 +99,15 @@ AbstractOverlayWidget { Rectangle { id: border anchors.fill: parent - color: "transparent" + color: (root.fancyBorders && GlobalStates.overlayOpen) ? Appearance.colors.colLayer1 : "transparent" radius: root.radius border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1) border.width: 1 + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + layer.enabled: GlobalStates.overlayOpen layer.effect: OpacityMask { maskSource: Rectangle { @@ -110,25 +117,23 @@ AbstractOverlayWidget { } } - Column { + ColumnLayout { id: contentColumn - z: -1 + z: root.fancyBorders ? 0 : -1 anchors.fill: parent + spacing: 0 // Title bar Rectangle { id: titleBar opacity: GlobalStates.overlayOpen ? 1 : 0 - anchors { - left: parent.left - right: parent.right - } - property real padding: 2 + Layout.fillWidth: true + property real padding: 6 implicitWidth: titleBarRow.implicitWidth + padding * 2 implicitHeight: titleBarRow.implicitHeight + padding * 2 - color: Appearance.m3colors.m3surfaceContainer - border.color: Appearance.colors.colOutlineVariant - border.width: 1 + color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1 + // border.color: Appearance.colors.colOutlineVariant + // border.width: 1 RowLayout { id: titleBarRow @@ -191,7 +196,10 @@ AbstractOverlayWidget { // Content Item { id: contentContainer - anchors.horizontalCenter: parent.horizontalCenter + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: root.fancyBorders ? root.padding : 0 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter implicitWidth: root.contentItem.implicitWidth implicitHeight: root.contentItem.implicitHeight children: [root.contentItem] diff --git a/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml b/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml index abb1cbf26..bb60bd1fd 100644 --- a/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml +++ b/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml @@ -1,9 +1,13 @@ import QtQuick +import QtQuick.Layouts import Quickshell import qs.modules.common import qs.modules.overlay StyledOverlayWidget { id: root - contentItem: CrosshairContent {} + fancyBorders: false // Crosshair should be see-through + contentItem: CrosshairContent { + anchors.centerIn: parent + } } diff --git a/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml b/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml new file mode 100644 index 000000000..e853819b4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml @@ -0,0 +1,122 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.overlay + +StyledOverlayWidget { + id: root + + contentItem: Rectangle { + id: contentItem + anchors.centerIn: parent + radius: root.contentRadius + color: Appearance.m3colors.m3surfaceContainer + property real padding: 8 + implicitHeight: contentColumn.implicitHeight + padding * 2 + implicitWidth: 350 + ColumnLayout { + id: contentColumn + anchors { + fill: parent + margins: parent.padding + } + spacing: 10 + + Row { + Layout.alignment: Qt.AlignHCenter + spacing: 10 + + BigRecorderButton { + materialSymbol: "screenshot_region" + name: "Screenshot region" + onClicked: { + GlobalStates.overlayOpen = false; + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]); + } + } + + BigRecorderButton { + materialSymbol: "photo_camera" + name: "Screenshot" + onClicked: { + GlobalStates.overlayOpen = false; + Quickshell.execDetached(["bash", "-c", "grim - | wl-copy"]); + } + } + + BigRecorderButton { + materialSymbol: "screen_record" + name: "Record region" + onClicked: { + GlobalStates.overlayOpen = false; + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "recordWithSound"]); + } + } + + BigRecorderButton { + materialSymbol: "capture" + name: "Record screen" + onClicked: { + GlobalStates.overlayOpen = false; + Quickshell.execDetached([Directories.recordScriptPath, "--fullscreen", "--sound"]); + } + } + } + + RippleButton { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + buttonRadius: height / 2 + colBackground: Appearance.colors.colLayer3 + colBackgroundHover: Appearance.colors.colLayer3Hover + colRipple: Appearance.colors.colLayer3Active + onClicked: { + GlobalStates.overlayOpen = false; + Qt.openUrlExternally(Directories.videos); + } + contentItem: Row { + anchors.centerIn: parent + spacing: 6 + MaterialSymbol { + anchors.verticalCenter: parent.verticalCenter + text: "animated_images" + iconSize: 20 + } + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Open recordings folder") + } + } + } + } + } + + component BigRecorderButton: RippleButton { + id: bigButton + required property string materialSymbol + required property string name + implicitHeight: 66 + implicitWidth: 66 + buttonRadius: height / 2 + + colBackground: Appearance.colors.colLayer3 + colBackgroundHover: Appearance.colors.colLayer3Hover + colRipple: Appearance.colors.colLayer3Active + + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: bigButton.materialSymbol + iconSize: 28 + } + + StyledToolTip { + text: bigButton.name + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/resources/Resources.qml b/dots/.config/quickshell/ii/modules/overlay/resources/Resources.qml new file mode 100644 index 000000000..1b78b867e --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/resources/Resources.qml @@ -0,0 +1,135 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects +import Qt.labs.synchronizer +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.overlay + +StyledOverlayWidget { + id: root + property list resources: [ + { + "icon": "planner_review", + "name": Translation.tr("CPU"), + "history": ResourceUsage.cpuUsageHistory, + "maxAvailableString": ResourceUsage.maxAvailableCpuString + }, + { + "icon": "memory", + "name": Translation.tr("RAM"), + "history": ResourceUsage.memoryUsageHistory, + "maxAvailableString": ResourceUsage.maxAvailableMemoryString + }, + { + "icon": "swap_horiz", + "name": Translation.tr("Swap"), + "history": ResourceUsage.swapUsageHistory, + "maxAvailableString": ResourceUsage.maxAvailableSwapString + }, + ] + + contentItem: Rectangle { + id: contentItem + anchors.centerIn: parent + color: Appearance.m3colors.m3surfaceContainer + radius: root.contentRadius + property real padding: 4 + implicitWidth: 350 + implicitHeight: 200 + // implicitHeight: contentColumn.implicitHeight + padding * 2 + ColumnLayout { + id: contentColumn + anchors { + fill: parent + margins: parent.padding + } + spacing: 10 + + SecondaryTabBar { + id: tabBar + + currentIndex: Persistent.states.overlay.resources.tabIndex + onCurrentIndexChanged: { + Persistent.states.overlay.resources.tabIndex = tabBar.currentIndex; + } + + Repeater { + model: root.resources.length + delegate: SecondaryTabButton { + required property int index + property var modelData: root.resources[index] + buttonIcon: modelData.icon + buttonText: modelData.name + } + } + } + + ResourceSummary { + Layout.margins: 8 + history: root.resources[tabBar.currentIndex]?.history ?? [] + maxAvailableString: root.resources[tabBar.currentIndex]?.maxAvailableString ?? "--" + } + } + } + + component ResourceSummary: RowLayout { + id: resourceSummary + required property list history + required property string maxAvailableString + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 12 + + ColumnLayout { + spacing: 2 + StyledText { + text: (resourceSummary.history[resourceSummary.history.length - 1] * 100).toFixed(1) + "%" + font { + family: Appearance.font.family.numbers + variableAxes: Appearance.font.variableAxes.numbers + pixelSize: Appearance.font.pixelSize.huge + } + } + StyledText { + text: Translation.tr("of %1").arg(resourceSummary.maxAvailableString) + font { + // family: Appearance.font.family.numbers + // variableAxes: Appearance.font.variableAxes.numbers + pixelSize: Appearance.font.pixelSize.smallie + } + color: Appearance.colors.colSubtext + } + Item { + Layout.fillHeight: true + } + } + Rectangle { + id: graphBg + Layout.fillWidth: true + Layout.fillHeight: true + radius: Appearance.rounding.small + color: Appearance.colors.colSecondaryContainer + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: graphBg.width + height: graphBg.height + radius: graphBg.radius + } + } + Graph { + anchors.fill: parent + values: root.resources[tabBar.currentIndex]?.history ?? [] + points: ResourceUsage.historyLength + alignment: Graph.Alignment.Right + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml b/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml index 433a2e11f..487f470b5 100644 --- a/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml +++ b/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml @@ -10,9 +10,10 @@ StyledOverlayWidget { contentItem: Rectangle { anchors.centerIn: parent color: Appearance.m3colors.m3surfaceContainer + radius: root.contentRadius property real padding: 16 - implicitHeight: 700 - implicitWidth: 400 + implicitHeight: 600 + implicitWidth: 350 VolumeDialogContent { anchors.fill: parent diff --git a/dots/.config/quickshell/ii/modules/overview/SearchBar.qml b/dots/.config/quickshell/ii/modules/overview/SearchBar.qml index a0abd54c8..5cd7b9ac5 100644 --- a/dots/.config/quickshell/ii/modules/overview/SearchBar.qml +++ b/dots/.config/quickshell/ii/modules/overview/SearchBar.qml @@ -99,7 +99,7 @@ RowLayout { Layout.bottomMargin: 4 onClicked: { GlobalStates.overviewOpen = false; - Hyprland.dispatch("global quickshell:regionSearch") + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "search"]); } text: "image_search" StyledToolTip { diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index 31f28e45c..a52471ba6 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -33,6 +33,10 @@ PanelWindow { property var selectionMode: RegionSelection.SelectionMode.RectCorners signal dismiss() + property string saveScreenshotDir: Config.options.screenSnip.savePath !== "" + ? Config.options.screenSnip.savePath + : "" + property string screenshotDir: Directories.screenshotTemp property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl property string fileUploadApiEndpoint: "https://uguu.se/upload" @@ -259,7 +263,23 @@ PanelWindow { } switch (root.action) { case RegionSelection.SnipAction.Copy: - snipProc.command = ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`] + if (saveScreenshotDir === "") { + // not saving the screenshot, just copy to clipboard + snipProc.command = ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`] + break; + } + + const savePathBase = root.saveScreenshotDir + + snipProc.command = [ + "bash", "-c", + `mkdir -p '${StringUtils.shellSingleQuoteEscape(savePathBase)}' && \ + saveFileName="screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png" && \ + savePath="${savePathBase}/$saveFileName" && \ + ${cropToStdout} | tee >(wl-copy) > "$savePath" && \ + ${cleanup}` + ] + break; case RegionSelection.SnipAction.Edit: snipProc.command = ["bash", "-c", `${cropToStdout} | swappy -f - && ${cleanup}`] diff --git a/dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml b/dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml index 811cf7ca6..7d51dbe12 100644 --- a/dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml @@ -90,4 +90,6 @@ ContentPage { } } + + } diff --git a/dots/.config/quickshell/ii/modules/settings/BarConfig.qml b/dots/.config/quickshell/ii/modules/settings/BarConfig.qml index 9738bf49c..a53d30644 100644 --- a/dots/.config/quickshell/ii/modules/settings/BarConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/BarConfig.qml @@ -321,7 +321,7 @@ ContentPage { value: '[]' }, { - displayName: Translation.tr("Japanese"), + displayName: Translation.tr("Han chars"), icon: "square_dot", value: '["一","二","三","四","五","六","七","八","九","十","十一","十二","十三","十四","十五","十六","十七","十八","十九","二十"]' }, diff --git a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index c26cce455..c57406a7f 100644 --- a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -7,6 +7,98 @@ import qs.modules.common.widgets ContentPage { forceWidth: true + ContentSection { + icon: "keyboard" + title: Translation.tr("Cheat sheet") + + ContentSubsection { + title: Translation.tr("Super key symbol") + tooltip: Translation.tr("You can also manually edit cheatsheet.superKey") + ConfigSelectionArray { + currentValue: Config.options.cheatsheet.superKey + onSelected: newValue => { + Config.options.cheatsheet.superKey = newValue; + } + // Use a nerdfont to see the icons + options: ([ + "󰖳", "", "󰨡", "", "󰌽", "󰣇", "", "", "", + "", "", "󱄛", "", "", "⌘", "󰀲", "󰟍", "" + ]).map(icon => { return { + displayName: icon, + value: icon + } + }) + } + } + + ConfigSwitch { + buttonIcon: "󰘵" + text: Translation.tr("Use macOS-like symbols for mods keys") + checked: Config.options.cheatsheet.useMacSymbol + onCheckedChanged: { + Config.options.cheatsheet.useMacSymbol = checked; + } + StyledToolTip { + text: Translation.tr("e.g. 󰘴 for Ctrl, 󰘵 for Alt, 󰘶 for Shift, etc") + } + } + + ConfigSwitch { + buttonIcon: "󱊶" + text: Translation.tr("Use symbols for function keys") + checked: Config.options.cheatsheet.useFnSymbol + onCheckedChanged: { + Config.options.cheatsheet.useFnSymbol = checked; + } + StyledToolTip { + text: Translation.tr("e.g. 󱊫 for F1, 󱊶 for F12") + } + } + ConfigSwitch { + buttonIcon: "󰍽" + text: Translation.tr("Use symbols for mouse") + checked: Config.options.cheatsheet.useMouseSymbol + onCheckedChanged: { + Config.options.cheatsheet.useMouseSymbol = checked; + } + StyledToolTip { + text: Translation.tr("Replace 󱕐 for \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"LMB\", R󰍽 \"RMB\", 󱕒 \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"") + } + } + ConfigSwitch { + buttonIcon: "highlight_keyboard_focus" + text: Translation.tr("Split buttons") + checked: Config.options.cheatsheet.splitButtons + onCheckedChanged: { + Config.options.cheatsheet.splitButtons = checked; + } + StyledToolTip { + text: Translation.tr("Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \"󰘴 + A\" instead of \"󰘴 A\")") + } + + } + + ConfigSpinBox { + text: Translation.tr("Keybind font size") + value: Config.options.cheatsheet.fontSize.key + from: 8 + to: 30 + stepSize: 1 + onValueChanged: { + Config.options.cheatsheet.fontSize.key = value; + } + } + ConfigSpinBox { + text: Translation.tr("Description font size") + value: Config.options.cheatsheet.fontSize.comment + from: 8 + to: 30 + stepSize: 1 + onValueChanged: { + Config.options.cheatsheet.fontSize.comment = value; + } + } + } ContentSection { icon: "call_to_action" title: Translation.tr("Dock") @@ -647,4 +739,5 @@ ContentPage { } } } + } diff --git a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml index c9ff0c16b..f5931bb1b 100644 --- a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml @@ -85,6 +85,31 @@ ContentPage { } + ContentSection { + icon: "file_open" + title: Translation.tr("Save paths") + + MaterialTextArea { + Layout.fillWidth: true + placeholderText: Translation.tr("Video Recording Path") + text: Config.options.screenRecord.savePath + wrapMode: TextEdit.Wrap + onTextChanged: { + Config.options.screenRecord.savePath = text; + } + } + + MaterialTextArea { + Layout.fillWidth: true + placeholderText: Translation.tr("Screenshot Path (leave empty to just copy)") + text: Config.options.screenSnip.savePath + wrapMode: TextEdit.Wrap + onTextChanged: { + Config.options.screenSnip.savePath = text; + } + } + } + ContentSection { icon: "search" title: Translation.tr("Search") diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/dots/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index ca7723db3..a62a9616c 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -7,7 +7,6 @@ import QtQuick.Layouts Item { id: root - property int currentTab: 0 property var tabButtonList: [ {"name": Translation.tr("Pomodoro"), "icon": "search_activity"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} @@ -17,20 +16,20 @@ Item { Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs if (event.key === Qt.Key_PageDown) { - currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) + tabBar.incrementCurrentIndex(); } else if (event.key === Qt.Key_PageUp) { - currentTab = Math.max(currentTab - 1, 0) + tabBar.decrementCurrentIndex(); } event.accepted = true } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S - if (currentTab === 0) { + if (tabBar.currentIndex === 0) { TimerService.togglePomodoro() } else { TimerService.toggleStopwatch() } event.accepted = true } else if (event.key === Qt.Key_R) { // Reset with R - if (currentTab === 0) { + if (tabBar.currentIndex === 0) { TimerService.resetPomodoro() } else { TimerService.stopwatchReset() @@ -46,82 +45,19 @@ Item { anchors.fill: parent spacing: 0 - TabBar { + SecondaryTabBar { id: tabBar - Layout.fillWidth: true - currentIndex: currentTab - onCurrentIndexChanged: currentTab = currentIndex - - background: Item { - WheelHandler { - onWheel: (event) => { - if (event.angleDelta.y < 0) - tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) - else if (event.angleDelta.y > 0) - tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - } + currentIndex: swipeView.currentIndex Repeater { model: root.tabButtonList delegate: SecondaryTabButton { - selected: (index == currentTab) buttonText: modelData.name buttonIcon: modelData.icon } } } - Item { // Tab indicator - id: tabIndicator - Layout.fillWidth: true - height: 3 - property bool enableIndicatorAnimation: false - Connections { - target: root - function onCurrentTabChanged() { - tabIndicator.enableIndicatorAnimation = true - } - } - - Rectangle { - id: indicator - property int tabCount: root.tabButtonList.length - property real fullTabSize: root.width / tabCount; - property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth - - implicitWidth: targetWidth - anchors { - top: parent.top - bottom: parent.bottom - } - - x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2 - - color: Appearance.colors.colPrimary - radius: Appearance.rounding.full - - Behavior on x { - enabled: tabIndicator.enableIndicatorAnimation - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - - Behavior on implicitWidth { - enabled: tabIndicator.enableIndicatorAnimation - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - } - } - - Rectangle { // Tabbar bottom border - id: tabBarBottomBorder - Layout.fillWidth: true - height: 1 - color: Appearance.colors.colOutlineVariant - } - SwipeView { id: swipeView Layout.topMargin: 10 @@ -129,11 +65,7 @@ Item { Layout.fillHeight: true spacing: 10 clip: true - currentIndex: currentTab - onCurrentIndexChanged: { - tabIndicator.enableIndicatorAnimation = true - currentTab = currentIndex - } + currentIndex: tabBar.currentIndex // Tabs PomodoroTimer {} diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml index c4b4a5479..875022249 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml @@ -23,7 +23,7 @@ AndroidQuickToggleButton { interval: 300 repeat: false onTriggered: { - Hyprland.dispatch("global quickshell:regionScreenshot") + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]); } } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/todo/TodoWidget.qml b/dots/.config/quickshell/ii/modules/sidebarRight/todo/TodoWidget.qml index 99b0100f5..36c885523 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/todo/TodoWidget.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/todo/TodoWidget.qml @@ -7,7 +7,6 @@ import QtQuick.Layouts Item { id: root - property int currentTab: 0 property var tabButtonList: [{"icon": "checklist", "name": Translation.tr("Unfinished")}, {"name": Translation.tr("Done"), "icon": "check_circle"}] property bool showAddDialog: false property int dialogMargins: 20 @@ -17,9 +16,9 @@ Item { Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageDown) { - currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) + tabBar.incrementCurrentIndex(); } else if (event.key === Qt.Key_PageUp) { - currentTab = Math.max(currentTab - 1, 0) + tabBar.decrementCurrentIndex(); } event.accepted = true; } @@ -39,82 +38,19 @@ Item { anchors.fill: parent spacing: 0 - TabBar { + SecondaryTabBar { id: tabBar - Layout.fillWidth: true - currentIndex: currentTab - onCurrentIndexChanged: currentTab = currentIndex - - background: Item { - WheelHandler { - onWheel: (event) => { - if (event.angleDelta.y < 0) - tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) - else if (event.angleDelta.y > 0) - tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - } + currentIndex: swipeView.currentIndex Repeater { model: root.tabButtonList delegate: SecondaryTabButton { - selected: (index == currentTab) buttonText: modelData.name buttonIcon: modelData.icon } } } - Item { // Tab indicator - id: tabIndicator - Layout.fillWidth: true - height: 3 - property bool enableIndicatorAnimation: false - Connections { - target: root - function onCurrentTabChanged() { - tabIndicator.enableIndicatorAnimation = true - } - } - - Rectangle { - id: indicator - property int tabCount: root.tabButtonList.length - property real fullTabSize: root.width / tabCount; - property real targetWidth: tabBar?.contentItem?.children[0]?.children[tabBar.currentIndex]?.tabContentWidth ?? 0 - - implicitWidth: targetWidth - anchors { - top: parent.top - bottom: parent.bottom - } - - x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2 - - color: Appearance.colors.colPrimary - radius: Appearance.rounding.full - - Behavior on x { - enabled: tabIndicator.enableIndicatorAnimation - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - - Behavior on implicitWidth { - enabled: tabIndicator.enableIndicatorAnimation - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - } - } - - Rectangle { // Tabbar bottom border - id: tabBarBottomBorder - Layout.fillWidth: true - height: 1 - color: Appearance.colors.colOutlineVariant - } - SwipeView { id: swipeView Layout.topMargin: 10 @@ -122,11 +58,7 @@ Item { Layout.fillHeight: true spacing: 10 clip: true - currentIndex: currentTab - onCurrentIndexChanged: { - tabIndicator.enableIndicatorAnimation = true - currentTab = currentIndex - } + currentIndex: tabBar.currentIndex // To Do tab TaskList { @@ -215,7 +147,7 @@ Item { Todo.addTask(todoInput.text) todoInput.text = "" root.showAddDialog = false - root.currentTab = 0 // Show unfinished tasks + tabBar.setCurrentIndex(0) // Show unfinished tasks } } diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml index 391d2e78c..921f63603 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml @@ -36,7 +36,7 @@ Item { hoverEnabled: true acceptedButtons: Qt.NoButton - Bar.ClockWidgetTooltip { + Bar.ClockWidgetPopup { hoverTarget: mouseArea } } diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml index 7a512564a..677941d64 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml @@ -74,30 +74,13 @@ MouseArea { anchors.centerIn: parent spacing: 4 - Row { - spacing: 4 - - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - fill: 0 - font.weight: Font.Medium - text: "music_note" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnSurfaceVariant - } - - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: "Media" - font { - weight: Font.Medium - pixelSize: Appearance.font.pixelSize.normal - } - color: Appearance.colors.colOnSurfaceVariant - } + Bar.StyledPopupHeaderRow { + icon: "music_note" + label: Translation.tr("Media") } StyledText { + color: Appearance.colors.colOnSurfaceVariant text: `${cleanedTitle}${activePlayer?.trackArtist ? '\n' + activePlayer.trackArtist : ''}` } } diff --git a/dots/.config/quickshell/ii/scripts/videos/record.sh b/dots/.config/quickshell/ii/scripts/videos/record.sh index 794bcf1ba..bf0ab504d 100755 --- a/dots/.config/quickshell/ii/scripts/videos/record.sh +++ b/dots/.config/quickshell/ii/scripts/videos/record.sh @@ -1,5 +1,18 @@ #!/usr/bin/env bash +CONFIG_FILE="$HOME/.config/illogical-impulse/config.json" +JSON_PATH=".screenRecord.savePath" + +CUSTOM_PATH=$(jq -r "$JSON_PATH" "$CONFIG_FILE" 2>/dev/null) + +RECORDING_DIR="" + +if [[ -n "$CUSTOM_PATH" ]]; then + RECORDING_DIR="$CUSTOM_PATH" +else + RECORDING_DIR="$HOME/Videos" # Use default path +fi + getdate() { date '+%Y-%m-%d_%H.%M.%S' } @@ -10,12 +23,8 @@ getactivemonitor() { hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name' } -xdgvideo="$(xdg-user-dir VIDEOS)" -if [[ $xdgvideo = "$HOME" ]]; then - unset xdgvideo -fi -mkdir -p "${xdgvideo:-$HOME/Videos}" -cd "${xdgvideo:-$HOME/Videos}" || exit +mkdir -p "$RECORDING_DIR" +cd "$RECORDING_DIR" || exit # parse --region without modifying $@ so other flags like --fullscreen still work ARGS=("$@") @@ -66,4 +75,4 @@ else wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" fi fi -fi +fi \ No newline at end of file diff --git a/dots/.config/quickshell/ii/services/Battery.qml b/dots/.config/quickshell/ii/services/Battery.qml index f40087216..e2a285f22 100644 --- a/dots/.config/quickshell/ii/services/Battery.qml +++ b/dots/.config/quickshell/ii/services/Battery.qml @@ -31,6 +31,25 @@ Singleton { property real timeToEmpty: UPower.displayDevice.timeToEmpty property real timeToFull: UPower.displayDevice.timeToFull + property real health: (function() { + const devList = UPower.devices.values; + for (let i = 0; i < devList.length; ++i) { + const dev = devList[i]; + if (dev.isLaptopBattery && dev.healthSupported) { + const health = dev.healthPercentage; + if (health === 0) { + return 0.01; + } else if (health < 1) { + return health * 100; + } else { + return health; + } + } + } + return 0; + })() + + onIsLowAndNotChargingChanged: { if (!root.available || !isLowAndNotCharging) return; Quickshell.execDetached([ diff --git a/dots/.config/quickshell/ii/services/ResourceUsage.qml b/dots/.config/quickshell/ii/services/ResourceUsage.qml index 650528408..df823adf7 100644 --- a/dots/.config/quickshell/ii/services/ResourceUsage.qml +++ b/dots/.config/quickshell/ii/services/ResourceUsage.qml @@ -10,17 +10,55 @@ import Quickshell.Io * Simple polled resource usage service with RAM, Swap, and CPU usage. */ Singleton { - property double memoryTotal: 1 - property double memoryFree: 1 - property double memoryUsed: memoryTotal - memoryFree - property double memoryUsedPercentage: memoryUsed / memoryTotal - property double swapTotal: 1 - property double swapFree: 1 - property double swapUsed: swapTotal - swapFree - property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0 - property double cpuUsage: 0 + id: root + property real memoryTotal: 1 + property real memoryFree: 0 + property real memoryUsed: memoryTotal - memoryFree + property real memoryUsedPercentage: memoryUsed / memoryTotal + property real swapTotal: 1 + property real swapFree: 0 + property real swapUsed: swapTotal - swapFree + property real swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0 + property real cpuUsage: 0 property var previousCpuStats + property string maxAvailableMemoryString: kbToGbString(ResourceUsage.memoryTotal) + property string maxAvailableSwapString: kbToGbString(ResourceUsage.swapTotal) + property string maxAvailableCpuString: "--" + + readonly property int historyLength: Config?.options.resources.historyLength ?? 60 + property list cpuUsageHistory: [] + property list memoryUsageHistory: [] + property list swapUsageHistory: [] + + function kbToGbString(kb) { + return (kb / (1024 * 1024)).toFixed(1) + " GB"; + } + + function updateMemoryUsageHistory() { + memoryUsageHistory = [...memoryUsageHistory, memoryUsedPercentage] + if (memoryUsageHistory.length > historyLength) { + memoryUsageHistory.shift() + } + } + function updateSwapUsageHistory() { + swapUsageHistory = [...swapUsageHistory, swapUsedPercentage] + if (swapUsageHistory.length > historyLength) { + swapUsageHistory.shift() + } + } + function updateCpuUsageHistory() { + cpuUsageHistory = [...cpuUsageHistory, cpuUsage] + if (cpuUsageHistory.length > historyLength) { + cpuUsageHistory.shift() + } + } + function updateHistories() { + updateMemoryUsageHistory() + updateSwapUsageHistory() + updateCpuUsageHistory() + } + Timer { interval: 1 running: true @@ -53,10 +91,24 @@ Singleton { previousCpuStats = { total, idle } } + + root.updateHistories() interval = Config.options?.resources?.updateInterval ?? 3000 } } FileView { id: fileMeminfo; path: "/proc/meminfo" } FileView { id: fileStat; path: "/proc/stat" } + + Process { + id: findCpuMaxFreqProc + command: ["bash", "-c", "lscpu | grep 'CPU max MHz' | awk '{print $4}'"] + running: true + stdout: StdioCollector { + id: outputCollector + onStreamFinished: { + root.maxAvailableCpuString = (parseFloat(outputCollector.text) / 1000).toFixed(0) + " GHz" + } + } + } }