1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-29 14:32:08 -04:00

Compare commits

...

3 Commits

Author SHA1 Message Date
Body 389fffaf64 feat(Clipboard-Bar-Hist): Add search/filter to saved clipboard entries & animation states (#2464)
* Fix gaps and overlaps when filtering clipboard history

* feat(Clipboard-Bar-Hist): Add search/filter to saved clipboard entries as well. Change title on toggle between recent/saved.

* keep Pinned/Saved icon highlighted when selected

* add back filter animations

* Implement snap state for list views based on animation settings

---------

Co-authored-by: purian23 <purian23@gmail.com>
2026-05-30 15:10:58 -04:00
purian23 b7daf3f64a feat(ipc): add powerprofile status & shared profile helpers
- Follow-up to PR #2515
2026-05-30 14:57:01 -04:00
Huỳnh Thiện Lộc 461da22b08 feat(ipc): add native powerprofile target for power profiles management (#2515)
* feat: add native powerprofile IPC target for power profiles management

* feat: show centered PowerProfileModal with 3 square buttons for powerprofile IPC toggle

* style: enhance PowerProfileModal size, icons, description, and keyboard hints

* feat: add Space key binding to select highlighted power profile
2026-05-30 14:51:19 -04:00
14 changed files with 592 additions and 33 deletions
+63
View File
@@ -282,6 +282,53 @@ dms ipc call inhibit toggle
dms ipc call inhibit enable dms ipc call inhibit enable
``` ```
## Target: `powerprofile`
Power profile control via `power-profiles-daemon`. Changes stay in sync with DMS UI and trigger the power profile OSD when enabled.
Requires `power-profiles-daemon` to be installed and running. Works on all compositors.
### Functions
**`open`**
- Show the power profile picker modal
- Returns: Success confirmation or error if daemon unavailable
**`close`**
- Close the power profile picker modal
- Returns: Success confirmation
**`toggle`**
- Toggle power profile picker modal visibility
- Returns: Success confirmation or error if daemon unavailable
**`list`**
- List available profile slugs, one per line
- Returns: `power-saver`, `balanced`, and `performance` when supported
**`status`**
- Get the currently active profile slug
- Returns: `power-saver`, `balanced`, `performance`, or error if daemon unavailable
**`set <profile>`**
- Set the active power profile
- Parameters: Profile slug or alias — `power-saver` (`powersaver`, `saver`, `0`), `balanced` (`1`), `performance` (`2`)
- Returns: Success confirmation or error if profile unknown, unsupported, or write failed
**`cycle`**
- Cycle to the next available profile in order: power-saver → balanced → performance → power-saver
- Returns: Success confirmation or error if daemon unavailable or write failed
### Examples
```bash
dms ipc call powerprofile status
dms ipc call powerprofile list
dms ipc call powerprofile cycle
dms ipc call powerprofile set balanced
dms ipc call powerprofile set performance
dms ipc call powerprofile toggle
```
## Target: `wallpaper` ## Target: `wallpaper`
Wallpaper management and retrieval with support for per-monitor configurations. Wallpaper management and retrieval with support for per-monitor configurations.
@@ -543,6 +590,18 @@ Power menu modal control for system power actions.
- `close` - Hide power menu modal - `close` - Hide power menu modal
- `toggle` - Toggle power menu modal visibility - `toggle` - Toggle power menu modal visibility
### Target: `powerprofile`
Power profile picker modal and profile control via `power-profiles-daemon`.
**Functions:**
- `open` - Show power profile picker modal
- `close` - Hide power profile picker modal
- `toggle` - Toggle power profile picker modal visibility
- `list` - List available profile slugs
- `status` - Get current profile slug
- `set <profile>` - Set profile by slug or alias (`power-saver`, `balanced`, `performance`)
- `cycle` - Cycle to the next available profile
### Target: `control-center` ### Target: `control-center`
Control Center popout containing network, bluetooth, audio, power, and other quick settings. Control Center popout containing network, bluetooth, audio, power, and other quick settings.
@@ -673,6 +732,10 @@ dms ipc call processlist toggle
# Show power menu # Show power menu
dms ipc call powermenu toggle dms ipc call powermenu toggle
# Cycle or set power profile (requires power-profiles-daemon)
dms ipc call powerprofile cycle
dms ipc call powerprofile toggle
# Open notepad # Open notepad
dms ipc call notepad toggle dms ipc call notepad toggle
+1
View File
@@ -970,6 +970,7 @@ Singleton {
readonly property int shorterDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.shorter readonly property int shorterDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.shorter
readonly property int shortDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.short readonly property int shortDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.short
readonly property bool snapListModelChanges: shortDuration <= 0
readonly property int mediumDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.medium readonly property int mediumDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.medium
readonly property int longDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.long readonly property int longDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.long
readonly property int extraLongDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.extraLong readonly property int extraLongDuration: (typeof SettingsData !== "undefined" && SettingsData.animationSpeed === SettingsData.AnimationSpeed.Custom) ? SettingsData.customAnimationDuration : currentDurations.extraLong
+18
View File
@@ -1185,6 +1185,24 @@ Item {
} }
} }
LazyLoader {
id: powerProfileModalLoader
active: false
PowerProfileModal {
id: powerProfileModal
Component.onCompleted: {
PopoutService.powerProfileModal = powerProfileModal;
}
}
Component.onCompleted: {
PopoutService.powerProfileModalLoader = powerProfileModalLoader;
}
}
DMSShellIPC { DMSShellIPC {
powerMenuModalLoader: powerMenuModalLoader powerMenuModalLoader: powerMenuModalLoader
processListModalLoader: processListModalLoader processListModalLoader: processListModalLoader
+70
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import Quickshell.Services.UPower
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Modules.Settings.DisplayConfig import qs.Modules.Settings.DisplayConfig
@@ -1890,4 +1891,73 @@ Item {
target: "tray" target: "tray"
} }
IpcHandler {
function open(): string {
if (!PowerProfileWatcher.available)
return "ERROR: power-profiles-daemon not available";
PopoutService.openPowerProfileModal();
return "POWERPROFILE_OPEN_SUCCESS";
}
function close(): string {
PopoutService.closePowerProfileModal();
return "POWERPROFILE_CLOSE_SUCCESS";
}
function toggle(): string {
if (!PowerProfileWatcher.available)
return "ERROR: power-profiles-daemon not available";
PopoutService.togglePowerProfileModal();
return "POWERPROFILE_TOGGLE_SUCCESS";
}
function list(): string {
if (!PowerProfileWatcher.available)
return "ERROR: power-profiles-daemon not available";
return PowerProfileWatcher.availableProfiles.map(profile => PowerProfileWatcher.profileSlug(profile)).join("\n");
}
function status(): string {
if (!PowerProfileWatcher.available)
return "ERROR: power-profiles-daemon not available";
return PowerProfileWatcher.profileSlug(PowerProfiles.profile);
}
function set(profile: string): string {
if (!PowerProfileWatcher.available)
return "ERROR: power-profiles-daemon not available";
if (!profile)
return "ERROR: No profile specified";
const parsed = PowerProfileWatcher.parseProfileSlug(profile);
if (parsed === -1)
return "ERROR: Unknown power profile. Supported options: power-saver, balanced, performance";
if (parsed === PowerProfile.Performance && !PowerProfiles.hasPerformanceProfile)
return "ERROR: Performance profile not supported by hardware";
if (!PowerProfileWatcher.applyProfile(parsed))
return "ERROR: Failed to set power profile";
return "POWERPROFILE_SET_SUCCESS";
}
function cycle(): string {
if (!PowerProfileWatcher.available)
return "ERROR: power-profiles-daemon not available";
if (!PowerProfileWatcher.cycleProfile())
return "ERROR: Failed to set power profile";
return "POWERPROFILE_CYCLE_SUCCESS";
}
target: "powerprofile"
}
} }
@@ -26,7 +26,8 @@ Item {
ClipboardHeader { ClipboardHeader {
id: header id: header
width: parent.width width: parent.width
totalCount: modal.totalCount recentsCount: modal.unpinnedEntries.length
savedCount: modal.pinnedEntries.length
showKeyboardHints: modal.showKeyboardHints showKeyboardHints: modal.showKeyboardHints
activeTab: modal.activeTab activeTab: modal.activeTab
pinnedCount: modal.pinnedCount pinnedCount: modal.pinnedCount
@@ -99,6 +100,20 @@ Item {
pressDelay: 0 pressDelay: 0
flickableDirection: Flickable.VerticalFlick flickableDirection: Flickable.VerticalFlick
states: [
State {
name: "snap"
when: Theme.snapListModelChanges
PropertyChanges {
target: clipboardListView
add: null
remove: null
displaced: null
move: null
}
}
]
function ensureVisible(index) { function ensureVisible(index) {
if (index < 0 || index >= count) { if (index < 0 || index >= count) {
return; return;
@@ -159,6 +174,20 @@ Item {
pressDelay: 0 pressDelay: 0
flickableDirection: Flickable.VerticalFlick flickableDirection: Flickable.VerticalFlick
states: [
State {
name: "snap"
when: Theme.snapListModelChanges
PropertyChanges {
target: savedListView
add: null
remove: null
displaced: null
move: null
}
}
]
function ensureVisible(index) { function ensureVisible(index) {
if (index < 0 || index >= count) { if (index < 0 || index >= count) {
return; return;
@@ -6,7 +6,8 @@ import qs.Modals.Clipboard
Item { Item {
id: header id: header
property int totalCount: 0 property int recentsCount: 0
property int savedCount: 0
property bool showKeyboardHints: false property bool showKeyboardHints: false
property string activeTab: "recents" property string activeTab: "recents"
property int pinnedCount: 0 property int pinnedCount: 0
@@ -31,7 +32,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("Clipboard History") + ` (${totalCount})` text: (header.activeTab === "saved" ? I18n.tr("Clipboard Saved") : I18n.tr("Clipboard History")) + ` (${header.activeTab === "saved" ? header.savedCount : header.recentsCount})`
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -48,6 +49,7 @@ Item {
iconName: "push_pin" iconName: "push_pin"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4
iconColor: header.activeTab === "saved" ? Theme.primary : Theme.surfaceText iconColor: header.activeTab === "saved" ? Theme.primary : Theme.surfaceText
backgroundColor: header.activeTab === "saved" ? Theme.primarySelected : "transparent"
visible: header.pinnedCount > 0 visible: header.pinnedCount > 0
tooltipText: header.activeTab === "saved" ? I18n.tr("Recent") : I18n.tr("Saved") tooltipText: header.activeTab === "saved" ? I18n.tr("Recent") : I18n.tr("Saved")
onClicked: tabChanged(header.activeTab === "saved" ? "recents" : "saved") onClicked: tabChanged(header.activeTab === "saved" ? "recents" : "saved")
+277
View File
@@ -0,0 +1,277 @@
import QtQuick
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
import Quickshell.Services.UPower
DankModal {
id: root
layerNamespace: "dms:power-profiles"
keepPopoutsOpen: true
property int selectedIndex: 0
property var profileModel: PowerProfileWatcher.availableProfiles
function openCentered() {
open();
}
function hideDialog() {
close();
}
shouldBeVisible: false
modalWidth: 440
modalHeight: 290
enableShadow: true
onBackgroundClicked: hideDialog()
onShouldBeVisibleChanged: {
if (!shouldBeVisible)
return;
if (typeof PowerProfiles !== "undefined") {
const current = PowerProfiles.profile;
const idx = profileModel.indexOf(current);
if (idx !== -1) {
selectedIndex = idx;
}
}
}
onShouldHaveFocusChanged: {
if (!shouldHaveFocus)
return;
Qt.callLater(() => modalFocusScope.forceActiveFocus());
}
modalFocusScope.Keys.onPressed: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
switch (event.key) {
case Qt.Key_Left:
case Qt.Key_Up:
case Qt.Key_Backtab:
selectedIndex = (selectedIndex - 1 + profileModel.length) % profileModel.length;
event.accepted = true;
break;
case Qt.Key_Right:
case Qt.Key_Down:
case Qt.Key_Tab:
selectedIndex = (selectedIndex + 1) % profileModel.length;
event.accepted = true;
break;
case Qt.Key_Space:
case Qt.Key_Return:
case Qt.Key_Enter:
if (selectedIndex >= 0 && selectedIndex < profileModel.length) {
setProfile(profileModel[selectedIndex]);
}
event.accepted = true;
break;
case Qt.Key_1:
if (profileModel.length > 0) {
setProfile(profileModel[0]);
}
event.accepted = true;
break;
case Qt.Key_2:
if (profileModel.length > 1) {
setProfile(profileModel[1]);
}
event.accepted = true;
break;
case Qt.Key_3:
if (profileModel.length > 2) {
setProfile(profileModel[2]);
}
event.accepted = true;
break;
case Qt.Key_Escape:
hideDialog();
event.accepted = true;
break;
}
}
function setProfile(profile) {
if (PowerProfileWatcher.applyProfile(profile)) {
hideDialog();
return;
}
if (!PowerProfileWatcher.available)
ToastService.showError(I18n.tr("power-profiles-daemon not available"));
else
ToastService.showError(I18n.tr("Failed to set power profile"));
}
content: Component {
Item {
anchors.fill: parent
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
Row {
width: parent.width
Column {
width: parent.width - 40
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Power Mode")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: I18n.tr("Choose a power profile")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
width: parent.width
elide: Text.ElideRight
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: root.hideDialog()
}
}
Row {
id: buttonsRow
width: parent.width
spacing: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
model: root.profileModel
Rectangle {
id: profileButton
required property int index
required property int modelData
readonly property bool isSelected: root.selectedIndex === index
readonly property bool isActive: (typeof PowerProfiles !== "undefined") && PowerProfiles.profile === modelData
width: (parent.width - Theme.spacingM * (root.profileModel.length - 1)) / root.profileModel.length
height: 120
radius: Theme.cornerRadius
color: {
if (isActive)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
if (isSelected)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
if (mouseArea.containsMouse)
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.06);
}
border.color: isActive ? Theme.primary : (isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) : "transparent")
border.width: (isActive || isSelected) ? 2 : 0
// Shortcut Key Badge on Top-Right Corner
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Theme.spacingS
width: 20
height: 20
radius: 4
color: isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
border.color: isActive ? Theme.primary : "transparent"
border.width: isActive ? 1 : 0
StyledText {
text: (index + 1).toString()
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: isActive ? Theme.primary : Theme.surfaceTextMedium
anchors.centerIn: parent
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: Theme.getPowerProfileIcon(modelData)
size: Theme.iconSize + 16
color: isActive ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: Theme.getPowerProfileLabel(modelData)
font.pixelSize: Theme.fontSizeMedium
color: isActive ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
root.selectedIndex = index;
}
onClicked: {
root.setProfile(modelData);
}
}
}
}
}
// Selected power profile description
StyledText {
text: (root.selectedIndex >= 0 && root.selectedIndex < root.profileModel.length) ? Theme.getPowerProfileDescription(root.profileModel[root.selectedIndex]) : ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: parent.width - Theme.spacingL * 2
}
// Keyboard Shortcut Guide Footer
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXS
opacity: 0.5
DankIcon {
name: "keyboard"
size: Theme.fontSizeSmall
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Use keys 1-3 or arrows, Enter/Space to select")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceText
}
}
}
}
}
}
@@ -24,14 +24,13 @@ Rectangle {
} }
function setProfile(profile) { function setProfile(profile) {
if (typeof PowerProfiles === "undefined") { if (PowerProfileWatcher.applyProfile(profile))
ToastService.showError(I18n.tr("power-profiles-daemon not available"));
return; return;
}
PowerProfiles.profile = profile; if (!PowerProfileWatcher.available)
if (PowerProfiles.profile !== profile) { ToastService.showError(I18n.tr("power-profiles-daemon not available"));
else
ToastService.showError(I18n.tr("Failed to set power profile")); ToastService.showError(I18n.tr("Failed to set power profile"));
}
} }
Column { Column {
@@ -193,7 +192,7 @@ Rectangle {
} }
DankButtonGroup { DankButtonGroup {
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] property var profileModel: PowerProfileWatcher.availableProfiles
property int currentProfileIndex: { property int currentProfileIndex: {
if (typeof PowerProfiles === "undefined") if (typeof PowerProfiles === "undefined")
return 1; return 1;
@@ -21,14 +21,13 @@ DankPopout {
} }
function setProfile(profile) { function setProfile(profile) {
if (typeof PowerProfiles === "undefined") { if (PowerProfileWatcher.applyProfile(profile))
ToastService.showError(I18n.tr("power-profiles-daemon not available"));
return; return;
}
PowerProfiles.profile = profile; if (!PowerProfileWatcher.available)
if (PowerProfiles.profile !== profile) { ToastService.showError(I18n.tr("power-profiles-daemon not available"));
else
ToastService.showError(I18n.tr("Failed to set power profile")); ToastService.showError(I18n.tr("Failed to set power profile"));
}
} }
popupWidth: 400 popupWidth: 400
@@ -555,7 +554,7 @@ DankPopout {
DankButtonGroup { DankButtonGroup {
id: profileButtonGroup id: profileButtonGroup
property var profileModel: (typeof PowerProfiles !== "undefined") ? [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []) : [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance] property var profileModel: PowerProfileWatcher.availableProfiles
property int currentProfileIndex: { property int currentProfileIndex: {
if (typeof PowerProfiles === "undefined") if (typeof PowerProfiles === "undefined")
return 1; return 1;
@@ -140,30 +140,24 @@ BasePill {
log.info("Trigger! Delta: " + delta); log.info("Trigger! Delta: " + delta);
// This is after the other delta checks so it only shows on valid Y scroll // This is after the other delta checks so it only shows on valid Y scroll
if (typeof PowerProfiles === "undefined") { if (!PowerProfileWatcher.available) {
ToastService.showError(I18n.tr("power-profiles-daemon not available")); ToastService.showError(I18n.tr("power-profiles-daemon not available"));
return; return;
} }
// Get list of profiles, and current index const profiles = PowerProfileWatcher.availableProfiles;
const profiles = [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []);
var index = profiles.findIndex(profile => PowerProfiles.profile === profile); var index = profiles.findIndex(profile => PowerProfiles.profile === profile);
// Step once based on mouse wheel direction
if (delta > 0) if (delta > 0)
index += 1; index += 1;
else else
index -= 1; index -= 1;
// Already at end of list, can't go further
if (index < 0 || index >= profiles.length) if (index < 0 || index >= profiles.length)
return; return;
// Set new profile if (!PowerProfileWatcher.applyProfile(profiles[index]))
PowerProfiles.profile = profiles[index];
if (PowerProfiles.profile !== profiles[index]) {
ToastService.showError(I18n.tr("Failed to set power profile")); ToastService.showError(I18n.tr("Failed to set power profile"));
}
} }
} }
} }
@@ -301,10 +301,19 @@ Item {
clip: true clip: true
spacing: 2 spacing: 2
add: null states: [
remove: null State {
displaced: null name: "snap"
move: null when: Theme.snapListModelChanges
PropertyChanges {
target: processListView
add: null
remove: null
displaced: null
move: null
}
}
]
model: ScriptModel { model: ScriptModel {
values: root.cachedProcesses values: root.cachedProcesses
+5 -3
View File
@@ -68,15 +68,17 @@ Singleton {
clipboardEntries = filtered; clipboardEntries = filtered;
unpinnedEntries = filtered.filter(e => !e.pinned); unpinnedEntries = filtered.filter(e => !e.pinned);
pinnedEntries = filtered.filter(e => e.pinned);
totalCount = clipboardEntries.length; totalCount = clipboardEntries.length;
if (unpinnedEntries.length === 0) { const activeCount = Math.max(unpinnedEntries.length, pinnedEntries.length);
if (activeCount === 0) {
keyboardNavigationActive = false; keyboardNavigationActive = false;
selectedIndex = 0; selectedIndex = 0;
return; return;
} }
if (selectedIndex >= unpinnedEntries.length) { if (selectedIndex >= activeCount) {
selectedIndex = unpinnedEntries.length - 1; selectedIndex = activeCount - 1;
} }
} }
+36
View File
@@ -50,6 +50,8 @@ Singleton {
property var bluetoothPairingModal: null property var bluetoothPairingModal: null
property var networkInfoModal: null property var networkInfoModal: null
property var windowRuleModalLoader: null property var windowRuleModalLoader: null
property var powerProfileModal: null
property var powerProfileModalLoader: null
property var notepadSlideouts: [] property var notepadSlideouts: []
@@ -675,6 +677,40 @@ Singleton {
} }
} }
function openPowerProfileModal() {
if (powerProfileModal) {
powerProfileModal.openCentered();
} else if (powerProfileModalLoader) {
powerProfileModalLoader.active = true;
Qt.callLater(() => powerProfileModal?.openCentered());
}
}
function closePowerProfileModal() {
powerProfileModal?.close();
}
function togglePowerProfileModal() {
if (powerProfileModal) {
if (powerProfileModal.shouldBeVisible) {
powerProfileModal.close();
} else {
powerProfileModal.openCentered();
}
} else if (powerProfileModalLoader) {
powerProfileModalLoader.active = true;
Qt.callLater(() => {
if (powerProfileModal) {
if (powerProfileModal.shouldBeVisible) {
powerProfileModal.close();
} else {
powerProfileModal.openCentered();
}
}
});
}
}
function showProcessListModal() { function showProcessListModal() {
if (processListModal) { if (processListModal) {
processListModal.show(); processListModal.show();
@@ -11,8 +11,68 @@ Singleton {
property int currentProfile: -1 property int currentProfile: -1
property int previousProfile: -1 property int previousProfile: -1
readonly property bool available: typeof PowerProfiles !== "undefined"
readonly property var availableProfiles: {
if (!available)
return [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance];
return [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []);
}
signal profileChanged(int profile) signal profileChanged(int profile)
function profileSlug(profile: int): string {
switch (profile) {
case PowerProfile.PowerSaver:
return "power-saver";
case PowerProfile.Balanced:
return "balanced";
case PowerProfile.Performance:
return "performance";
default:
return "unknown";
}
}
function parseProfileSlug(slug: string): int {
if (!slug)
return -1;
const lower = slug.toLowerCase().trim();
if (lower === "power-saver" || lower === "powersaver" || lower === "saver" || lower === "0")
return PowerProfile.PowerSaver;
if (lower === "balanced" || lower === "1")
return PowerProfile.Balanced;
if (lower === "performance" || lower === "2")
return PowerProfile.Performance;
return -1;
}
function applyProfile(profile: int): bool {
if (!available)
return false;
if (profile === PowerProfile.Performance && !PowerProfiles.hasPerformanceProfile)
return false;
if (availableProfiles.indexOf(profile) === -1)
return false;
PowerProfiles.profile = profile;
return PowerProfiles.profile === profile;
}
function cycleProfile(): bool {
if (!available)
return false;
const profiles = availableProfiles;
const index = profiles.indexOf(PowerProfiles.profile);
const nextProfile = index === -1 ? PowerProfile.Balanced : profiles[(index + 1) % profiles.length];
return applyProfile(nextProfile);
}
Connections { Connections {
target: typeof PowerProfiles !== "undefined" ? PowerProfiles : null target: typeof PowerProfiles !== "undefined" ? PowerProfiles : null