Merge branch 'main' into featOverlay/MangoHud-Fps-Limiter

This commit is contained in:
reakjra
2025-11-07 18:30:42 +01:00
committed by GitHub
39 changed files with 975 additions and 514 deletions
+2
View File
@@ -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
@@ -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)}%`
}
}
}
@@ -43,7 +43,7 @@ Item {
hoverEnabled: true
acceptedButtons: Qt.NoButton
ClockWidgetTooltip {
ClockWidgetPopup {
hoverTarget: mouseArea
}
}
@@ -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
}
}
}
}
@@ -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
}
}
}
}
@@ -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)}%`
}
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
@@ -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
@@ -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 {
}
}
}
}
@@ -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
@@ -80,18 +80,31 @@ Singleton {
}
property JsonObject overlay: JsonObject {
property list<string> open: ["crosshair"]
property list<string> 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
@@ -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<real> 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()
}
}
@@ -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
}
}
@@ -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
}
}
}
@@ -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)
@@ -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();
}
@@ -6,9 +6,11 @@ Singleton {
id: root
readonly property list<var> 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
@@ -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 {} }
}
@@ -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]
@@ -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
}
}
@@ -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
}
}
}
@@ -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<var> 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<real> 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
}
}
}
}
@@ -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
@@ -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 {
@@ -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}`]
@@ -90,4 +90,6 @@ ContentPage {
}
}
}
@@ -321,7 +321,7 @@ ContentPage {
value: '[]'
},
{
displayName: Translation.tr("Japanese"),
displayName: Translation.tr("Han chars"),
icon: "square_dot",
value: '["一","二","三","四","五","六","七","八","九","十","十一","十二","十三","十四","十五","十六","十七","十八","十九","二十"]'
},
@@ -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 {
}
}
}
}
@@ -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")
@@ -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 {}
@@ -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"]);
}
}
@@ -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
}
}
@@ -36,7 +36,7 @@ Item {
hoverEnabled: true
acceptedButtons: Qt.NoButton
Bar.ClockWidgetTooltip {
Bar.ClockWidgetPopup {
hoverTarget: mouseArea
}
}
@@ -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 : ''}`
}
}
@@ -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 <value> 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
@@ -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([
@@ -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<real> cpuUsageHistory: []
property list<real> memoryUsageHistory: []
property list<real> 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"
}
}
}
}