diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-sync.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-sync.svg new file mode 100644 index 000000000..73df6838e --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-sync.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index d5169e701..c9330d78e 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -536,6 +536,12 @@ Singleton { } property bool secondPrecision: false } + + property JsonObject updates: JsonObject { + property int checkInterval: 120 // minutes + property int adviseUpdateThreshold: 75 // packages + property int stronglyAdviseUpdateThreshold: 200 // packages + } property JsonObject wallpaperSelector: JsonObject { property bool useSystemFileDialog: false diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/BarMenu.qml b/dots/.config/quickshell/ii/modules/waffle/bar/BarMenu.qml index 14a9bc0ec..871abc23f 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/BarMenu.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/BarMenu.qml @@ -11,8 +11,10 @@ BarPopup { id: root default property var menuData property var model: [ - {iconName: "start-here", text: "Start", action: () => {print("hello")}} + { iconName: "start-here", text: "Start", action: () => {print("hello")} }, + { type : "separator" }, ] + readonly property bool hasIcons: model.some(item => item.iconName !== undefined && item.iconName !== "") padding: 2 contentItem: ColumnLayout { @@ -21,18 +23,35 @@ BarPopup { Repeater { model: root.model - delegate: WButton { - id: btn - Layout.fillWidth: true + delegate: DelegateChooser { + role: "type" + DelegateChoice { + roleValue: "separator" + Rectangle { + Layout.topMargin: 2 + Layout.bottomMargin: 2 + Layout.fillWidth: true + implicitHeight: 1 + color: Looks.colors.bg0Border + } + } + DelegateChoice { + roleValue: undefined + WButton { + id: btn + Layout.fillWidth: true - required property var modelData - icon.name: modelData.iconName ? modelData.iconName : "" - monochromeIcon: modelData.monochromeIcon ?? true - text: modelData.text ? modelData.text : "" + required property var modelData + forceShowIcon: root.hasIcons + icon.name: modelData.iconName ? modelData.iconName : "" + monochromeIcon: modelData.monochromeIcon ?? true + text: modelData.text ? modelData.text : "" - onClicked: { - if (modelData.action) modelData.action(); - root.close(); + onClicked: { + if (modelData.action) modelData.action(); + root.close(); + } + } } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml b/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml index 30f695367..512b3b2bd 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml @@ -91,7 +91,7 @@ Loader { fill: realContent margins: -border.width } - border.color: ColorUtils.transparentize(Looks.colors.bg0Border, Looks.shadowTransparency) + border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, Looks.shadowTransparency) border.width: root.ambientShadowWidth color: "transparent" radius: realContent.radius + border.width diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml new file mode 100644 index 000000000..ac6118e8b --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml @@ -0,0 +1,41 @@ +import QtQuick +import qs +import qs.services +import qs.modules.common +import qs.modules.waffle.looks + +BarButton { + id: root + + visible: Updates.available && Updates.updateAdvised + + padding: 4 + contentItem: Item { + anchors.centerIn: parent + implicitWidth: iconContent.implicitWidth + implicitHeight: iconContent.implicitHeight + + FluentIcon { + id: iconContent + anchors.centerIn: parent + icon: "arrow-sync" + + Rectangle { + anchors { + right: parent.right + bottom: parent.bottom + margins: 1 + } + implicitWidth: 8 + implicitHeight: implicitWidth + radius: height / 2 + color: Updates.updateStronglyAdvised ? Looks.colors.warning : Looks.colors.accent + } + } + } + + BarToolTip { + extraVisibleCondition: root.shouldShowTooltip + text: Translation.tr("Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages").arg(Updates.count) + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml b/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml index aa064118a..2ac4dc944 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml @@ -68,6 +68,7 @@ Rectangle { shown: Config.options.waffles.bar.leftAlignApps sourceComponent: WidgetsButton {} } + UpdatesButton {} SystemButton {} TimeButton {} } diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskAppButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskAppButton.qml index 58f5a9959..a5915e83e 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskAppButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskAppButton.qml @@ -87,6 +87,13 @@ AppButton { id: contextMenu model: [ + ...((root.desktopEntry?.actions.length > 0) ? root.desktopEntry.actions.map(action =>({ + iconName: action.icon, + text: action.name, + action: () => { + action.execute() + } + })).concat({ type: "separator" }) : []), { iconName: root.iconName, text: root.desktopEntry ? root.desktopEntry.name : StringUtils.toTitleCase(appEntry.appId), diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml index 777dfb3e7..0228ee755 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml @@ -70,7 +70,7 @@ PopupWindow { fill: contentItem margins: -border.width } - border.color: ColorUtils.transparentize(Looks.colors.bg0Border, Looks.shadowTransparency) + border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, Looks.shadowTransparency) border.width: 1 color: "transparent" radius: Looks.radius.large + border.width diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index 8ece22ea5..d2f67fea7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -20,6 +20,7 @@ Singleton { property real shadowTransparency: 0.6 colors: QtObject { id: colors + property color ambientShadow: ColorUtils.transparentize("#000000", 0.4) property color bg0: root.dark ? "#1C1C1C" : "#EEEEEE" property color bg0Border: root.dark ? "#404040" : "#BEBEBE" property color bg1: root.dark ? "#2C2C2C" : "#F7F7F7" @@ -34,6 +35,7 @@ Singleton { property color fg1: root.dark ? "#D1D1D1" : "#626262" property color danger: "#C42B1C" property color dangerActive: "#B62D1F" + property color warning: "#FF9900" // property color accent: root.dark ? "#A5C6D8" : "#5377A3" property color accent: Appearance.m3colors.m3primary property color accentUnfocused: root.dark ? "#989898" : "#848484" diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml index 5583fd48f..3b48a79e3 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml @@ -14,6 +14,7 @@ Button { property color colBackground: ColorUtils.transparentize(Looks.colors.bg1) property alias monochromeIcon: buttonIcon.monochrome + property bool forceShowIcon: false property var altAction: () => {} property var middleClickAction: () => {} @@ -71,12 +72,12 @@ Button { spacing: 12 FluentIcon { id: buttonIcon + visible: root.icon.name !== "" || root.forceShowIcon monochrome: true implicitSize: 16 Layout.leftMargin: 6 Layout.fillWidth: false Layout.alignment: Qt.AlignVCenter - visible: root.icon.name !== "" icon: root.icon.name } WText { diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml index b35258204..6b89d4f24 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml @@ -10,14 +10,14 @@ import qs.modules.waffle.looks PopupToolTip { id: root - property real padding: 2 - verticalPadding: padding - horizontalPadding: padding + property real visualMargin: 11 + verticalPadding: visualMargin + horizontalPadding: visualMargin contentItem: Item { anchors.centerIn: parent - implicitWidth: realContent.implicitWidth + root.verticalPadding * 2 - implicitHeight: realContent.implicitHeight + root.horizontalPadding * 2 + implicitWidth: realContent.implicitWidth + 2 * 2 + implicitHeight: realContent.implicitHeight + 2 * 2 Rectangle { id: ambientShadow @@ -26,7 +26,7 @@ PopupToolTip { fill: realContent margins: -border.width } - border.color: ColorUtils.transparentize(Looks.colors.bg0Border, Looks.shadowTransparency) + border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, Looks.shadowTransparency) border.width: 1 color: "transparent" radius: realContent.radius + border.width diff --git a/dots/.config/quickshell/ii/services/Updates.qml b/dots/.config/quickshell/ii/services/Updates.qml new file mode 100644 index 000000000..58b8be892 --- /dev/null +++ b/dots/.config/quickshell/ii/services/Updates.qml @@ -0,0 +1,57 @@ +pragma Singleton + +import qs.modules.common +import qs.modules.common.functions +import QtQuick +import Quickshell +import Quickshell.Io + +/* + * System updates service. Currently only supports Arch. + */ +Singleton { + id: root + + property bool available: false + property int count: 0 + + readonly property bool updateAdvised: available && count > Config.options.updates.adviseUpdateThreshold + readonly property bool updateStronglyAdvised: available && count > Config.options.updates.stronglyAdviseUpdateThreshold + + function load() {} + function refresh() { + if (!available) return; + print("[Updates] Checking for system updates") + checkUpdatesProc.running = true; + } + + Timer { + interval: Config.options.updates.checkInterval * 60 * 1000 + repeat: true + running: Config.ready + onTriggered: { + print("[Updates] Periodic update check due") + root.refresh(); + } + } + + Process { + id: checkAvailabilityProc + running: true + command: ["which", "checkupdates"] + onExited: (exitCode, exitStatus) => { + root.available = (exitCode === 0); + root.refresh(); + } + } + + Process { + id: checkUpdatesProc + command: ["bash", "-c", "checkupdates | wc -l"] + stdout: StdioCollector { + onStreamFinished: { + root.count = parseInt(text.trim()); + } + } + } +} diff --git a/dots/.config/quickshell/ii/shell.qml b/dots/.config/quickshell/ii/shell.qml index ea100fdda..746c1003d 100644 --- a/dots/.config/quickshell/ii/shell.qml +++ b/dots/.config/quickshell/ii/shell.qml @@ -49,6 +49,7 @@ ShellRoot { ConflictKiller.load() Cliphist.refresh() Wallpapers.load() + Updates.load() } // Load enabled stuff